diff --git a/README.md b/README.md index b857711f..3c738025 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/?branch=master) ![PHPUnit Tests](https://github.com/Part-DB/Part-DB-symfony/workflows/PHPUnit%20Tests/badge.svg) ![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg) [![codecov](https://codecov.io/gh/Part-DB/Part-DB-server/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server) @@ -62,7 +61,8 @@ for the first time. * Automatic thumbnail generation for pictures * Use cloud providers (like Octopart, Digikey, Farnell, LCSC or TME) to automatically get part information, datasheets, and prices for parts -* Retrieve part information from arbitrary shop websites, using either conventional data extraction from structured metadata, or AI based data extraction +* Retrieve part information from arbitrary shop websites, using either conventional data extraction from structured metadata, or AI based data extraction. +A browser plugin allows to quickly submit parts from any website to your Part-DB instance, and even allows to circumvent anti-bot measures on shop websites. * API to access Part-DB from other applications/scripts * [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as the central datasource for your KiCad and see available parts from Part-DB directly inside KiCad. diff --git a/VERSION b/VERSION index 6ceb272e..d8b69897 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.11.1 +2.12.0 diff --git a/assets/controllers/common/dirty_form_controller.js b/assets/controllers/common/dirty_form_controller.js new file mode 100644 index 00000000..aad2e6b0 --- /dev/null +++ b/assets/controllers/common/dirty_form_controller.js @@ -0,0 +1,274 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {Controller} from "@hotwired/stimulus"; +import {visit} from "@hotwired/turbo"; +import * as bootbox from "bootbox"; +import "../../css/components/bootbox_extensions.css"; +import "../../css/components/dirty_form.css"; + +/** + * Attach to a
element (or a wrapper containing a ) to prevent accidental navigation + * away when the form has unsaved changes. + * + * Dirty detection is event-driven: `change` and `input` events bubble up to the form and trigger + * a check of whether any element's current value differs from the DOM default recorded in the HTML + * (`defaultValue` / `defaultChecked` / `option.defaultSelected`). Using both events covers both + * native widgets (which fire `change`) and rich-text editors like CKEditor (which fire `input` + * when they sync their underlying textarea). + * + * Validation failures (server returns 200 with `.is-invalid` fields) are always treated as dirty: + * the submitted data was never saved, so navigating away would lose it. This removes the need for + * any snapshot mechanism — the `.is-invalid` classes in the re-rendered HTML are the signal. + * + * Intercepts three navigation paths: + * 1. Any link click (capture phase) + * 2. window beforeunload + * 3. turbo:before-visit + * + * Values: + * - confirmTitle (String) – dialog title + * - confirmMessage (String) – dialog body text + */ +export default class extends Controller { + static values = { + confirmTitle: {type: String, default: 'Unsaved Changes'}, + confirmMessage: {type: String, default: 'You have unsaved changes. Are you sure you want to leave this page?'}, + }; + + connect() { + this._form = (this.element.tagName === 'FORM') ? this.element : this.element.querySelector('form'); + this._isDirty = false; + this._submitting = false; + this._navigating = false; + + this._changeHandler = this._handleChange.bind(this); + this._linkClickHandler = this._handleLinkClick.bind(this); + this._beforeUnloadHandler = this._handleBeforeUnload.bind(this); + this._turboBeforeVisitHandler = this._handleTurboBeforeVisit.bind(this); + this._turboSubmitEndHandler = this._handleTurboSubmitEnd.bind(this); + + if (this._form) { + this._form.addEventListener('change', this._changeHandler); + // CKEditor (and other rich-text widgets) dispatch `input` rather than `change` + // when their underlying textarea value is updated. + this._form.addEventListener('input', this._changeHandler); + } + document.addEventListener('click', this._linkClickHandler, true); + window.addEventListener('beforeunload', this._beforeUnloadHandler); + document.addEventListener('turbo:before-visit', this._turboBeforeVisitHandler); + document.addEventListener('turbo:submit-end', this._turboSubmitEndHandler); + + const modal = this.element.closest('.modal'); + if (modal) { + this._modal = modal; + this._modalHideHandler = this._handleModalHide.bind(this); + modal.addEventListener('hide.bs.modal', this._modalHideHandler); + } + } + + disconnect() { + if (this._form) { + this._form.removeEventListener('change', this._changeHandler); + this._form.removeEventListener('input', this._changeHandler); + } + document.removeEventListener('click', this._linkClickHandler, true); + window.removeEventListener('beforeunload', this._beforeUnloadHandler); + document.removeEventListener('turbo:before-visit', this._turboBeforeVisitHandler); + document.removeEventListener('turbo:submit-end', this._turboSubmitEndHandler); + + if (this._modal && this._modalHideHandler) { + this._modal.removeEventListener('hide.bs.modal', this._modalHideHandler); + } + } + + /** data-action="submit->common--dirty-form#submit" — suppresses the guard while saving. */ + submit() { + this._submitting = true; + } + + /** + * data-action="reset->common--dirty-form#resetDirtyState" — marks the form as clean after + * a programmatic reset. Native change events are not fired by form.reset(), so we set the + * flag directly. Turbo also calls form.reset() internally before the post-submit redirect; + * the _submitting guard prevents that from incorrectly clearing the flag. + */ + resetDirtyState() { + if (this._submitting) return; + + // Wait for a frame to allow the form's DOM state to update after the reset() call, then refresh markers and update the dirty flag. + requestAnimationFrame(() => { + this._isDirty = false; + this._clearDirtyMarkers(); + }); + } + + _handleChange(event) { + const target = event?.target; + if (target?.name) { + this._updateDirtyMarker(target); + } else { + this._refreshDirtyMarkers(); + } + this._isDirty = this._form?.querySelector('[data-dirty]') !== null; + } + + /** + * Walk every named form element and update its `data-dirty` attribute. + * Un-named elements (e.g. the visible TristateCheckbox whose name was removed) are + * skipped — they are not submitted and are not the source of truth for form data. + */ + _refreshDirtyMarkers() { + if (!this._form) return; + for (const el of this._form.elements) { + if (!el.name) continue; + this._updateDirtyMarker(el); + } + } + + /** + * Set or clear `data-dirty` on a single named form element. + * Hidden inputs are not visually rendered, so special handling applies: + * - TristateCheckbox: the hidden backing input is preceded by a nameless visual checkbox — + * mark that instead. + * - Other hidden inputs (e.g. CSRF tokens): ignored. + * TomSelect hides the so the dirty-check + * controller can detect changes, and restores that value when the form is reset. + */ +export default function form_reset_handler() { + const self = this; + const input = this.input; + + // Multiple selects not yet supported + if (input.multiple) { + return; + } + + // Always capture the initial value, even empty string. + // Empty string is falsy, so the old `|| null` guard would silently skip it, + // leaving data-default-value unset and breaking the dirty check for blank defaults. + input.dataset.defaultValue = input.value; + + if (input.form) { + input.form.addEventListener('reset', () => { + input.value = input.dataset.defaultValue ?? ''; + self.sync(); + }); + } +} diff --git a/composer.json b/composer.json index b23ea92b..a1f15834 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "api-platform/json-api": "^4.0.0", "api-platform/symfony": "^4.0.0", "beberlei/doctrineextensions": "^1.2", - "brick/math": "^0.14.8", + "brick/math": "^0.17.0", "brick/schema": "^0.2.0", "composer/ca-bundle": "^1.5", "composer/package-versions-deprecated": "^1.11.99.5", @@ -57,9 +57,9 @@ "scheb/2fa-trusted-device": "^v7.11.0", "shivas/versioning-bundle": "^4.0", "spatie/db-dumper": "^3.3.1", - "symfony/ai-bundle": "^0.8.0", - "symfony/ai-lm-studio-platform": "^0.8.0", - "symfony/ai-open-router-platform": "^0.8.0", + "symfony/ai-bundle": "^0.9.0", + "symfony/ai-lm-studio-platform": "^0.9.0", + "symfony/ai-open-router-platform": "^0.9.0", "symfony/apache-pack": "^1.0", "symfony/asset": "7.4.*", "symfony/console": "7.4.*", diff --git a/composer.lock b/composer.lock index e4144934..34715b7b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "31a276e9a2b45a04facbe2d88f4a042f", + "content-hash": "d6bda397c505e1e6d540c814a2368fbb", "packages": [ { "name": "amphp/amp", @@ -456,16 +456,16 @@ }, { "name": "amphp/http-client", - "version": "v5.3.4", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/amphp/http-client.git", - "reference": "75ad21574fd632594a2dd914496647816d5106bc" + "reference": "ca155026acafa74a612d776a97202d53077fee86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http-client/zipball/75ad21574fd632594a2dd914496647816d5106bc", - "reference": "75ad21574fd632594a2dd914496647816d5106bc", + "url": "https://api.github.com/repos/amphp/http-client/zipball/ca155026acafa74a612d776a97202d53077fee86", + "reference": "ca155026acafa74a612d776a97202d53077fee86", "shasum": "" }, "require": { @@ -493,9 +493,8 @@ "amphp/phpunit-util": "^3", "ext-json": "*", "kelunik/link-header-rfc5988": "^1", - "laminas/laminas-diactoros": "^2.3", "phpunit/phpunit": "^9", - "psalm/phar": "~5.23" + "psalm/phar": "6.16.1" }, "suggest": { "amphp/file": "Required for file request bodies and HTTP archive logging", @@ -542,7 +541,7 @@ ], "support": { "issues": "https://github.com/amphp/http-client/issues", - "source": "https://github.com/amphp/http-client/tree/v5.3.4" + "source": "https://github.com/amphp/http-client/tree/v5.3.6" }, "funding": [ { @@ -550,7 +549,7 @@ "type": "github" } ], - "time": "2025-08-16T20:41:23+00:00" + "time": "2026-05-15T23:29:38+00:00" }, { "name": "amphp/parser", @@ -616,16 +615,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.2.3", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "7b52598c2e9105ebcddf247fc523161581930367" + "reference": "a044733e080940d1483f56caff0c412ad6982776" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", - "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/a044733e080940d1483f56caff0c412ad6982776", + "reference": "a044733e080940d1483f56caff0c412ad6982776", "shasum": "" }, "require": { @@ -637,7 +636,7 @@ "amphp/php-cs-fixer-config": "^2", "amphp/phpunit-util": "^3", "phpunit/phpunit": "^9", - "psalm/phar": "^5.18" + "psalm/phar": "6.16.1" }, "type": "library", "autoload": { @@ -671,7 +670,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + "source": "https://github.com/amphp/pipeline/tree/v1.2.4" }, "funding": [ { @@ -679,7 +678,7 @@ "type": "github" } ], - "time": "2025-03-16T16:33:53+00:00" + "time": "2026-05-06T05:37:57+00:00" }, { "name": "amphp/process", @@ -977,22 +976,22 @@ }, { "name": "api-platform/doctrine-common", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-common.git", - "reference": "2072247e3c8126d815f20324e7aaa97c2b5ee889" + "reference": "089b196c2f8e4d14333aaa3c6db33356e8fd8be0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/2072247e3c8126d815f20324e7aaa97c2b5ee889", - "reference": "2072247e3c8126d815f20324e7aaa97c2b5ee889", + "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/089b196c2f8e4d14333aaa3c6db33356e8fd8be0", + "reference": "089b196c2f8e4d14333aaa3c6db33356e8fd8be0", "shasum": "" }, "require": { "api-platform/metadata": "^4.2.6", "api-platform/state": "^4.2.4", - "doctrine/collections": "^2.1", + "doctrine/collections": "^2.1 || ^3.0", "doctrine/common": "^3.2.2", "doctrine/persistence": "^3.2 || ^4.0", "php": ">=8.2" @@ -1061,22 +1060,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-common/tree/v4.3.4" + "source": "https://github.com/api-platform/doctrine-common/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-04T13:25:58+00:00" }, { "name": "api-platform/doctrine-orm", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-orm.git", - "reference": "3dc88ee48ffcdb6eee45ec1d3e9f25ea2aad4eaa" + "reference": "095a4c56cdd9986208100dedd5d28be50a4830ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/3dc88ee48ffcdb6eee45ec1d3e9f25ea2aad4eaa", - "reference": "3dc88ee48ffcdb6eee45ec1d3e9f25ea2aad4eaa", + "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/095a4c56cdd9986208100dedd5d28be50a4830ba", + "reference": "095a4c56cdd9986208100dedd5d28be50a4830ba", "shasum": "" }, "require": { @@ -1150,13 +1149,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-orm/tree/v4.3.4" + "source": "https://github.com/api-platform/doctrine-orm/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-07T11:45:31+00:00" }, { "name": "api-platform/documentation", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/documentation.git", @@ -1213,13 +1212,13 @@ ], "description": "API Platform documentation controller.", "support": { - "source": "https://github.com/api-platform/documentation/tree/v4.3.4" + "source": "https://github.com/api-platform/documentation/tree/v4.3.6" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/http-cache", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/http-cache.git", @@ -1293,22 +1292,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/http-cache/tree/v4.3.4" + "source": "https://github.com/api-platform/http-cache/tree/v4.3.6" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/hydra", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/hydra.git", - "reference": "9b0a677b21ee4f2ec255386a84bdcf1d12ea7bc4" + "reference": "317a696e396b80ba87de2560679c362923ef0a14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/hydra/zipball/9b0a677b21ee4f2ec255386a84bdcf1d12ea7bc4", - "reference": "9b0a677b21ee4f2ec255386a84bdcf1d12ea7bc4", + "url": "https://api.github.com/repos/api-platform/hydra/zipball/317a696e396b80ba87de2560679c362923ef0a14", + "reference": "317a696e396b80ba87de2560679c362923ef0a14", "shasum": "" }, "require": { @@ -1380,22 +1379,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/hydra/tree/v4.3.4" + "source": "https://github.com/api-platform/hydra/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-11T11:50:19+00:00" }, { "name": "api-platform/json-api", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/json-api.git", - "reference": "30e399ea2266403d04fd93df83c6983cf0a30e5d" + "reference": "3a562e7f1bb1bc802e58eff674a20b78fe107275" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/json-api/zipball/30e399ea2266403d04fd93df83c6983cf0a30e5d", - "reference": "30e399ea2266403d04fd93df83c6983cf0a30e5d", + "url": "https://api.github.com/repos/api-platform/json-api/zipball/3a562e7f1bb1bc802e58eff674a20b78fe107275", + "reference": "3a562e7f1bb1bc802e58eff674a20b78fe107275", "shasum": "" }, "require": { @@ -1462,13 +1461,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/json-api/tree/v4.3.4" + "source": "https://github.com/api-platform/json-api/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-22T11:06:32+00:00" }, { "name": "api-platform/json-schema", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/json-schema.git", @@ -1543,13 +1542,13 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/json-schema/tree/v4.3.4" + "source": "https://github.com/api-platform/json-schema/tree/v4.3.6" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/jsonld", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/jsonld.git", @@ -1623,22 +1622,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/jsonld/tree/v4.3.4" + "source": "https://github.com/api-platform/jsonld/tree/v4.3.6" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/metadata", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/metadata.git", - "reference": "e93caa26e7992ca138f2d12f79b5b25d2d091b7b" + "reference": "e9e8a7b85d2d513edff3b108072f8ab23a9d6344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/metadata/zipball/e93caa26e7992ca138f2d12f79b5b25d2d091b7b", - "reference": "e93caa26e7992ca138f2d12f79b5b25d2d091b7b", + "url": "https://api.github.com/repos/api-platform/metadata/zipball/e9e8a7b85d2d513edff3b108072f8ab23a9d6344", + "reference": "e9e8a7b85d2d513edff3b108072f8ab23a9d6344", "shasum": "" }, "require": { @@ -1721,13 +1720,13 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/metadata/tree/v4.3.4" + "source": "https://github.com/api-platform/metadata/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-22T12:00:17+00:00" }, { "name": "api-platform/openapi", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/openapi.git", @@ -1812,22 +1811,22 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/openapi/tree/v4.3.4" + "source": "https://github.com/api-platform/openapi/tree/v4.3.6" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/serializer", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/serializer.git", - "reference": "bd7c26cc8e6858abc9661d677c15eaf4c61e08e3" + "reference": "2c4f996bb6e5fef49106df0c48d0c1954e10998b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/serializer/zipball/bd7c26cc8e6858abc9661d677c15eaf4c61e08e3", - "reference": "bd7c26cc8e6858abc9661d677c15eaf4c61e08e3", + "url": "https://api.github.com/repos/api-platform/serializer/zipball/2c4f996bb6e5fef49106df0c48d0c1954e10998b", + "reference": "2c4f996bb6e5fef49106df0c48d0c1954e10998b", "shasum": "" }, "require": { @@ -1836,7 +1835,7 @@ "php": ">=8.2", "symfony/property-access": "^6.4 || ^7.0 || ^8.0", "symfony/property-info": "^6.4 || ^7.1 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/serializer": "^6.4.37 || ^7.4.9 || ^8.0.9", "symfony/validator": "^6.4.11 || ^7.0 || ^8.0" }, "require-dev": { @@ -1906,22 +1905,22 @@ "serializer" ], "support": { - "source": "https://github.com/api-platform/serializer/tree/v4.3.4" + "source": "https://github.com/api-platform/serializer/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-12T10:07:44+00:00" }, { "name": "api-platform/state", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/state.git", - "reference": "dda8789e95b1627a6427edb48f9024b306fdf5ff" + "reference": "6e3f6d75e605ba7171a7590c82da5126979a936b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/state/zipball/dda8789e95b1627a6427edb48f9024b306fdf5ff", - "reference": "dda8789e95b1627a6427edb48f9024b306fdf5ff", + "url": "https://api.github.com/repos/api-platform/state/zipball/6e3f6d75e605ba7171a7590c82da5126979a936b", + "reference": "6e3f6d75e605ba7171a7590c82da5126979a936b", "shasum": "" }, "require": { @@ -1929,7 +1928,7 @@ "php": ">=8.2", "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^3.1", - "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", "symfony/serializer": "^6.4 || ^7.0 || ^8.0", "symfony/translation-contracts": "^3.0" }, @@ -2003,22 +2002,22 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/state/tree/v4.3.4" + "source": "https://github.com/api-platform/state/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-22T12:02:28+00:00" }, { "name": "api-platform/symfony", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/symfony.git", - "reference": "532063884e3f91a8a831322a572220cc55501a2f" + "reference": "13308ad99dd1479e70fe79c20519d8135df8e7b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/symfony/zipball/532063884e3f91a8a831322a572220cc55501a2f", - "reference": "532063884e3f91a8a831322a572220cc55501a2f", + "url": "https://api.github.com/repos/api-platform/symfony/zipball/13308ad99dd1479e70fe79c20519d8135df8e7b9", + "reference": "13308ad99dd1479e70fe79c20519d8135df8e7b9", "shasum": "" }, "require": { @@ -2035,6 +2034,7 @@ "php": ">=8.2", "symfony/asset": "^6.4 || ^7.0 || ^8.0", "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", "symfony/property-access": "^6.4 || ^7.0 || ^8.0", "symfony/property-info": "^6.4 || ^7.0 || ^8.0", "symfony/security-core": "^6.4 || ^7.0 || ^8.0", @@ -2131,28 +2131,28 @@ "symfony" ], "support": { - "source": "https://github.com/api-platform/symfony/tree/v4.3.4" + "source": "https://github.com/api-platform/symfony/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-18T09:34:32+00:00" }, { "name": "api-platform/validator", - "version": "v4.3.4", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/api-platform/validator.git", - "reference": "22693bc3d3538af700cf274b99c834c37b1d1a68" + "reference": "6df6804799f8831469d2602d0845a0316e81fbab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/validator/zipball/22693bc3d3538af700cf274b99c834c37b1d1a68", - "reference": "22693bc3d3538af700cf274b99c834c37b1d1a68", + "url": "https://api.github.com/repos/api-platform/validator/zipball/6df6804799f8831469d2602d0845a0316e81fbab", + "reference": "6df6804799f8831469d2602d0845a0316e81fbab", "shasum": "" }, "require": { "api-platform/metadata": "^4.3", "php": ">=8.2", - "symfony/http-kernel": "^6.4 || ^7.1 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.1 || ^8.0", "symfony/serializer": "^6.4 || ^7.1 || ^8.0", "symfony/type-info": "^7.3 || ^8.0", "symfony/validator": "^6.4.11 || ^7.1 || ^8.0", @@ -2207,9 +2207,9 @@ "validator" ], "support": { - "source": "https://github.com/api-platform/validator/tree/v4.3.4" + "source": "https://github.com/api-platform/validator/tree/v4.3.6" }, - "time": "2026-04-30T12:21:24+00:00" + "time": "2026-05-07T11:45:31+00:00" }, { "name": "beberlei/assert", @@ -2342,23 +2342,22 @@ }, { "name": "brick/math", - "version": "0.14.8", + "version": "0.17.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "6aef71a9fbbd1ee7be0e313cd627f8e6f7125a5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/6aef71a9fbbd1ee7be0e313cd627f8e6f7125a5b", + "reference": "6aef71a9fbbd1ee7be0e313cd627f8e6f7125a5b", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -2390,7 +2389,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/0.17.1" }, "funding": [ { @@ -2398,7 +2397,7 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-04-19T20:55:20+00:00" }, { "name": "brick/schema", @@ -2513,16 +2512,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.11", + "version": "1.5.12", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6" + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/68ff39175e8e94a4bb1d259407ce51a6a60f09e6", - "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", "shasum": "" }, "require": { @@ -2569,7 +2568,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.11" + "source": "https://github.com/composer/ca-bundle/tree/1.5.12" }, "funding": [ { @@ -2581,7 +2580,7 @@ "type": "github" } ], - "time": "2026-03-30T09:16:10+00:00" + "time": "2026-05-19T11:26:22+00:00" }, { "name": "composer/package-versions-deprecated", @@ -3986,16 +3985,16 @@ }, { "name": "doctrine/orm", - "version": "3.6.3", + "version": "3.6.7", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "e88cd591f0786089dee22b972c28aa2076df51c0" + "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/e88cd591f0786089dee22b972c28aa2076df51c0", - "reference": "e88cd591f0786089dee22b972c28aa2076df51c0", + "url": "https://api.github.com/repos/doctrine/orm/zipball/bc217c0e19c3a9eadfa67697143b87c9ba01272c", + "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c", "shasum": "" }, "require": { @@ -4068,9 +4067,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.6.3" + "source": "https://github.com/doctrine/orm/tree/3.6.7" }, - "time": "2026-04-02T06:53:27+00:00" + "time": "2026-05-25T16:45:47+00:00" }, { "name": "doctrine/persistence", @@ -4637,16 +4636,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.10.4", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "aec528da477062d3af11f51e6b33402be233b21f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aec528da477062d3af11f51e6b33402be233b21f", + "reference": "aec528da477062d3af11f51e6b33402be233b21f", "shasum": "" }, "require": { @@ -4664,8 +4663,9 @@ "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "guzzle/client-integration-tests": "3.0.2", + "guzzlehttp/test-server": "^0.3.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -4743,7 +4743,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.10.4" }, "funding": [ { @@ -4759,20 +4759,20 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-05-22T19:00:53+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.3.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2", + "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2", "shasum": "" }, "require": { @@ -4780,7 +4780,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { @@ -4826,7 +4826,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "source": "https://github.com/guzzle/promises/tree/2.4.1" }, "funding": [ { @@ -4842,20 +4842,20 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-05-20T22:57:30+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.9.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + "reference": "73ab136360b5dfd858006eae9795e8fe43c80361" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/73ab136360b5dfd858006eae9795e8fe43c80361", + "reference": "73ab136360b5dfd858006eae9795e8fe43c80361", "shasum": "" }, "require": { @@ -4870,9 +4870,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", + "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -4943,7 +4943,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.9.0" + "source": "https://github.com/guzzle/psr7/tree/2.10.1" }, "funding": [ { @@ -4959,7 +4959,7 @@ "type": "tidelift" } ], - "time": "2026-03-10T16:41:02+00:00" + "time": "2026-05-20T09:27:36+00:00" }, { "name": "hshn/base64-encoded-file", @@ -7742,16 +7742,16 @@ }, { "name": "nette/utils", - "version": "v4.1.3", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", "shasum": "" }, "require": { @@ -7827,9 +7827,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.3" + "source": "https://github.com/nette/utils/tree/v4.1.4" }, - "time": "2026-02-13T03:05:33+00:00" + "time": "2026-05-11T20:49:54+00:00" }, { "name": "nikolaposa/version", @@ -8092,21 +8092,21 @@ }, { "name": "onelogin/php-saml", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "b009f160e4ac11f49366a45e0d45706b48429353" + "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/b009f160e4ac11f49366a45e0d45706b48429353", - "reference": "b009f160e4ac11f49366a45e0d45706b48429353", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/26b3a47349415e5b7aa300ba4ab7fc316c65f19e", + "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e", "shasum": "" }, "require": { "php": ">=7.3", - "robrichards/xmlseclibs": ">=3.1.4" + "robrichards/xmlseclibs": "^3.1.5" }, "require-dev": { "pdepend/pdepend": "^2.8.0", @@ -8152,7 +8152,7 @@ "type": "github" } ], - "time": "2025-12-09T10:50:49+00:00" + "time": "2026-05-07T22:38:04+00:00" }, { "name": "oskarstark/enum-helper", @@ -9671,16 +9671,16 @@ }, { "name": "revolt/event-loop", - "version": "v1.0.8", + "version": "v1.0.9", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" + "reference": "44061cf513e53c6200372fc935ac42271566295d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", - "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d", + "reference": "44061cf513e53c6200372fc935ac42271566295d", "shasum": "" }, "require": { @@ -9690,7 +9690,7 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^9", - "psalm/phar": "^5.15" + "psalm/phar": "6.16.*" }, "type": "library", "extra": { @@ -9737,9 +9737,9 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.9" }, - "time": "2025-08-27T21:33:23+00:00" + "time": "2026-05-16T17:55:38+00:00" }, { "name": "rhukster/dom-sanitizer", @@ -10729,21 +10729,21 @@ }, { "name": "symfony/ai-bundle", - "version": "v0.8.0", + "version": "v0.9.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-bundle.git", - "reference": "847365e0f885f8814421e9c94f03ce19e0b54bbc" + "reference": "77fd1b513174770acf49abd68effa995fa518f7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-bundle/zipball/847365e0f885f8814421e9c94f03ce19e0b54bbc", - "reference": "847365e0f885f8814421e9c94f03ce19e0b54bbc", + "url": "https://api.github.com/repos/symfony/ai-bundle/zipball/77fd1b513174770acf49abd68effa995fa518f7c", + "reference": "77fd1b513174770acf49abd68effa995fa518f7c", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-platform": "^0.8", + "symfony/ai-platform": "^0.9", "symfony/clock": "^7.3|^8.0", "symfony/config": "^7.3|^8.0", "symfony/console": "^7.3|^8.0", @@ -10758,72 +10758,74 @@ "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^11.5.53", - "symfony/ai-agent": "^0.8", - "symfony/ai-ai-ml-api-platform": "^0.8", - "symfony/ai-albert-platform": "^0.8", - "symfony/ai-amazee-ai-platform": "^0.8", - "symfony/ai-anthropic-platform": "^0.8", - "symfony/ai-azure-platform": "^0.8", - "symfony/ai-azure-search-store": "^0.8", - "symfony/ai-bedrock-platform": "^0.8", - "symfony/ai-cache-message-store": "^0.8", - "symfony/ai-cache-platform": "^0.8", - "symfony/ai-cache-store": "^0.8", - "symfony/ai-cartesia-platform": "^0.8", - "symfony/ai-cerebras-platform": "^0.8", - "symfony/ai-chat": "^0.8", - "symfony/ai-chroma-db-store": "^0.8", - "symfony/ai-click-house-store": "^0.8", - "symfony/ai-cloudflare-message-store": "^0.8", - "symfony/ai-cloudflare-store": "^0.8", - "symfony/ai-decart-platform": "^0.8", - "symfony/ai-deep-seek-platform": "^0.8", - "symfony/ai-docker-model-runner-platform": "^0.8", - "symfony/ai-doctrine-message-store": "^0.8", - "symfony/ai-elasticsearch-store": "^0.8", - "symfony/ai-eleven-labs-platform": "^0.8", - "symfony/ai-failover-platform": "^0.8", - "symfony/ai-gemini-platform": "^0.8", - "symfony/ai-generic-platform": "^0.8", - "symfony/ai-hugging-face-platform": "^0.8", - "symfony/ai-lm-studio-platform": "^0.8", - "symfony/ai-manticore-search-store": "^0.8", - "symfony/ai-maria-db-store": "^0.8", - "symfony/ai-meilisearch-message-store": "^0.8", - "symfony/ai-meilisearch-store": "^0.8", - "symfony/ai-meta-platform": "^0.8", - "symfony/ai-milvus-store": "^0.8", - "symfony/ai-mistral-platform": "^0.8", - "symfony/ai-mongo-db-message-store": "^0.8", - "symfony/ai-mongo-db-store": "^0.8", - "symfony/ai-neo4j-store": "^0.8", - "symfony/ai-ollama-platform": "^0.8", - "symfony/ai-open-ai-platform": "^0.8", - "symfony/ai-open-responses-platform": "^0.8", - "symfony/ai-open-router-platform": "^0.8", - "symfony/ai-open-search-store": "^0.8", - "symfony/ai-perplexity-platform": "^0.8", - "symfony/ai-pinecone-store": "^0.8", - "symfony/ai-pogocache-message-store": "^0.8", - "symfony/ai-postgres-store": "^0.8", - "symfony/ai-qdrant-store": "^0.8", - "symfony/ai-redis-message-store": "^0.8", - "symfony/ai-redis-store": "^0.8", - "symfony/ai-replicate-platform": "^0.8", - "symfony/ai-s3vectors-store": "^0.8", - "symfony/ai-scaleway-platform": "^0.8", - "symfony/ai-session-message-store": "^0.8", - "symfony/ai-sqlite-store": "^0.8", - "symfony/ai-store": "^0.8", - "symfony/ai-supabase-store": "^0.8", - "symfony/ai-surreal-db-message-store": "^0.8", - "symfony/ai-surreal-db-store": "^0.8", - "symfony/ai-transformers-php-platform": "^0.8", - "symfony/ai-typesense-store": "^0.8", - "symfony/ai-vektor-store": "^0.8", - "symfony/ai-vertex-ai-platform": "^0.8", - "symfony/ai-voyage-platform": "^0.8", - "symfony/ai-weaviate-store": "^0.8", + "symfony/ai-agent": "^0.9", + "symfony/ai-ai-ml-api-platform": "^0.9", + "symfony/ai-albert-platform": "^0.9", + "symfony/ai-amazee-ai-platform": "^0.9", + "symfony/ai-anthropic-platform": "^0.9", + "symfony/ai-azure-platform": "^0.9", + "symfony/ai-azure-search-store": "^0.9", + "symfony/ai-bedrock-platform": "^0.9", + "symfony/ai-cache-message-store": "^0.9", + "symfony/ai-cache-platform": "^0.9", + "symfony/ai-cache-store": "^0.9", + "symfony/ai-cartesia-platform": "^0.9", + "symfony/ai-cerebras-platform": "^0.9", + "symfony/ai-chat": "^0.9", + "symfony/ai-chroma-db-store": "^0.9", + "symfony/ai-click-house-store": "^0.9", + "symfony/ai-cloudflare-message-store": "^0.9", + "symfony/ai-cloudflare-store": "^0.9", + "symfony/ai-cohere-platform": "^0.9", + "symfony/ai-decart-platform": "^0.9", + "symfony/ai-deep-seek-platform": "^0.9", + "symfony/ai-docker-model-runner-platform": "^0.9", + "symfony/ai-doctrine-message-store": "^0.9", + "symfony/ai-elasticsearch-store": "^0.9", + "symfony/ai-eleven-labs-platform": "^0.9", + "symfony/ai-failover-platform": "^0.9", + "symfony/ai-gemini-platform": "^0.9", + "symfony/ai-generic-platform": "^0.9", + "symfony/ai-hugging-face-platform": "^0.9", + "symfony/ai-lm-studio-platform": "^0.9", + "symfony/ai-manticore-search-store": "^0.9", + "symfony/ai-maria-db-store": "^0.9", + "symfony/ai-meilisearch-message-store": "^0.9", + "symfony/ai-meilisearch-store": "^0.9", + "symfony/ai-meta-platform": "^0.9", + "symfony/ai-milvus-store": "^0.9", + "symfony/ai-mistral-platform": "^0.9", + "symfony/ai-mongo-db-message-store": "^0.9", + "symfony/ai-mongo-db-store": "^0.9", + "symfony/ai-neo4j-store": "^0.9", + "symfony/ai-ollama-platform": "^0.9", + "symfony/ai-open-ai-platform": "^0.9", + "symfony/ai-open-responses-platform": "^0.9", + "symfony/ai-open-router-platform": "^0.9", + "symfony/ai-open-search-store": "^0.9", + "symfony/ai-ovh-platform": "^0.9", + "symfony/ai-perplexity-platform": "^0.9", + "symfony/ai-pinecone-store": "^0.9", + "symfony/ai-pogocache-message-store": "^0.9", + "symfony/ai-postgres-store": "^0.9", + "symfony/ai-qdrant-store": "^0.9", + "symfony/ai-redis-message-store": "^0.9", + "symfony/ai-redis-store": "^0.9", + "symfony/ai-replicate-platform": "^0.9", + "symfony/ai-s3vectors-store": "^0.9", + "symfony/ai-scaleway-platform": "^0.9", + "symfony/ai-session-message-store": "^0.9", + "symfony/ai-sqlite-store": "^0.9", + "symfony/ai-store": "^0.9", + "symfony/ai-supabase-store": "^0.9", + "symfony/ai-surreal-db-message-store": "^0.9", + "symfony/ai-surreal-db-store": "^0.9", + "symfony/ai-transformers-php-platform": "^0.9", + "symfony/ai-typesense-store": "^0.9", + "symfony/ai-vektor-store": "^0.9", + "symfony/ai-vertex-ai-platform": "^0.9", + "symfony/ai-voyage-platform": "^0.9", + "symfony/ai-weaviate-store": "^0.9", "symfony/expression-language": "^7.3|^8.0", "symfony/security-core": "^7.3|^8.0", "symfony/translation": "^7.3|^8.0", @@ -10861,7 +10863,7 @@ ], "description": "Integration bundle for Symfony AI components", "support": { - "source": "https://github.com/symfony/ai-bundle/tree/v0.8.0" + "source": "https://github.com/symfony/ai-bundle/tree/v0.9.0" }, "funding": [ { @@ -10881,25 +10883,25 @@ "type": "tidelift" } ], - "time": "2026-04-20T21:23:24+00:00" + "time": "2026-05-16T08:40:45+00:00" }, { "name": "symfony/ai-generic-platform", - "version": "v0.8.0", + "version": "v0.9.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-generic-platform.git", - "reference": "2e358c0e88c676fad0b61b3df715f9822d29a7e3" + "reference": "8887d12b8ea97d079c5c97de4aebb19f42c58dc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-generic-platform/zipball/2e358c0e88c676fad0b61b3df715f9822d29a7e3", - "reference": "2e358c0e88c676fad0b61b3df715f9822d29a7e3", + "url": "https://api.github.com/repos/symfony/ai-generic-platform/zipball/8887d12b8ea97d079c5c97de4aebb19f42c58dc5", + "reference": "8887d12b8ea97d079c5c97de4aebb19f42c58dc5", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-platform": "^0.8", + "symfony/ai-platform": "^0.9", "symfony/http-client": "^7.3|^8.0" }, "require-dev": { @@ -10946,7 +10948,7 @@ "platform" ], "support": { - "source": "https://github.com/symfony/ai-generic-platform/tree/v0.8.0" + "source": "https://github.com/symfony/ai-generic-platform/tree/v0.9.0" }, "funding": [ { @@ -10966,26 +10968,26 @@ "type": "tidelift" } ], - "time": "2026-04-20T21:23:24+00:00" + "time": "2026-05-16T01:01:33+00:00" }, { "name": "symfony/ai-lm-studio-platform", - "version": "v0.8.0", + "version": "v0.9.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-lm-studio-platform.git", - "reference": "ad1c046dd9e7d6e474bc86554443e2d9400a7826" + "reference": "9e53e56c8c3a04dddb955088b40904e747ec3981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-lm-studio-platform/zipball/ad1c046dd9e7d6e474bc86554443e2d9400a7826", - "reference": "ad1c046dd9e7d6e474bc86554443e2d9400a7826", + "url": "https://api.github.com/repos/symfony/ai-lm-studio-platform/zipball/9e53e56c8c3a04dddb955088b40904e747ec3981", + "reference": "9e53e56c8c3a04dddb955088b40904e747ec3981", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-generic-platform": "^0.8", - "symfony/ai-platform": "^0.8", + "symfony/ai-generic-platform": "^0.9", + "symfony/ai-platform": "^0.9", "symfony/http-client": "^7.3|^8.0" }, "require-dev": { @@ -11033,7 +11035,7 @@ "platform" ], "support": { - "source": "https://github.com/symfony/ai-lm-studio-platform/tree/v0.8.0" + "source": "https://github.com/symfony/ai-lm-studio-platform/tree/v0.9.0" }, "funding": [ { @@ -11053,33 +11055,34 @@ "type": "tidelift" } ], - "time": "2026-04-20T21:23:24+00:00" + "time": "2026-05-16T01:01:33+00:00" }, { "name": "symfony/ai-open-router-platform", - "version": "v0.8.0", + "version": "v0.9.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-open-router-platform.git", - "reference": "eb5ed3176b78bc489bf325c5d6bc4efc255804be" + "reference": "7e2b560c86f618cd5d33f9f0c581d83bebc9802f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-open-router-platform/zipball/eb5ed3176b78bc489bf325c5d6bc4efc255804be", - "reference": "eb5ed3176b78bc489bf325c5d6bc4efc255804be", + "url": "https://api.github.com/repos/symfony/ai-open-router-platform/zipball/7e2b560c86f618cd5d33f9f0c581d83bebc9802f", + "reference": "7e2b560c86f618cd5d33f9f0c581d83bebc9802f", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-generic-platform": "^0.8", - "symfony/ai-platform": "^0.8", + "symfony/ai-generic-platform": "^0.9", + "symfony/ai-platform": "^0.9", "symfony/http-client": "^7.3|^8.0" }, "require-dev": { "phpstan/phpstan": "^2.1", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^11.5.53" + "phpunit/phpunit": "^11.5.53", + "symfony/console": "^7.4|^8.0" }, "type": "symfony-ai-platform", "extra": { @@ -11119,7 +11122,7 @@ "platform" ], "support": { - "source": "https://github.com/symfony/ai-open-router-platform/tree/v0.8.0" + "source": "https://github.com/symfony/ai-open-router-platform/tree/v0.9.0" }, "funding": [ { @@ -11139,20 +11142,20 @@ "type": "tidelift" } ], - "time": "2026-04-20T21:23:24+00:00" + "time": "2026-05-16T01:01:33+00:00" }, { "name": "symfony/ai-platform", - "version": "v0.8.1", + "version": "v0.9.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-platform.git", - "reference": "86ed9396f53cad02b5d1ca8092956ea74f65823f" + "reference": "fb55ebdf20bbe30af6752a0ce6a25abc56b2b625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-platform/zipball/86ed9396f53cad02b5d1ca8092956ea74f65823f", - "reference": "86ed9396f53cad02b5d1ca8092956ea74f65823f", + "url": "https://api.github.com/repos/symfony/ai-platform/zipball/fb55ebdf20bbe30af6752a0ce6a25abc56b2b625", + "reference": "fb55ebdf20bbe30af6752a0ce6a25abc56b2b625", "shasum": "" }, "require": { @@ -11251,7 +11254,7 @@ "voyage" ], "support": { - "source": "https://github.com/symfony/ai-platform/tree/v0.8.1" + "source": "https://github.com/symfony/ai-platform/tree/v0.9.0" }, "funding": [ { @@ -11271,7 +11274,7 @@ "type": "tidelift" } ], - "time": "2026-04-20T21:28:38+00:00" + "time": "2026-05-15T19:15:50+00:00" }, { "name": "symfony/apache-pack", @@ -11374,16 +11377,16 @@ }, { "name": "symfony/cache", - "version": "v7.4.9", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "3860fa12a5013b48d445909c6ea07f870e10ba7c" + "reference": "902d621e0b6ef0ebeaa133770b5c339a19328589" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/3860fa12a5013b48d445909c6ea07f870e10ba7c", - "reference": "3860fa12a5013b48d445909c6ea07f870e10ba7c", + "url": "https://api.github.com/repos/symfony/cache/zipball/902d621e0b6ef0ebeaa133770b5c339a19328589", + "reference": "902d621e0b6ef0ebeaa133770b5c339a19328589", "shasum": "" }, "require": { @@ -11454,7 +11457,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.9" + "source": "https://github.com/symfony/cache/tree/v7.4.12" }, "funding": [ { @@ -11474,20 +11477,20 @@ "type": "tidelift" } ], - "time": "2026-04-29T13:21:53+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + "reference": "225e8a254166bd3442e370c6f50145465db63831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", - "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831", + "reference": "225e8a254166bd3442e370c6f50145465db63831", "shasum": "" }, "require": { @@ -11501,7 +11504,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -11534,7 +11537,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.0" }, "funding": [ { @@ -11545,12 +11548,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-13T15:25:07+00:00" + "time": "2026-05-05T15:33:14+00:00" }, { "name": "symfony/clock", @@ -11632,16 +11639,16 @@ }, { "name": "symfony/config", - "version": "v7.4.9", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "d4a277b7a0f26487db16b264d935c617b7d994ea" + "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/d4a277b7a0f26487db16b264d935c617b7d994ea", - "reference": "d4a277b7a0f26487db16b264d935c617b7d994ea", + "url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", + "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", "shasum": "" }, "require": { @@ -11687,7 +11694,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.9" + "source": "https://github.com/symfony/config/tree/v7.4.10" }, "funding": [ { @@ -11707,20 +11714,20 @@ "type": "tidelift" } ], - "time": "2026-04-29T14:25:20+00:00" + "time": "2026-05-03T14:20:49+00:00" }, { "name": "symfony/console", - "version": "v7.4.9", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" + "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", - "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", + "url": "https://api.github.com/repos/symfony/console/zipball/ed0107e43ab452aa77ae99e005b95e56b556e075", + "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075", "shasum": "" }, "require": { @@ -11785,7 +11792,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.9" + "source": "https://github.com/symfony/console/tree/v7.4.11" }, "funding": [ { @@ -11805,7 +11812,7 @@ "type": "tidelift" } ], - "time": "2026-04-22T15:21:55+00:00" + "time": "2026-05-13T12:04:42+00:00" }, { "name": "symfony/css-selector", @@ -11878,16 +11885,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v7.4.9", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "27cd9f912438d07ced76008bc66cf8b0cf4de622" + "reference": "4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/27cd9f912438d07ced76008bc66cf8b0cf4de622", - "reference": "27cd9f912438d07ced76008bc66cf8b0cf4de622", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d", + "reference": "4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d", "shasum": "" }, "require": { @@ -11938,7 +11945,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.9" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.10" }, "funding": [ { @@ -11958,20 +11965,20 @@ "type": "tidelift" } ], - "time": "2026-04-30T18:38:49+00:00" + "time": "2026-05-06T11:55:30+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -11984,7 +11991,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -12009,7 +12016,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -12020,12 +12027,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/doctrine-bridge", @@ -12142,16 +12153,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8" + "reference": "b59b59122690976550fd142c23fab62c84738db6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2918e7c2ba964defca1f5b69c6f74886529e2dc8", - "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b59b59122690976550fd142c23fab62c84738db6", + "reference": "b59b59122690976550fd142c23fab62c84738db6", "shasum": "" }, "require": { @@ -12190,7 +12201,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.8" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.12" }, "funding": [ { @@ -12210,20 +12221,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/dotenv", - "version": "v7.4.9", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "ba757a8564a0ccac1a26a859b83295645020ea68" + "reference": "82e9b1355c68ef7b96397dbd34cc75a92eebae7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/ba757a8564a0ccac1a26a859b83295645020ea68", - "reference": "ba757a8564a0ccac1a26a859b83295645020ea68", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/82e9b1355c68ef7b96397dbd34cc75a92eebae7c", + "reference": "82e9b1355c68ef7b96397dbd34cc75a92eebae7c", "shasum": "" }, "require": { @@ -12268,7 +12279,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v7.4.9" + "source": "https://github.com/symfony/dotenv/tree/v7.4.11" }, "funding": [ { @@ -12288,7 +12299,7 @@ "type": "tidelift" } ], - "time": "2026-04-29T13:21:53+00:00" + "time": "2026-05-11T13:02:51+00:00" }, { "name": "symfony/error-handler", @@ -12459,16 +12470,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { @@ -12482,7 +12493,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -12515,7 +12526,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" }, "funding": [ { @@ -12526,12 +12537,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/expression-language", @@ -12603,16 +12618,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.9", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3" + "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/dcd8f96bcdc0f128ec406c765cc066c6035d1be3", - "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50", + "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50", "shasum": "" }, "require": { @@ -12649,7 +12664,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.9" + "source": "https://github.com/symfony/filesystem/tree/v7.4.11" }, "funding": [ { @@ -12669,7 +12684,7 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-05-11T16:38:44+00:00" }, { "name": "symfony/finder", @@ -12917,16 +12932,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.4.9", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "601423cc0af2eb5e8c4acdf21fed553d456ff802" + "reference": "637f5cac1ac2698a012b41610215bf366004295f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/601423cc0af2eb5e8c4acdf21fed553d456ff802", - "reference": "601423cc0af2eb5e8c4acdf21fed553d456ff802", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/637f5cac1ac2698a012b41610215bf366004295f", + "reference": "637f5cac1ac2698a012b41610215bf366004295f", "shasum": "" }, "require": { @@ -13051,7 +13066,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.4.9" + "source": "https://github.com/symfony/framework-bundle/tree/v7.4.11" }, "funding": [ { @@ -13071,7 +13086,7 @@ "type": "tidelift" } ], - "time": "2026-04-30T08:57:13+00:00" + "time": "2026-05-13T12:04:42+00:00" }, { "name": "symfony/http-client", @@ -13176,16 +13191,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", "shasum": "" }, "require": { @@ -13198,7 +13213,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -13234,7 +13249,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" }, "funding": [ { @@ -13245,12 +13260,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-29T11:18:49+00:00" + "time": "2026-03-06T13:17:50+00:00" }, { "name": "symfony/http-foundation", @@ -13336,16 +13355,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "017e76ad089bac281553389269e259e155935e1a" + "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/017e76ad089bac281553389269e259e155935e1a", - "reference": "017e76ad089bac281553389269e259e155935e1a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", + "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", "shasum": "" }, "require": { @@ -13431,7 +13450,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.12" }, "funding": [ { @@ -13451,7 +13470,7 @@ "type": "tidelift" } ], - "time": "2026-03-31T20:57:01+00:00" + "time": "2026-05-20T09:27:11+00:00" }, { "name": "symfony/intl", @@ -13545,16 +13564,16 @@ }, { "name": "symfony/mailer", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62" + "reference": "5cefb712a25f320579615ba9e1942abaeade7dff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f6ea532250b476bfc1b56699b388a1bdbf168f62", - "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62", + "url": "https://api.github.com/repos/symfony/mailer/zipball/5cefb712a25f320579615ba9e1942abaeade7dff", + "reference": "5cefb712a25f320579615ba9e1942abaeade7dff", "shasum": "" }, "require": { @@ -13605,7 +13624,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.8" + "source": "https://github.com/symfony/mailer/tree/v7.4.12" }, "funding": [ { @@ -13625,20 +13644,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/mime", - "version": "v7.4.9", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2d550c4758ba4c47519a6667c36553d535705b0c" + "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2d550c4758ba4c47519a6667c36553d535705b0c", - "reference": "2d550c4758ba4c47519a6667c36553d535705b0c", + "url": "https://api.github.com/repos/symfony/mime/zipball/b198dd66c211c97119bcaaff7c13431dbbb5e470", + "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470", "shasum": "" }, "require": { @@ -13694,7 +13713,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.9" + "source": "https://github.com/symfony/mime/tree/v7.4.12" }, "funding": [ { @@ -13714,20 +13733,20 @@ "type": "tidelift" } ], - "time": "2026-04-29T13:21:53+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v7.4.9", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "20366bceee51838144a14805204bb792cb3d09f2" + "reference": "20bb2345ac7a9dd57724b6b7ada92c6d7d67b4b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/20366bceee51838144a14805204bb792cb3d09f2", - "reference": "20366bceee51838144a14805204bb792cb3d09f2", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/20bb2345ac7a9dd57724b6b7ada92c6d7d67b4b8", + "reference": "20bb2345ac7a9dd57724b6b7ada92c6d7d67b4b8", "shasum": "" }, "require": { @@ -13777,7 +13796,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v7.4.9" + "source": "https://github.com/symfony/monolog-bridge/tree/v7.4.12" }, "funding": [ { @@ -13797,7 +13816,7 @@ "type": "tidelift" } ], - "time": "2026-04-29T13:21:53+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/monolog-bundle", @@ -14110,16 +14129,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + "reference": "9c862df890f7c833b1101ac5578ec4dcf199efb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/9c862df890f7c833b1101ac5578ec4dcf199efb5", + "reference": "9c862df890f7c833b1101ac5578ec4dcf199efb5", "shasum": "" }, "require": { @@ -14168,7 +14187,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.0" }, "funding": [ { @@ -14188,20 +14207,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:13:48+00:00" + "time": "2026-05-25T12:39:52+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "3510b63d07376b04e57e27e82607d468bb134f78" + "reference": "445c90e341fccda10311019cf82ff73bb7343945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/3510b63d07376b04e57e27e82607d468bb134f78", - "reference": "3510b63d07376b04e57e27e82607d468bb134f78", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/445c90e341fccda10311019cf82ff73bb7343945", + "reference": "445c90e341fccda10311019cf82ff73bb7343945", "shasum": "" }, "require": { @@ -14256,7 +14275,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.38.0" }, "funding": [ { @@ -14276,7 +14295,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:50:15+00:00" + "time": "2026-05-25T11:52:53+00:00" }, { "name": "symfony/polyfill-intl-idn", @@ -14367,16 +14386,16 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -14428,7 +14447,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -14448,20 +14467,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "c4406e07046227db8844a25f89d111da078aaa9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/c4406e07046227db8844a25f89d111da078aaa9c", + "reference": "c4406e07046227db8844a25f89d111da078aaa9c", "shasum": "" }, "require": { @@ -14508,7 +14527,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.0" }, "funding": [ { @@ -14528,20 +14547,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-25T12:39:52+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + "reference": "a0e0aca0368801ec79f8791dea9a7c12af527c93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/a0e0aca0368801ec79f8791dea9a7c12af527c93", + "reference": "a0e0aca0368801ec79f8791dea9a7c12af527c93", "shasum": "" }, "require": { @@ -14588,7 +14607,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.0" }, "funding": [ { @@ -14608,20 +14627,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T18:47:49+00:00" + "time": "2026-05-25T12:12:52+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" + "reference": "3e851ffb42624b64b3b9ec234a0e2074b0f880e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/3e851ffb42624b64b3b9ec234a0e2074b0f880e3", + "reference": "3e851ffb42624b64b3b9ec234a0e2074b0f880e3", "shasum": "" }, "require": { @@ -14668,7 +14687,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.0" }, "funding": [ { @@ -14688,7 +14707,7 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:10:57+00:00" + "time": "2026-05-25T11:50:50+00:00" }, { "name": "symfony/polyfill-uuid", @@ -14775,16 +14794,16 @@ }, { "name": "symfony/process", - "version": "v7.4.8", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "60f19cd3badc8de688421e21e4305eba50f8089a" + "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/60f19cd3badc8de688421e21e4305eba50f8089a", - "reference": "60f19cd3badc8de688421e21e4305eba50f8089a", + "url": "https://api.github.com/repos/symfony/process/zipball/d9593c9efa40499eb078b81144de42cbc28a31f0", + "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0", "shasum": "" }, "require": { @@ -14816,7 +14835,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.8" + "source": "https://github.com/symfony/process/tree/v7.4.11" }, "funding": [ { @@ -14836,7 +14855,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-11T16:55:21+00:00" }, { "name": "symfony/property-access", @@ -15099,16 +15118,16 @@ }, { "name": "symfony/rate-limiter", - "version": "v7.4.9", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "0b0078d29f6e64b8833492e9f76a853f23fb1a81" + "reference": "778c5239c7fd6bf9b886dedf3d84ddb156ddb888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/0b0078d29f6e64b8833492e9f76a853f23fb1a81", - "reference": "0b0078d29f6e64b8833492e9f76a853f23fb1a81", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/778c5239c7fd6bf9b886dedf3d84ddb156ddb888", + "reference": "778c5239c7fd6bf9b886dedf3d84ddb156ddb888", "shasum": "" }, "require": { @@ -15149,7 +15168,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v7.4.9" + "source": "https://github.com/symfony/rate-limiter/tree/v7.4.10" }, "funding": [ { @@ -15169,20 +15188,20 @@ "type": "tidelift" } ], - "time": "2026-04-29T13:21:53+00:00" + "time": "2026-05-04T13:25:50+00:00" }, { "name": "symfony/routing", - "version": "v7.4.9", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "287771d8bc86eacb30678dd10eda6c64a859951f" + "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/287771d8bc86eacb30678dd10eda6c64a859951f", - "reference": "287771d8bc86eacb30678dd10eda6c64a859951f", + "url": "https://api.github.com/repos/symfony/routing/zipball/3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", + "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", "shasum": "" }, "require": { @@ -15234,7 +15253,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.9" + "source": "https://github.com/symfony/routing/tree/v7.4.12" }, "funding": [ { @@ -15254,20 +15273,20 @@ "type": "tidelift" } ], - "time": "2026-04-22T15:21:55+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/runtime", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "6d792a64fec1eae2f011cfe9ab5978a9eab3071e" + "reference": "0b032fa77359745db793df5aff626779180c5f3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/6d792a64fec1eae2f011cfe9ab5978a9eab3071e", - "reference": "6d792a64fec1eae2f011cfe9ab5978a9eab3071e", + "url": "https://api.github.com/repos/symfony/runtime/zipball/0b032fa77359745db793df5aff626779180c5f3b", + "reference": "0b032fa77359745db793df5aff626779180c5f3b", "shasum": "" }, "require": { @@ -15280,6 +15299,7 @@ "require-dev": { "composer/composer": "^2.6", "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/dotenv": "^6.4|^7.0|^8.0", "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/http-kernel": "^6.4|^7.0|^8.0" @@ -15317,7 +15337,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.4.8" + "source": "https://github.com/symfony/runtime/tree/v7.4.12" }, "funding": [ { @@ -15337,20 +15357,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/security-bundle", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "6f73fdfd9ad23bf24b6f6c8d35be3ea6853d91af" + "reference": "6f6f859b437fb95028addfa21b417d25daca86d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/6f73fdfd9ad23bf24b6f6c8d35be3ea6853d91af", - "reference": "6f73fdfd9ad23bf24b6f6c8d35be3ea6853d91af", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/6f6f859b437fb95028addfa21b417d25daca86d5", + "reference": "6f6f859b437fb95028addfa21b417d25daca86d5", "shasum": "" }, "require": { @@ -15429,7 +15449,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v7.4.8" + "source": "https://github.com/symfony/security-bundle/tree/v7.4.12" }, "funding": [ { @@ -15449,20 +15469,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:54:39+00:00" + "time": "2026-05-15T07:14:02+00:00" }, { "name": "symfony/security-core", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "23e0cd6615661e33e53faf714bf6a130c2f75c25" + "reference": "efff84605474ec682c7d9c6278088811e6f3caaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/23e0cd6615661e33e53faf714bf6a130c2f75c25", - "reference": "23e0cd6615661e33e53faf714bf6a130c2f75c25", + "url": "https://api.github.com/repos/symfony/security-core/zipball/efff84605474ec682c7d9c6278088811e6f3caaa", + "reference": "efff84605474ec682c7d9c6278088811e6f3caaa", "shasum": "" }, "require": { @@ -15520,7 +15540,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.4.8" + "source": "https://github.com/symfony/security-core/tree/v7.4.12" }, "funding": [ { @@ -15540,7 +15560,7 @@ "type": "tidelift" } ], - "time": "2026-03-31T07:00:19+00:00" + "time": "2026-05-15T06:48:59+00:00" }, { "name": "symfony/security-csrf", @@ -15618,16 +15638,16 @@ }, { "name": "symfony/security-http", - "version": "v7.4.9", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "a34991b13899de1f953df245395aa2196f9bc113" + "reference": "1fc7ca636cbd2cad29b42cc13c9fd0c681c6efee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/a34991b13899de1f953df245395aa2196f9bc113", - "reference": "a34991b13899de1f953df245395aa2196f9bc113", + "url": "https://api.github.com/repos/symfony/security-http/zipball/1fc7ca636cbd2cad29b42cc13c9fd0c681c6efee", + "reference": "1fc7ca636cbd2cad29b42cc13c9fd0c681c6efee", "shasum": "" }, "require": { @@ -15686,7 +15706,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.4.9" + "source": "https://github.com/symfony/security-http/tree/v7.4.12" }, "funding": [ { @@ -15706,20 +15726,20 @@ "type": "tidelift" } ], - "time": "2026-04-22T15:21:55+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/serializer", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "006fd51717addf2df2bd1a64dafef6b7fab6b455" + "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/006fd51717addf2df2bd1a64dafef6b7fab6b455", - "reference": "006fd51717addf2df2bd1a64dafef6b7fab6b455", + "url": "https://api.github.com/repos/symfony/serializer/zipball/268c5aa6c4bd675eddd89348e7ecac292a843ddd", + "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd", "shasum": "" }, "require": { @@ -15732,7 +15752,7 @@ "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/dependency-injection": "<6.4", - "symfony/property-access": "<6.4", + "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", "symfony/property-info": "<6.4", "symfony/type-info": "<7.2.5", "symfony/uid": "<6.4", @@ -15754,7 +15774,7 @@ "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/messenger": "^6.4|^7.0|^8.0", "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", "symfony/type-info": "^7.2.5|^8.0", @@ -15790,7 +15810,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.4.8" + "source": "https://github.com/symfony/serializer/tree/v7.4.10" }, "funding": [ { @@ -15810,20 +15830,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T21:34:42+00:00" + "time": "2026-05-03T13:03:28+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -15841,7 +15861,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -15877,7 +15897,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -15897,7 +15917,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/stimulus-bundle", @@ -16040,16 +16060,16 @@ }, { "name": "symfony/string", - "version": "v7.4.8", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "114ac57257d75df748eda23dd003878080b8e688" + "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", - "reference": "114ac57257d75df748eda23dd003878080b8e688", + "url": "https://api.github.com/repos/symfony/string/zipball/965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", + "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", "shasum": "" }, "require": { @@ -16107,7 +16127,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.8" + "source": "https://github.com/symfony/string/tree/v7.4.11" }, "funding": [ { @@ -16127,20 +16147,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-13T12:04:42+00:00" }, { "name": "symfony/translation", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "33600f8489485425bfcddd0d983391038d3422e7" + "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/33600f8489485425bfcddd0d983391038d3422e7", - "reference": "33600f8489485425bfcddd0d983391038d3422e7", + "url": "https://api.github.com/repos/symfony/translation/zipball/ada7578c30dd5feaa8259cff3e885069ea81ddde", + "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde", "shasum": "" }, "require": { @@ -16207,7 +16227,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.8" + "source": "https://github.com/symfony/translation/tree/v7.4.10" }, "funding": [ { @@ -16227,20 +16247,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-06T11:19:24+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", "shasum": "" }, "require": { @@ -16253,7 +16273,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -16289,7 +16309,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" }, "funding": [ { @@ -16309,20 +16329,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/twig-bridge", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "ac43e7e59298ed1ce98c8d228b651d46e907d02c" + "reference": "81663873d946531129c76c65e80b681ce99c0e89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ac43e7e59298ed1ce98c8d228b651d46e907d02c", - "reference": "ac43e7e59298ed1ce98c8d228b651d46e907d02c", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/81663873d946531129c76c65e80b681ce99c0e89", + "reference": "81663873d946531129c76c65e80b681ce99c0e89", "shasum": "" }, "require": { @@ -16338,7 +16358,7 @@ "symfony/form": "<6.4.32|>7,<7.3.10|>7.4,<7.4.4|>8.0,<8.0.4", "symfony/http-foundation": "<6.4", "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.4.36|>7,<7.4.8|>8.0,<8.0.8", + "symfony/mime": "<6.4.37|>7,<7.4.9|>8.0,<8.0.9", "symfony/serializer": "<6.4", "symfony/translation": "<6.4", "symfony/workflow": "<6.4" @@ -16359,7 +16379,7 @@ "symfony/http-foundation": "^7.3|^8.0", "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4.36|^7.4.8|^8.0.8", + "symfony/mime": "^6.4.37|^7.4.9|^8.0.9", "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/routing": "^6.4|^7.0|^8.0", @@ -16404,7 +16424,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.4.8" + "source": "https://github.com/symfony/twig-bridge/tree/v7.4.12" }, "funding": [ { @@ -16424,7 +16444,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:17:09+00:00" + "time": "2026-04-29T17:13:54+00:00" }, { "name": "symfony/twig-bundle", @@ -16863,16 +16883,16 @@ }, { "name": "symfony/validator", - "version": "v7.4.9", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "d3bb3dcfbaa26b5782196819dac2e1097d5fae2c" + "reference": "c76458623af9a3fe3b2e5b09b36453f334c2a361" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/d3bb3dcfbaa26b5782196819dac2e1097d5fae2c", - "reference": "d3bb3dcfbaa26b5782196819dac2e1097d5fae2c", + "url": "https://api.github.com/repos/symfony/validator/zipball/c76458623af9a3fe3b2e5b09b36453f334c2a361", + "reference": "c76458623af9a3fe3b2e5b09b36453f334c2a361", "shasum": "" }, "require": { @@ -16943,7 +16963,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.4.9" + "source": "https://github.com/symfony/validator/tree/v7.4.10" }, "funding": [ { @@ -16963,7 +16983,7 @@ "type": "tidelift" } ], - "time": "2026-04-30T15:35:16+00:00" + "time": "2026-05-05T15:30:56+00:00" }, { "name": "symfony/var-dumper", @@ -17298,16 +17318,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c58fdf7b3d6c2995368264c49e4e8b05bcff2883" + "reference": "8b6952b56ca6417f25f7a65758cadd0ce02edc51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c58fdf7b3d6c2995368264c49e4e8b05bcff2883", - "reference": "c58fdf7b3d6c2995368264c49e4e8b05bcff2883", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8b6952b56ca6417f25f7a65758cadd0ce02edc51", + "reference": "8b6952b56ca6417f25f7a65758cadd0ce02edc51", "shasum": "" }, "require": { @@ -17350,7 +17370,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.8" + "source": "https://github.com/symfony/yaml/tree/v7.4.12" }, "funding": [ { @@ -17370,7 +17390,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symplify/easy-coding-standard", @@ -17424,16 +17444,16 @@ }, { "name": "tecnickcom/tc-lib-barcode", - "version": "2.4.39", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-barcode.git", - "reference": "11886fb5a44ec0f6e77302439e9ebf55034383fa" + "reference": "4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/11886fb5a44ec0f6e77302439e9ebf55034383fa", - "reference": "11886fb5a44ec0f6e77302439e9ebf55034383fa", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb", + "reference": "4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb", "shasum": "" }, "require": { @@ -17441,15 +17461,13 @@ "ext-date": "*", "ext-gd": "*", "ext-pcre": "*", - "php": ">=8.1", - "tecnickcom/tc-lib-color": "^2.5" + "php": ">=8.2", + "tecnickcom/tc-lib-color": "^2.7" }, "require-dev": { "pdepend/pdepend": "^2.16", "phpcompatibility/php-compatibility": "^10.0.0@dev", - "phpmd/phpmd": "^2.15", - "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5 || ^10.5", - "squizlabs/php_codesniffer": "^4.0" + "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5" }, "type": "library", "autoload": { @@ -17521,32 +17539,30 @@ "type": "github" } ], - "time": "2026-05-01T19:04:12+00:00" + "time": "2026-05-22T07:09:18+00:00" }, { "name": "tecnickcom/tc-lib-color", - "version": "2.5.3", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-color.git", - "reference": "136d522f1640723e490b79171e910e647403d971" + "reference": "6947cc9fffe23a21642279b8ab73a43f3311c5f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/136d522f1640723e490b79171e910e647403d971", - "reference": "136d522f1640723e490b79171e910e647403d971", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/6947cc9fffe23a21642279b8ab73a43f3311c5f9", + "reference": "6947cc9fffe23a21642279b8ab73a43f3311c5f9", "shasum": "" }, "require": { "ext-pcre": "*", - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "pdepend/pdepend": "^2.16", "phpcompatibility/php-compatibility": "^10.0.0@dev", - "phpmd/phpmd": "^2.15", - "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5 || ^10.5", - "squizlabs/php_codesniffer": "^4.0" + "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5" }, "type": "library", "autoload": { @@ -17591,7 +17607,7 @@ "type": "github" } ], - "time": "2026-05-01T19:02:25+00:00" + "time": "2026-05-22T06:55:57+00:00" }, { "name": "thecodingmachine/safe", @@ -17841,16 +17857,16 @@ }, { "name": "twig/cssinliner-extra", - "version": "v3.24.0", + "version": "v3.26.0", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "c25fa18b09a418e4d1454ec291f9406f630675ba" + "reference": "1b0dc906bbad7226c967bd325e99cccb1a850c4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/c25fa18b09a418e4d1454ec291f9406f630675ba", - "reference": "c25fa18b09a418e4d1454ec291f9406f630675ba", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/1b0dc906bbad7226c967bd325e99cccb1a850c4b", + "reference": "1b0dc906bbad7226c967bd325e99cccb1a850c4b", "shasum": "" }, "require": { @@ -17894,7 +17910,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.24.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.26.0" }, "funding": [ { @@ -17906,7 +17922,7 @@ "type": "tidelift" } ], - "time": "2025-12-02T14:45:16+00:00" + "time": "2026-05-15T13:14:14+00:00" }, { "name": "twig/extra-bundle", @@ -18052,16 +18068,16 @@ }, { "name": "twig/inky-extra", - "version": "v3.24.0", + "version": "v3.26.0", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "6bdca65a38167f7bd0ad7ea04819098d465a5cc4" + "reference": "0a8b24f0d0247bf6dc5e2af0c0ab09c0c4e5343e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/6bdca65a38167f7bd0ad7ea04819098d465a5cc4", - "reference": "6bdca65a38167f7bd0ad7ea04819098d465a5cc4", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/0a8b24f0d0247bf6dc5e2af0c0ab09c0c4e5343e", + "reference": "0a8b24f0d0247bf6dc5e2af0c0ab09c0c4e5343e", "shasum": "" }, "require": { @@ -18106,7 +18122,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.24.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.26.0" }, "funding": [ { @@ -18118,20 +18134,20 @@ "type": "tidelift" } ], - "time": "2025-12-02T14:45:16+00:00" + "time": "2026-05-15T13:14:14+00:00" }, { "name": "twig/intl-extra", - "version": "v3.24.0", + "version": "v3.26.0", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f" + "reference": "98f5ad5bff13230fcd2d834d9e79b50adf3ccda9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f", - "reference": "32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/98f5ad5bff13230fcd2d834d9e79b50adf3ccda9", + "reference": "98f5ad5bff13230fcd2d834d9e79b50adf3ccda9", "shasum": "" }, "require": { @@ -18170,7 +18186,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.24.0" + "source": "https://github.com/twigphp/intl-extra/tree/v3.26.0" }, "funding": [ { @@ -18182,20 +18198,20 @@ "type": "tidelift" } ], - "time": "2026-01-17T13:57:47+00:00" + "time": "2026-05-19T20:44:48+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.24.0", + "version": "v3.26.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "67a11120356e034a5bbc70c5b9b9a4d0f31ca06e" + "reference": "e3f3fd0836eb6c39457da22c8a76abaac62692b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/67a11120356e034a5bbc70c5b9b9a4d0f31ca06e", - "reference": "67a11120356e034a5bbc70c5b9b9a4d0f31ca06e", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/e3f3fd0836eb6c39457da22c8a76abaac62692b9", + "reference": "e3f3fd0836eb6c39457da22c8a76abaac62692b9", "shasum": "" }, "require": { @@ -18242,7 +18258,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.24.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.26.0" }, "funding": [ { @@ -18254,7 +18270,7 @@ "type": "tidelift" } ], - "time": "2026-02-07T08:07:38+00:00" + "time": "2026-05-15T13:14:02+00:00" }, { "name": "twig/string-extra", @@ -18325,16 +18341,16 @@ }, { "name": "twig/twig", - "version": "v3.24.0", + "version": "v3.26.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a6769aefb305efef849dc25c9fd1653358c148f0" + "reference": "1fcae487b180d78e6351f4e0afa91f9eab96a2bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6769aefb305efef849dc25c9fd1653358c148f0", - "reference": "a6769aefb305efef849dc25c9fd1653358c148f0", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/1fcae487b180d78e6351f4e0afa91f9eab96a2bc", + "reference": "1fcae487b180d78e6351f4e0afa91f9eab96a2bc", "shasum": "" }, "require": { @@ -18389,7 +18405,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.24.0" + "source": "https://github.com/twigphp/Twig/tree/v3.26.0" }, "funding": [ { @@ -18401,7 +18417,7 @@ "type": "tidelift" } ], - "time": "2026-03-17T21:31:11+00:00" + "time": "2026-05-20T07:31:59+00:00" }, { "name": "ua-parser/uap-php", @@ -18539,16 +18555,16 @@ }, { "name": "web-auth/webauthn-lib", - "version": "5.3.2", + "version": "5.3.4", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "a272f254c056fb3d6c80a4801d3c7c5fedc6a08d" + "reference": "dbb2d7a03db5893da2ef1f2898063ab8f7792838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/a272f254c056fb3d6c80a4801d3c7c5fedc6a08d", - "reference": "a272f254c056fb3d6c80a4801d3c7c5fedc6a08d", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/dbb2d7a03db5893da2ef1f2898063ab8f7792838", + "reference": "dbb2d7a03db5893da2ef1f2898063ab8f7792838", "shasum": "" }, "require": { @@ -18609,7 +18625,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/5.3.2" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.3.4" }, "funding": [ { @@ -18621,20 +18637,20 @@ "type": "patreon" } ], - "time": "2026-05-01T12:14:37+00:00" + "time": "2026-05-18T11:59:46+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "5.3.2", + "version": "5.3.4", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "1d20af98b50810e8776c52b671201b6bb73ea981" + "reference": "7bf9d0e5e1f6d6bcad97c6bd93dc11a61d6fbd83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/1d20af98b50810e8776c52b671201b6bb73ea981", - "reference": "1d20af98b50810e8776c52b671201b6bb73ea981", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/7bf9d0e5e1f6d6bcad97c6bd93dc11a61d6fbd83", + "reference": "7bf9d0e5e1f6d6bcad97c6bd93dc11a61d6fbd83", "shasum": "" }, "require": { @@ -18692,7 +18708,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.3.2" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.3.4" }, "funding": [ { @@ -18704,20 +18720,20 @@ "type": "patreon" } ], - "time": "2026-05-04T08:08:16+00:00" + "time": "2026-05-24T09:55:30+00:00" }, { "name": "webmozart/assert", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", - "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", "shasum": "" }, "require": { @@ -18733,7 +18749,11 @@ }, "type": "library", "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, "branch-alias": { + "dev-master": "2.0-dev", "dev-feature/2-0": "2.0-dev" } }, @@ -18764,9 +18784,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.3.0" + "source": "https://github.com/webmozarts/assert/tree/2.4.0" }, - "time": "2026-04-11T10:33:05+00:00" + "time": "2026-05-20T13:07:01+00:00" }, { "name": "willdurand/negotiation", @@ -19403,11 +19423,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.54", + "version": "2.1.55", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd", - "reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9eaac3826ed5e9b8427350a43cac825eeca3f566", + "reference": "9eaac3826ed5e9b8427350a43cac825eeca3f566", "shasum": "" }, "require": { @@ -19452,20 +19472,20 @@ "type": "github" } ], - "time": "2026-04-29T13:31:09+00:00" + "time": "2026-05-18T11:57:34+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.21", + "version": "2.0.22", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "81dac0ee4363c2359128aec844df31efb215dddc" + "reference": "e87516b034749432d51653c0147e053e476e8c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/81dac0ee4363c2359128aec844df31efb215dddc", - "reference": "81dac0ee4363c2359128aec844df31efb215dddc", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e87516b034749432d51653c0147e053e476e8c53", + "reference": "e87516b034749432d51653c0147e053e476e8c53", "shasum": "" }, "require": { @@ -19499,6 +19519,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", + "shipmonk/name-collision-detector": "^2.1", "symfony/cache": "^5.4", "symfony/uid": "^5.4 || ^6.4 || ^7.3" }, @@ -19526,9 +19547,9 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.21" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.22" }, - "time": "2026-04-17T13:00:39+00:00" + "time": "2026-05-09T08:10:48+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -19583,16 +19604,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.15", + "version": "2.0.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "9b85ab476969b87bbe2253b69e265a9359b2f395" + "reference": "a12176b639dec54e8bfd0a5ebf5fc36ffe003b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/9b85ab476969b87bbe2253b69e265a9359b2f395", - "reference": "9b85ab476969b87bbe2253b69e265a9359b2f395", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/a12176b639dec54e8bfd0a5ebf5fc36ffe003b5d", + "reference": "a12176b639dec54e8bfd0a5ebf5fc36ffe003b5d", "shasum": "" }, "require": { @@ -19651,9 +19672,9 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.15" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.18" }, - "time": "2026-02-26T10:15:59+00:00" + "time": "2026-05-18T14:51:49+00:00" }, { "name": "phpunit/php-code-coverage", @@ -20114,16 +20135,16 @@ }, { "name": "rector/rector", - "version": "2.4.2", + "version": "2.4.4", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946" + "reference": "4661c582a20f03df585d2e3fdc4af1b83d67a091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946", - "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/4661c582a20f03df585d2e3fdc4af1b83d67a091", + "reference": "4661c582a20f03df585d2e3fdc4af1b83d67a091", "shasum": "" }, "require": { @@ -20162,7 +20183,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.4.2" + "source": "https://github.com/rectorphp/rector/tree/2.4.4" }, "funding": [ { @@ -20170,7 +20191,7 @@ "type": "github" } ], - "time": "2026-04-16T13:07:34+00:00" + "time": "2026-05-20T19:30:21+00:00" }, { "name": "roave/security-advisories", @@ -20178,12 +20199,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "9d468c11a8da481c22b4e610494babae032fdb03" + "reference": "f9f1a88a11437cacd4d26b4953416af5c5425389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9d468c11a8da481c22b4e610494babae032fdb03", - "reference": "9d468c11a8da481c22b4e610494babae032fdb03", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/f9f1a88a11437cacd4d26b4953416af5c5425389", + "reference": "f9f1a88a11437cacd4d26b4953416af5c5425389", "shasum": "" }, "conflict": { @@ -20250,7 +20271,7 @@ "backpack/filemanager": "<2.0.2|>=3,<3.0.9", "bacula-web/bacula-web": "<9.7.1", "badaso/core": "<=2.9.11", - "bagisto/bagisto": "<2.3.10", + "bagisto/bagisto": "<=2.3.15", "barrelstrength/sprout-base-email": "<1.2.7", "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.8", @@ -20298,13 +20319,13 @@ "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", - "ci4-cms-erp/ci4ms": "<=0.31.7", + "ci4-cms-erp/ci4ms": "<=0.31.8", "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "ckeditor/ckeditor": "<4.25", "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", "co-stack/fal_sftp": "<0.2.6", - "cockpit-hq/cockpit": "<2.14", - "code16/sharp": "<9.20", + "cockpit-hq/cockpit": "<=2.14", + "code16/sharp": "<9.22", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.10", "codeigniter4/framework": "<4.6.2", @@ -20314,7 +20335,7 @@ "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", "commerceteam/commerce": ">=0.9.6,<0.9.9", "components/jquery": ">=1.0.3,<3.5", - "composer/composer": "<2.2.27|>=2.3,<2.9.6", + "composer/composer": "<2.2.28|>=2.3,<2.9.8", "concrete5/concrete5": "<9.4.8", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", @@ -20324,14 +20345,14 @@ "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", - "coreshop/core-shop": "<4.1.9", + "coreshop/core-shop": "<4.1.9|==5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", "couleurcitron/tarteaucitron-wp": "<0.3", "cpsit/typo3-mailqueue": "<0.4.5|>=0.5,<0.5.2", "craftcms/aws-s3": ">=2.0.2,<=2.2.4", "craftcms/azure-blob": ">=2.0.0.0-beta1,<=2.1", - "craftcms/cms": "<=4.17.8|>=5,<5.9.15", + "craftcms/cms": "<4.17.12|>=5,<5.9.18", "craftcms/commerce": ">=4,<4.11|>=5,<5.6", "craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1", "craftcms/craft": ">=3.5,<=4.16.17|>=5.0.0.0-RC1-dev,<=5.8.21", @@ -20372,7 +20393,7 @@ "doctrine/mongodb-odm": "<1.0.2", "doctrine/mongodb-odm-bundle": "<3.0.1", "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<=22.0.4", + "dolibarr/dolibarr": "<=23.0.2", "dompdf/dompdf": "<2.0.4", "doublethreedigital/guest-entries": "<3.1.2", "dreamfactory/df-core": "<1.0.4", @@ -20429,6 +20450,7 @@ "erusev/parsedown": "<1.7.2", "ether/logs": "<3.0.4", "evolutioncms/evolution": "<=3.2.3", + "evoweb/sf-register": "<13.2.4|>=14,<14.0.2", "exceedone/exment": "<4.4.3|>=5,<5.0.3", "exceedone/laravel-admin": "<2.2.3|==3", "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", @@ -20451,7 +20473,7 @@ "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<2025.81", + "facturascripts/facturascripts": "<=2025.92|>=2026,<=2026.1", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", @@ -20474,6 +20496,7 @@ "flarum/nicknames": "<1.8.3", "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", "flarum/tags": "<=0.1.0.0-beta13", + "flightphp/core": "<3.18.1", "floriangaerber/magnesium": "<0.3.1", "fluidtypo3/vhs": "<5.1.1", "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", @@ -20492,19 +20515,21 @@ "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "friendsoftypo3/tt-address": "<8.1.2|>=9,<9.1.1|>=10,<10.0.1", "froala/wysiwyg-editor": "<=4.3", "frosh/adminer-platform": "<2.2.1", "froxlor/froxlor": "<2.3.6", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=7.1.0.0-RC4", + "funadmin/funadmin": "<=7.1.0.0-RC6", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "georgringer/news": "<1.3.3", + "georgringer/news": "<11.4.4|>=12,<12.3.2|>=13,<13.0.2|>=14,<14.0.3", "geshi/geshi": "<=1.0.9.1", "getformwork/formwork": "<=2.3.3", - "getgrav/grav": "<2.0.0.0-beta2", + "getgrav/grav": "<=2.0.0.0-RC1", "getgrav/grav-plugin-api": "<1.0.0.0-beta15", + "getgrav/grav-plugin-form": "<9.1", "getkirby/cms": "<4.9|>=5,<5.4", "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", "getkirby/panel": "<2.5.14", @@ -20564,6 +20589,7 @@ "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", + "intercom/intercom-php": "==5.0.2", "invoiceninja/invoiceninja": "<5.13.4", "ipl/web": "<=0.13", "islandora/crayfish": "<4.1", @@ -20603,16 +20629,17 @@ "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", "khodakhah/nodcms": "<=3.4.1", - "kimai/kimai": "<2.54", + "kimai/kimai": "<=2.55", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<=1.4.2", + "knplabs/knp-snappy": "<=1.7", "kohana/core": "<3.3.3", "koillection/koillection": "<1.6.12", "krayin/laravel-crm": "<=2.2", "kreait/firebase-php": ">=3.2,<3.8.1", "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", + "laktak/hjson": "<2.3", "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", @@ -20661,7 +20688,7 @@ "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", "manogi/nova-tiptap": "<=3.2.6", - "mantisbt/mantisbt": "<2.28.1", + "mantisbt/mantisbt": "<2.28.2", "marcwillmann/turn": "<0.3.3", "markhuot/craftql": "<=1.3.7", "marshmallow/nova-tiptap": "<5.7", @@ -20696,6 +20723,8 @@ "miniorange/miniorange-saml": "<1.4.3", "miraheze/ts-portal": "<=33", "mittwald/typo3_forum": "<1.2.1", + "mix/mix": ">=2,<=2.2.17", + "mmc/ceselector": "<3.0.3|>=4,<4.0.2|>=5,<5.0.1|>=6,<6.0.1", "mobiledetect/mobiledetectlib": "<2.8.32", "modx/revolution": "<=3.1", "mojo42/jirafeau": "<4.4", @@ -20799,7 +20828,7 @@ "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<=4.1", + "phpmyfaq/phpmyfaq": "<4.1.3", "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", @@ -20821,7 +20850,7 @@ "pimcore/demo": "<10.3", "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", - "pimcore/pimcore": "<=11.5.14.1|>=12,<12.3.3", + "pimcore/pimcore": "<=11.5.14.1|>=12,<12.3.3|==12.3.3", "pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1", "piwik/piwik": "<1.11", "pixelfed/pixelfed": "<0.12.5", @@ -20835,7 +20864,7 @@ "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.2.5|>=9.0.0.0-alpha1,<9.1", + "prestashop/prestashop": "<8.2.6|>=9,<9.1.1", "prestashop/productcomments": "<5.0.2", "prestashop/ps_checkout": "<5.3", "prestashop/ps_contactinfo": "<=3.3.2", @@ -20890,9 +20919,11 @@ "scheb/two-factor-bundle": "<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", - "setasign/fpdi": "<2.6.4", + "setasign/fpdi": "<2.6.7", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<1.2.1", + "shopper/cart": "<2.8", + "shopper/framework": "<2.8", "shopware/core": "<6.6.10.15-dev|>=6.7,<6.7.8.1-dev", "shopware/platform": "<6.6.10.15-dev|>=6.7,<6.7.8.1-dev", "shopware/production": "<=6.3.5.2", @@ -20924,6 +20955,7 @@ "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", "simplesamlphp/saml2-legacy": "<=4.16.15", "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-casserver": "<=7.0.2", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "simplesamlphp/simplesamlphp-module-openid": "<1", "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", @@ -20938,7 +20970,7 @@ "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<8.3.7", + "snipe/snipe-it": "<8.4.1", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6", @@ -20956,14 +20988,14 @@ "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<5.73.20|>=6,<6.13", + "statamic/cms": "<5.73.22|>=6,<6.18.1", "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<2.1.67", + "studio-42/elfinder": "<=2.1.67", "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", "sulu/form-bundle": ">=2,<2.5.3", - "sulu/sulu": "<2.6.22|>=3,<3.0.5", + "sulu/sulu": "<=2.6.22|>=3,<=3.0.5", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "svewap/a21glossary": "<=0.4.10", @@ -20981,42 +21013,51 @@ "symbiote/silverstripe-seed": "<6.0.3", "symbiote/silverstripe-versionedfiles": "<=2.0.3", "symfont/process": ">=0", - "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/cache": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/dom-crawler": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", + "symfony/html-sanitizer": ">=6.1,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", - "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6|>=7.4,<7.4.12|>=8,<8.0.12", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/json-path": ">=7.3,<7.4.12|>=8,<8.0.12", + "symfony/lox24-notifier": ">=7.1,<7.4.12|>=8,<8.0.12", + "symfony/mailer": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mailjet-mailer": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mailtrap-mailer": ">=7.2,<7.4.12|>=8,<8.0.12", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": ">=4.3,<4.3.8", + "symfony/mime": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/monolog-bridge": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", "symfony/process": "<5.4.51|>=6,<6.4.33|>=7,<7.1.7|>=7.3,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/routing": ">=2,<2.0.19", - "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/routing": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/runtime": ">=5.3,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/security-http": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", + "symfony/symfony": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/translation": ">=2,<2.0.17", - "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8|>=6.4.24,<6.4.40", + "symfony/twilio-notifier": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/ux-autocomplete": "<2.11.2", "symfony/ux-live-component": "<2.25.1", "symfony/ux-twig-component": "<2.25.1", "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4|>=7.2.9,<7.4.12|>=8,<8.0.12", "symfony/webhook": ">=6.3,<6.3.8", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symfony/yaml": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symphonycms/symphony-2": "<2.6.4", "t3/dce": "<0.11.5|>=2.2,<2.6.2", "t3g/svg-sanitizer": "<1.0.3", @@ -21030,7 +21071,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<4.1.1", + "thorsten/phpmyfaq": "<4.1.3", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7.2", @@ -21038,16 +21079,20 @@ "titon/framework": "<9.9.99", "tltneon/lgsl": "<7", "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "tomasnorre/crawler": "<11.0.13|>=12,<12.0.11", "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", "torrentpier/torrentpier": "<=2.8.8", - "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tpwd/ke_search": "<5.6.2|>=6,<6.6.1|>=7,<7.0.1", "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", - "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", + "twig/cssinliner-extra": "<3.26", + "twig/intl-extra": "<3.26", + "twig/markdown-extra": "<3.26", + "twig/twig": "<3.26", "typicms/core": "<16.1.7", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1|==14.2", @@ -21089,7 +21134,7 @@ "uvdesk/core-framework": "<=1.1.1", "vanilla/safecurl": "<0.9.2", "verbb/comments": "<1.5.5", - "verbb/formie": "<=2.1.43", + "verbb/formie": "<2.2.20|>=3.0.0.0-beta1,<3.1.24", "verbb/image-resizer": "<2.0.9", "verbb/knock-knock": "<1.2.8", "verot/class.upload.php": "<=2.1.6", @@ -21103,7 +21148,7 @@ "wallabag/wallabag": "<2.6.11", "wanglelecc/laracms": "<=1.0.3", "wapplersystems/a21glossary": "<=0.4.10", - "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4|>=5.3,<5.3.1", "web-auth/webauthn-lib": ">=4.5,<4.9|>=5.2,<5.2.4", "web-auth/webauthn-symfony-bundle": ">=5.2,<5.2.4", "web-feet/coastercms": "==5.5", @@ -21137,12 +21182,12 @@ "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", "yansongda/pay": "<=3.7.19", - "yeswiki/yeswiki": "<=4.6", + "yeswiki/yeswiki": "<4.6.4", "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.31", - "yiisoft/yii2": "<2.0.52", + "yiisoft/yii2": "<2.0.55", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<=2.0.45", @@ -21232,7 +21277,7 @@ "type": "tidelift" } ], - "time": "2026-05-05T21:24:41+00:00" + "time": "2026-05-24T20:23:40+00:00" }, { "name": "sebastian/cli-parser", @@ -21346,6 +21391,7 @@ "type": "github" } ], + "abandoned": true, "time": "2025-03-19T07:56:08+00:00" }, { @@ -21402,6 +21448,7 @@ "type": "github" } ], + "abandoned": true, "time": "2024-07-03T04:45:54+00:00" }, { @@ -22606,16 +22653,16 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v7.4.9", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "36dd8b8c05da059925c5804641aad9159e5b73e8" + "reference": "558fe81a383302318d9b92f7661deb731153c86e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/36dd8b8c05da059925c5804641aad9159e5b73e8", - "reference": "36dd8b8c05da059925c5804641aad9159e5b73e8", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/558fe81a383302318d9b92f7661deb731153c86e", + "reference": "558fe81a383302318d9b92f7661deb731153c86e", "shasum": "" }, "require": { @@ -22672,7 +22719,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.9" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.12" }, "funding": [ { @@ -22692,7 +22739,7 @@ "type": "tidelift" } ], - "time": "2026-04-22T15:21:55+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/reference.php b/config/reference.php index e1304d43..38a275d6 100644 --- a/config/reference.php +++ b/config/reference.php @@ -193,40 +193,40 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * workflows?: bool|array{ * enabled?: bool|Param, // Default: false * workflows?: array, - * definition_validators?: list, - * support_strategy?: scalar|Param|null, - * initial_marking?: backed-enum|string|list, - * events_to_dispatch?: null|list, - * places?: string|list, - * }>, - * transitions?: list, - * to?: backed-enum|string|list, - * weight?: int|Param, // Default: 1 - * metadata?: array, - * }>, + * audit_trail?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * type?: "workflow"|"state_machine"|Param, // Default: "state_machine" + * marking_store?: array{ + * type?: "method"|Param, + * property?: scalar|Param|null, + * service?: scalar|Param|null, + * }, + * supports?: string|list, + * definition_validators?: list, + * support_strategy?: scalar|Param|null, + * initial_marking?: \BackedEnum|string|list, + * events_to_dispatch?: null|list, + * places?: string|list, * }>, + * transitions?: list, + * to?: \BackedEnum|string|list, + * weight?: int|Param, // Default: 1 + * metadata?: array, + * }>, + * metadata?: array, + * }>, * }, * router?: bool|array{ // Router configuration * enabled?: bool|Param, // Default: false @@ -273,14 +273,14 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * base_path?: scalar|Param|null, // Default: "" * base_urls?: string|list, * packages?: array, - * }>, + * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|Param|null, // Default: null + * version?: scalar|Param|null, + * version_format?: scalar|Param|null, // Default: null + * json_manifest_path?: scalar|Param|null, // Default: null + * base_path?: scalar|Param|null, // Default: "" + * base_urls?: string|list, + * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration * enabled?: bool|Param, // Default: false @@ -318,16 +318,16 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * localizable_html_attributes?: list, * }, * providers?: array, - * locales?: list, - * }>, + * dsn?: scalar|Param|null, + * domains?: list, + * locales?: list, + * }>, * globals?: array, - * domain?: string|Param, - * }>, + * value?: mixed, + * message?: string|Param, + * parameters?: array, + * domain?: string|Param, + * }>, * }, * validation?: bool|array{ // Validation configuration * enabled?: bool|Param, // Default: true @@ -345,8 +345,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * disable_translation?: bool|Param, // Default: false * auto_mapping?: array, - * }>, + * services?: list, + * }>, * }, * annotations?: bool|array{ * enabled?: bool|Param, // Default: false @@ -362,11 +362,11 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * default_context?: array, * named_serializers?: array, - * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true - * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true - * }>, + * name_converter?: scalar|Param|null, + * default_context?: array, + * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true + * }>, * }, * property_access?: bool|array{ // Property access configuration * enabled?: bool|Param, // Default: true @@ -396,24 +396,24 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection" * default_pdo_provider?: scalar|Param|null, // Default: null * pools?: array, - * tags?: scalar|Param|null, // Default: null - * public?: bool|Param, // Default: false - * default_lifetime?: scalar|Param|null, // Default lifetime of the pool. - * provider?: scalar|Param|null, // Overwrite the setting from the default provider for this adapter. - * early_expiration_message_bus?: scalar|Param|null, - * clearer?: scalar|Param|null, - * }>, + * adapters?: string|list, + * tags?: scalar|Param|null, // Default: null + * public?: bool|Param, // Default: false + * default_lifetime?: scalar|Param|null, // Default lifetime of the pool. + * provider?: scalar|Param|null, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|Param|null, + * clearer?: scalar|Param|null, + * }>, * }, * php_errors?: array{ // PHP errors handling configuration * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true * throw?: bool|Param, // Throw PHP errors as \ErrorException instances. // Default: true * }, * exceptions?: array, + * log_level?: scalar|Param|null, // The level of log message. Null to let Symfony decide. // Default: null + * status_code?: scalar|Param|null, // The status code of the response. Null or 0 to let Symfony decide. // Default: null + * log_channel?: scalar|Param|null, // The channel of log message. Null to let Symfony decide. // Default: null + * }>, * web_link?: bool|array{ // Web links configuration * enabled?: bool|Param, // Default: true * }, @@ -428,8 +428,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * messenger?: bool|array{ // Messenger configuration * enabled?: bool|Param, // Default: false * routing?: array, - * }>, + * senders?: list, + * }>, * serializer?: array{ * default_serializer?: scalar|Param|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" * symfony_serializer?: array{ @@ -438,34 +438,34 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * }, * transports?: array, - * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * retry_strategy?: string|array{ - * service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null - * max_retries?: int|Param, // Default: 3 - * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 - * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 - * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 - * jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 - * }, - * rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null - * }>, + * dsn?: scalar|Param|null, + * serializer?: scalar|Param|null, // Service id of a custom serializer to use. // Default: null + * options?: array, + * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: string|array{ + * service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null + * }>, * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null * stop_worker_on_signals?: int|string|list, * default_bus?: scalar|Param|null, // Default: null * buses?: array, - * }>, + * default_middleware?: bool|string|array{ + * enabled?: bool|Param, // Default: true + * allow_no_handlers?: bool|Param, // Default: false + * allow_no_senders?: bool|Param, // Default: true + * }, + * middleware?: string|list, * }>, + * }>, * }, * scheduler?: bool|array{ // Scheduler configuration * enabled?: bool|Param, // Default: false @@ -511,9 +511,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * enabled?: bool|Param, // Default: false * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null * http_codes?: int|string|array, - * }>, + * code?: int|Param, + * methods?: string|list, + * }>, * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 @@ -523,57 +523,57 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * mock_response_factory?: scalar|Param|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. * scoped_clients?: array, - * headers?: array, - * max_redirects?: int|Param, // The maximum number of redirects to follow. - * http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. - * resolve?: array, - * proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection. - * no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached. - * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. - * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. - * bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. - * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. - * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. - * cafile?: scalar|Param|null, // A certificate authority file. - * capath?: scalar|Param|null, // A directory that contains multiple certificate authority files. - * local_cert?: scalar|Param|null, // A PEM formatted certificate file. - * local_pk?: scalar|Param|null, // A private key file. - * passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file. - * ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). - * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). - * sha1?: mixed, - * pin-sha256?: mixed, - * md5?: mixed, - * }, - * crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. - * extra?: array, - * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null - * caching?: bool|array{ // Caching configuration. - * enabled?: bool|Param, // Default: false - * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" - * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true - * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null - * }, - * retry_failed?: bool|array{ - * enabled?: bool|Param, // Default: false - * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null - * http_codes?: int|string|array, - * }>, - * max_retries?: int|Param, // Default: 3 - * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 - * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 - * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 - * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 - * }, - * }>, + * scope?: scalar|Param|null, // The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead. + * base_uri?: scalar|Param|null, // The URI to resolve relative URLs, following rules in RFC 3985, section 2. + * auth_basic?: scalar|Param|null, // An HTTP Basic authentication "username:password". + * auth_bearer?: scalar|Param|null, // A token enabling HTTP Bearer authorization. + * auth_ntlm?: scalar|Param|null, // A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension). + * query?: array, + * headers?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|Param|null, // A certificate authority file. + * capath?: scalar|Param|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|Param|null, // A PEM formatted certificate file. + * local_pk?: scalar|Param|null, // A private key file. + * passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null + * http_codes?: int|string|array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, * }, * mailer?: bool|array{ // Mailer configuration * enabled?: bool|Param, // Default: true @@ -586,8 +586,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * allowed_recipients?: string|list, * }, * headers?: array, + * value?: mixed, + * }>, * dkim_signer?: bool|array{ // DKIM signer configuration * enabled?: bool|Param, // Default: false * key?: scalar|Param|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" @@ -624,25 +624,25 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * notification_on_failed_messages?: bool|Param, // Default: false * channel_policy?: array>, * admin_recipients?: list, + * email?: scalar|Param|null, + * phone?: scalar|Param|null, // Default: "" + * }>, * }, * rate_limiter?: bool|array{ // Rate limiter configuration * enabled?: bool|Param, // Default: true * limiters?: array, - * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. - * interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). - * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". - * interval?: scalar|Param|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). - * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 - * }, - * }>, + * lock_factory?: scalar|Param|null, // The service ID of the lock factory used by this limiter (or null to disable locking). // Default: "auto" + * cache_pool?: scalar|Param|null, // The cache pool to use for storing the current limiter state. // Default: "cache.rate_limiter" + * storage_service?: scalar|Param|null, // The service ID of a custom storage implementation, this precedes any configured "cache_pool". // Default: null + * policy?: "fixed_window"|"token_bucket"|"sliding_window"|"compound"|"no_limit"|Param, // The algorithm to be used by this limiter. + * limiters?: string|list, + * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|Param|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 + * }, + * }>, * }, * uid?: bool|array{ // Uid configuration * enabled?: bool|Param, // Default: true @@ -655,33 +655,33 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * html_sanitizer?: bool|array{ // HtmlSanitizer configuration * enabled?: bool|Param, // Default: false * sanitizers?: array, - * block_elements?: string|list, - * drop_elements?: string|list, - * allow_attributes?: array, - * drop_attributes?: array, - * force_attributes?: array>, - * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false - * allowed_link_schemes?: string|list, - * allowed_link_hosts?: null|string|list, - * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false - * allowed_media_schemes?: string|list, - * allowed_media_hosts?: null|string|list, - * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false - * with_attribute_sanitizers?: string|list, - * without_attribute_sanitizers?: string|list, - * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 - * }>, + * allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false + * allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false + * allow_elements?: array, + * block_elements?: string|list, + * drop_elements?: string|list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: string|list, + * allowed_link_hosts?: null|string|list, + * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: string|list, + * allowed_media_hosts?: null|string|list, + * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: string|list, + * without_attribute_sanitizers?: string|list, + * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, * }, * webhook?: bool|array{ // Webhook configuration * enabled?: bool|Param, // Default: false * message_bus?: scalar|Param|null, // The message bus to use. // Default: "messenger.default_bus" * routing?: array, + * service?: scalar|Param|null, + * secret?: scalar|Param|null, // Default: "" + * }>, * }, * remote-event?: bool|array{ // RemoteEvent configuration * enabled?: bool|Param, // Default: false @@ -694,11 +694,62 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * dbal?: array{ * default_connection?: scalar|Param|null, * types?: array, + * class?: scalar|Param|null, + * commented?: bool|Param, // Deprecated: The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0. + * }>, * driver_schemes?: array, * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|Param|null, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|Param|null, + * slaves?: array, - * mapping_types?: array, - * default_table_options?: array, - * schema_manager_factory?: scalar|Param|null, // Default: "doctrine.dbal.default_schema_manager_factory" - * result_cache?: scalar|Param|null, - * slaves?: array, - * replicas?: array, * }>, + * replicas?: array, + * }>, * }, * orm?: array{ * default_entity_manager?: scalar|Param|null, @@ -828,94 +828,94 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false * }, * entity_managers?: array, - * }>, + * query_cache_driver?: string|array{ + * type?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * pool?: scalar|Param|null, + * }, + * metadata_cache_driver?: string|array{ + * type?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * pool?: scalar|Param|null, + * }, + * result_cache_driver?: string|array{ + * type?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * pool?: scalar|Param|null, + * }, + * entity_listeners?: array{ + * entities?: array, + * }>, + * }>, + * }, + * connection?: scalar|Param|null, + * class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|Param|null, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|Param|null, // Default: false + * naming_strategy?: scalar|Param|null, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|Param|null, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|Param|null, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|Param|null, // Default: null + * fetch_mode_subselect_batch_size?: scalar|Param|null, + * repository_factory?: scalar|Param|null, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * report_fields_where_declared?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. // Default: true + * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: string|array{ + * type?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * pool?: scalar|Param|null, * }, - * connection?: scalar|Param|null, - * class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" - * default_repository_class?: scalar|Param|null, // Default: "Doctrine\\ORM\\EntityRepository" - * auto_mapping?: scalar|Param|null, // Default: false - * naming_strategy?: scalar|Param|null, // Default: "doctrine.orm.naming_strategy.default" - * quote_strategy?: scalar|Param|null, // Default: "doctrine.orm.quote_strategy.default" - * typed_field_mapper?: scalar|Param|null, // Default: "doctrine.orm.typed_field_mapper.default" - * entity_listener_resolver?: scalar|Param|null, // Default: null - * fetch_mode_subselect_batch_size?: scalar|Param|null, - * repository_factory?: scalar|Param|null, // Default: "doctrine.orm.container_repository_factory" - * schema_ignore_classes?: list, - * report_fields_where_declared?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. // Default: true - * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14. See https://github.com/doctrine/orm/pull/6728. // Default: false - * second_level_cache?: array{ - * region_cache_driver?: string|array{ + * region_lock_lifetime?: scalar|Param|null, // Default: 60 + * log_enabled?: bool|Param, // Default: true + * region_lifetime?: scalar|Param|null, // Default: 3600 + * enabled?: bool|Param, // Default: true + * factory?: scalar|Param|null, + * regions?: array, - * loggers?: array, - * }, - * hydrators?: array, - * mappings?: array, - * dql?: array{ - * string_functions?: array, - * numeric_functions?: array, - * datetime_functions?: array, - * }, - * filters?: array, - * }>, - * identity_generation_preferences?: array, + * lock_path?: scalar|Param|null, // Default: "%kernel.cache_dir%/doctrine/orm/slc/filelock" + * lock_lifetime?: scalar|Param|null, // Default: 60 + * type?: scalar|Param|null, // Default: "default" + * lifetime?: scalar|Param|null, // Default: 0 + * service?: scalar|Param|null, + * name?: scalar|Param|null, + * }>, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * }>, + * identity_generation_preferences?: array, + * }>, * resolve_target_entities?: array, * }, * } @@ -957,391 +957,391 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * allow_if_equal_granted_denied?: bool|Param, // Default: true * }, * password_hashers?: array, - * hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" - * key_length?: scalar|Param|null, // Default: 40 - * ignore_case?: bool|Param, // Default: false - * encode_as_base64?: bool|Param, // Default: true - * iterations?: scalar|Param|null, // Default: 5000 - * cost?: int|Param, // Default: null - * memory_cost?: scalar|Param|null, // Default: null - * time_cost?: scalar|Param|null, // Default: null - * id?: scalar|Param|null, - * }>, + * algorithm?: scalar|Param|null, + * migrate_from?: string|list, + * hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|Param|null, // Default: 40 + * ignore_case?: bool|Param, // Default: false + * encode_as_base64?: bool|Param, // Default: true + * iterations?: scalar|Param|null, // Default: 5000 + * cost?: int|Param, // Default: null + * memory_cost?: scalar|Param|null, // Default: null + * time_cost?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * }>, * providers?: array, - * }, - * entity?: array{ - * class?: scalar|Param|null, // The full entity class name of your user class. - * property?: scalar|Param|null, // Default: null - * manager_name?: scalar|Param|null, // Default: null - * }, - * memory?: array{ - * users?: array, - * }>, - * }, - * ldap?: array{ - * service?: scalar|Param|null, - * base_dn?: scalar|Param|null, - * search_dn?: scalar|Param|null, // Default: null - * search_password?: scalar|Param|null, // Default: null - * extra_fields?: list, - * default_roles?: string|list, - * role_fetcher?: scalar|Param|null, // Default: null - * uid_key?: scalar|Param|null, // Default: "sAMAccountName" - * filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})" - * password_attribute?: scalar|Param|null, // Default: null - * }, - * saml?: array{ - * user_class?: scalar|Param|null, - * default_roles?: list, - * }, - * }>, + * id?: scalar|Param|null, + * chain?: array{ + * providers?: string|list, + * }, + * entity?: array{ + * class?: scalar|Param|null, // The full entity class name of your user class. + * property?: scalar|Param|null, // Default: null + * manager_name?: scalar|Param|null, // Default: null + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service?: scalar|Param|null, + * base_dn?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: null + * search_password?: scalar|Param|null, // Default: null + * extra_fields?: list, + * default_roles?: string|list, + * role_fetcher?: scalar|Param|null, // Default: null + * uid_key?: scalar|Param|null, // Default: "sAMAccountName" + * filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|Param|null, // Default: null + * }, + * saml?: array{ + * user_class?: scalar|Param|null, + * default_roles?: list, + * }, + * }>, * firewalls?: array, - * security?: bool|Param, // Default: true - * user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" - * request_matcher?: scalar|Param|null, - * access_denied_url?: scalar|Param|null, - * access_denied_handler?: scalar|Param|null, - * entry_point?: scalar|Param|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". - * provider?: scalar|Param|null, - * stateless?: bool|Param, // Default: false - * lazy?: bool|Param, // Default: false - * context?: scalar|Param|null, - * logout?: array{ - * enable_csrf?: bool|Param|null, // Default: null - * csrf_token_id?: scalar|Param|null, // Default: "logout" - * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" - * csrf_token_manager?: scalar|Param|null, - * path?: scalar|Param|null, // Default: "/logout" - * target?: scalar|Param|null, // Default: "/" - * invalidate_session?: bool|Param, // Default: true - * clear_site_data?: string|list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, - * delete_cookies?: string|array, - * }, - * switch_user?: array{ - * provider?: scalar|Param|null, - * parameter?: scalar|Param|null, // Default: "_switch_user" - * role?: scalar|Param|null, // Default: "ROLE_ALLOWED_TO_SWITCH" - * target_route?: scalar|Param|null, // Default: null - * }, - * required_badges?: list, - * custom_authenticators?: list, - * login_throttling?: array{ - * limiter?: scalar|Param|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". - * max_attempts?: int|Param, // Default: 5 - * interval?: scalar|Param|null, // Default: "1 minute" - * lock_factory?: scalar|Param|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null - * cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" - * storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null - * }, - * two_factor?: array{ - * check_path?: scalar|Param|null, // Default: "/2fa_check" - * post_only?: bool|Param, // Default: true - * auth_form_path?: scalar|Param|null, // Default: "/2fa" - * always_use_default_target_path?: bool|Param, // Default: false - * default_target_path?: scalar|Param|null, // Default: "/" - * success_handler?: scalar|Param|null, // Default: null - * failure_handler?: scalar|Param|null, // Default: null - * authentication_required_handler?: scalar|Param|null, // Default: null - * auth_code_parameter_name?: scalar|Param|null, // Default: "_auth_code" - * trusted_parameter_name?: scalar|Param|null, // Default: "_trusted" - * remember_me_sets_trusted?: scalar|Param|null, // Default: false - * multi_factor?: bool|Param, // Default: false - * prepare_on_login?: bool|Param, // Default: false - * prepare_on_access_denied?: bool|Param, // Default: false - * enable_csrf?: scalar|Param|null, // Default: false - * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" - * csrf_token_id?: scalar|Param|null, // Default: "two_factor" - * csrf_header?: scalar|Param|null, // Default: null - * csrf_token_manager?: scalar|Param|null, // Default: "scheb_two_factor.csrf_token_manager" - * provider?: scalar|Param|null, // Default: null - * }, - * webauthn?: array{ - * user_provider?: scalar|Param|null, // Default: null - * options_storage?: scalar|Param|null, // Deprecated: The child node "options_storage" at path "security.firewalls..webauthn.options_storage" is deprecated. Please use the root option "options_storage" instead. // Default: null - * success_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultSuccessHandler" - * failure_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultFailureHandler" - * secured_rp_ids?: array, - * authentication?: bool|array{ - * enabled?: bool|Param, // Default: true - * profile?: scalar|Param|null, // Default: "default" - * options_builder?: scalar|Param|null, // Default: null - * routes?: array{ - * host?: scalar|Param|null, // Default: null - * options_method?: scalar|Param|null, // Default: "POST" - * options_path?: scalar|Param|null, // Default: "/login/options" - * result_method?: scalar|Param|null, // Default: "POST" - * result_path?: scalar|Param|null, // Default: "/login" - * }, - * options_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultRequestOptionsHandler" - * }, - * registration?: bool|array{ - * enabled?: bool|Param, // Default: false - * hide_existing_credentials?: bool|Param, // Default: true - * profile?: scalar|Param|null, // Default: "default" - * options_builder?: scalar|Param|null, // Default: null - * routes?: array{ - * host?: scalar|Param|null, // Default: null - * options_method?: scalar|Param|null, // Default: "POST" - * options_path?: scalar|Param|null, // Default: "/register/options" - * result_method?: scalar|Param|null, // Default: "POST" - * result_path?: scalar|Param|null, // Default: "/register" - * }, - * options_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultCreationOptionsHandler" - * }, - * }, - * x509?: array{ - * provider?: scalar|Param|null, - * user?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN_Email" - * credentials?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN" - * user_identifier?: scalar|Param|null, // Default: "emailAddress" - * }, - * remote_user?: array{ - * provider?: scalar|Param|null, - * user?: scalar|Param|null, // Default: "REMOTE_USER" - * }, - * saml?: array{ - * provider?: scalar|Param|null, - * remember_me?: bool|Param, // Default: true - * success_handler?: scalar|Param|null, // Default: "Nbgrp\\OneloginSamlBundle\\Security\\Http\\Authentication\\SamlAuthenticationSuccessHandler" - * failure_handler?: scalar|Param|null, - * check_path?: scalar|Param|null, // Default: "/login_check" - * use_forward?: bool|Param, // Default: false - * login_path?: scalar|Param|null, // Default: "/login" - * identifier_attribute?: scalar|Param|null, // Default: null - * use_attribute_friendly_name?: bool|Param, // Default: false - * user_factory?: scalar|Param|null, // Default: null - * token_factory?: scalar|Param|null, // Default: null - * persist_user?: bool|Param, // Default: false - * always_use_default_target_path?: bool|Param, // Default: false - * default_target_path?: scalar|Param|null, // Default: "/" - * target_path_parameter?: scalar|Param|null, // Default: "_target_path" - * use_referer?: bool|Param, // Default: false - * failure_path?: scalar|Param|null, // Default: null - * failure_forward?: bool|Param, // Default: false - * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" - * }, - * login_link?: array{ - * check_route?: scalar|Param|null, // Route that will validate the login link - e.g. "app_login_link_verify". - * check_post_only?: scalar|Param|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false - * signature_properties?: list, - * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 - * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null - * used_link_cache?: scalar|Param|null, // Cache service id used to expired links of max_uses is set. - * success_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. - * failure_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. - * provider?: scalar|Param|null, // The user provider to load users from. - * secret?: scalar|Param|null, // Default: "%kernel.secret%" - * always_use_default_target_path?: bool|Param, // Default: false - * default_target_path?: scalar|Param|null, // Default: "/" - * login_path?: scalar|Param|null, // Default: "/login" - * target_path_parameter?: scalar|Param|null, // Default: "_target_path" - * use_referer?: bool|Param, // Default: false - * failure_path?: scalar|Param|null, // Default: null - * failure_forward?: bool|Param, // Default: false - * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" - * }, - * form_login?: array{ - * provider?: scalar|Param|null, - * remember_me?: bool|Param, // Default: true - * success_handler?: scalar|Param|null, - * failure_handler?: scalar|Param|null, - * check_path?: scalar|Param|null, // Default: "/login_check" - * use_forward?: bool|Param, // Default: false - * login_path?: scalar|Param|null, // Default: "/login" - * username_parameter?: scalar|Param|null, // Default: "_username" - * password_parameter?: scalar|Param|null, // Default: "_password" - * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" - * csrf_token_id?: scalar|Param|null, // Default: "authenticate" - * enable_csrf?: bool|Param, // Default: false - * post_only?: bool|Param, // Default: true - * form_only?: bool|Param, // Default: false - * always_use_default_target_path?: bool|Param, // Default: false - * default_target_path?: scalar|Param|null, // Default: "/" - * target_path_parameter?: scalar|Param|null, // Default: "_target_path" - * use_referer?: bool|Param, // Default: false - * failure_path?: scalar|Param|null, // Default: null - * failure_forward?: bool|Param, // Default: false - * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" - * }, - * form_login_ldap?: array{ - * provider?: scalar|Param|null, - * remember_me?: bool|Param, // Default: true - * success_handler?: scalar|Param|null, - * failure_handler?: scalar|Param|null, - * check_path?: scalar|Param|null, // Default: "/login_check" - * use_forward?: bool|Param, // Default: false - * login_path?: scalar|Param|null, // Default: "/login" - * username_parameter?: scalar|Param|null, // Default: "_username" - * password_parameter?: scalar|Param|null, // Default: "_password" - * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" - * csrf_token_id?: scalar|Param|null, // Default: "authenticate" - * enable_csrf?: bool|Param, // Default: false - * post_only?: bool|Param, // Default: true - * form_only?: bool|Param, // Default: false - * always_use_default_target_path?: bool|Param, // Default: false - * default_target_path?: scalar|Param|null, // Default: "/" - * target_path_parameter?: scalar|Param|null, // Default: "_target_path" - * use_referer?: bool|Param, // Default: false - * failure_path?: scalar|Param|null, // Default: null - * failure_forward?: bool|Param, // Default: false - * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" - * service?: scalar|Param|null, // Default: "ldap" - * dn_string?: scalar|Param|null, // Default: "{user_identifier}" - * query_string?: scalar|Param|null, - * search_dn?: scalar|Param|null, // Default: "" - * search_password?: scalar|Param|null, // Default: "" - * }, - * json_login?: array{ - * provider?: scalar|Param|null, - * remember_me?: bool|Param, // Default: true - * success_handler?: scalar|Param|null, - * failure_handler?: scalar|Param|null, - * check_path?: scalar|Param|null, // Default: "/login_check" - * use_forward?: bool|Param, // Default: false - * login_path?: scalar|Param|null, // Default: "/login" - * username_path?: scalar|Param|null, // Default: "username" - * password_path?: scalar|Param|null, // Default: "password" - * }, - * json_login_ldap?: array{ - * provider?: scalar|Param|null, - * remember_me?: bool|Param, // Default: true - * success_handler?: scalar|Param|null, - * failure_handler?: scalar|Param|null, - * check_path?: scalar|Param|null, // Default: "/login_check" - * use_forward?: bool|Param, // Default: false - * login_path?: scalar|Param|null, // Default: "/login" - * username_path?: scalar|Param|null, // Default: "username" - * password_path?: scalar|Param|null, // Default: "password" - * service?: scalar|Param|null, // Default: "ldap" - * dn_string?: scalar|Param|null, // Default: "{user_identifier}" - * query_string?: scalar|Param|null, - * search_dn?: scalar|Param|null, // Default: "" - * search_password?: scalar|Param|null, // Default: "" - * }, - * access_token?: array{ - * provider?: scalar|Param|null, - * remember_me?: bool|Param, // Default: true - * success_handler?: scalar|Param|null, - * failure_handler?: scalar|Param|null, - * realm?: scalar|Param|null, // Default: null - * token_extractors?: string|list, - * token_handler?: string|array{ - * id?: scalar|Param|null, - * oidc_user_info?: string|array{ - * base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). - * discovery?: array{ // Enable the OIDC discovery. - * cache?: array{ - * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. - * }, - * }, - * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" - * client?: scalar|Param|null, // HttpClient service id to use to call the OIDC server. - * }, - * oidc?: array{ - * discovery?: array{ // Enable the OIDC discovery. - * base_uri?: string|list, - * cache?: array{ - * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. - * }, - * }, - * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" - * audience?: scalar|Param|null, // Audience set in the token, for validation purpose. - * issuers?: list, - * algorithm?: array, - * algorithms?: list, - * key?: scalar|Param|null, // Deprecated: The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. // JSON-encoded JWK used to sign the token (must contain a "kty" key). - * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). - * encryption?: bool|array{ - * enabled?: bool|Param, // Default: false - * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false - * algorithms?: list, - * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). - * }, - * }, - * cas?: array{ - * validation_url?: scalar|Param|null, // CAS server validation URL - * prefix?: scalar|Param|null, // CAS prefix // Default: "cas" - * http_client?: scalar|Param|null, // HTTP Client service // Default: null - * }, - * oauth2?: scalar|Param|null, - * }, - * }, - * http_basic?: array{ - * provider?: scalar|Param|null, - * realm?: scalar|Param|null, // Default: "Secured Area" - * }, - * http_basic_ldap?: array{ - * provider?: scalar|Param|null, - * realm?: scalar|Param|null, // Default: "Secured Area" - * service?: scalar|Param|null, // Default: "ldap" - * dn_string?: scalar|Param|null, // Default: "{user_identifier}" - * query_string?: scalar|Param|null, - * search_dn?: scalar|Param|null, // Default: "" - * search_password?: scalar|Param|null, // Default: "" - * }, - * remember_me?: array{ - * secret?: scalar|Param|null, // Default: "%kernel.secret%" - * service?: scalar|Param|null, - * user_providers?: string|list, - * catch_exceptions?: bool|Param, // Default: true - * signature_properties?: list, - * token_provider?: string|array{ - * service?: scalar|Param|null, // The service ID of a custom remember-me token provider. - * doctrine?: bool|array{ - * enabled?: bool|Param, // Default: false - * connection?: scalar|Param|null, // Default: null - * }, - * }, - * token_verifier?: scalar|Param|null, // The service ID of a custom rememberme token verifier. - * name?: scalar|Param|null, // Default: "REMEMBERME" - * lifetime?: int|Param, // Default: 31536000 - * path?: scalar|Param|null, // Default: "/" + * pattern?: scalar|Param|null, + * host?: scalar|Param|null, + * methods?: string|list, + * security?: bool|Param, // Default: true + * user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|Param|null, + * access_denied_url?: scalar|Param|null, + * access_denied_handler?: scalar|Param|null, + * entry_point?: scalar|Param|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|Param|null, + * stateless?: bool|Param, // Default: false + * lazy?: bool|Param, // Default: false + * context?: scalar|Param|null, + * logout?: array{ + * enable_csrf?: bool|Param|null, // Default: null + * csrf_token_id?: scalar|Param|null, // Default: "logout" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_manager?: scalar|Param|null, + * path?: scalar|Param|null, // Default: "/logout" + * target?: scalar|Param|null, // Default: "/" + * invalidate_session?: bool|Param, // Default: true + * clear_site_data?: string|list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, + * delete_cookies?: string|array, + * }, + * switch_user?: array{ + * provider?: scalar|Param|null, + * parameter?: scalar|Param|null, // Default: "_switch_user" + * role?: scalar|Param|null, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|Param|null, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|Param|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int|Param, // Default: 5 + * interval?: scalar|Param|null, // Default: "1 minute" + * lock_factory?: scalar|Param|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null + * cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * two_factor?: array{ + * check_path?: scalar|Param|null, // Default: "/2fa_check" + * post_only?: bool|Param, // Default: true + * auth_form_path?: scalar|Param|null, // Default: "/2fa" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * success_handler?: scalar|Param|null, // Default: null + * failure_handler?: scalar|Param|null, // Default: null + * authentication_required_handler?: scalar|Param|null, // Default: null + * auth_code_parameter_name?: scalar|Param|null, // Default: "_auth_code" + * trusted_parameter_name?: scalar|Param|null, // Default: "_trusted" + * remember_me_sets_trusted?: scalar|Param|null, // Default: false + * multi_factor?: bool|Param, // Default: false + * prepare_on_login?: bool|Param, // Default: false + * prepare_on_access_denied?: bool|Param, // Default: false + * enable_csrf?: scalar|Param|null, // Default: false + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "two_factor" + * csrf_header?: scalar|Param|null, // Default: null + * csrf_token_manager?: scalar|Param|null, // Default: "scheb_two_factor.csrf_token_manager" + * provider?: scalar|Param|null, // Default: null + * }, + * webauthn?: array{ + * user_provider?: scalar|Param|null, // Default: null + * options_storage?: scalar|Param|null, // Deprecated: The child node "options_storage" at path "security.firewalls..webauthn.options_storage" is deprecated. Please use the root option "options_storage" instead. // Default: null + * success_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultSuccessHandler" + * failure_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultFailureHandler" + * secured_rp_ids?: array, + * authentication?: bool|array{ + * enabled?: bool|Param, // Default: true + * profile?: scalar|Param|null, // Default: "default" + * options_builder?: scalar|Param|null, // Default: null + * routes?: array{ + * host?: scalar|Param|null, // Default: null + * options_method?: scalar|Param|null, // Default: "POST" + * options_path?: scalar|Param|null, // Default: "/login/options" + * result_method?: scalar|Param|null, // Default: "POST" + * result_path?: scalar|Param|null, // Default: "/login" + * }, + * options_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultRequestOptionsHandler" * }, - * }>, + * registration?: bool|array{ + * enabled?: bool|Param, // Default: false + * hide_existing_credentials?: bool|Param, // Default: true + * profile?: scalar|Param|null, // Default: "default" + * options_builder?: scalar|Param|null, // Default: null + * routes?: array{ + * host?: scalar|Param|null, // Default: null + * options_method?: scalar|Param|null, // Default: "POST" + * options_path?: scalar|Param|null, // Default: "/register/options" + * result_method?: scalar|Param|null, // Default: "POST" + * result_path?: scalar|Param|null, // Default: "/register" + * }, + * options_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultCreationOptionsHandler" + * }, + * }, + * x509?: array{ + * provider?: scalar|Param|null, + * user?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|Param|null, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|Param|null, + * user?: scalar|Param|null, // Default: "REMOTE_USER" + * }, + * saml?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, // Default: "Nbgrp\\OneloginSamlBundle\\Security\\Http\\Authentication\\SamlAuthenticationSuccessHandler" + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * identifier_attribute?: scalar|Param|null, // Default: null + * use_attribute_friendly_name?: bool|Param, // Default: false + * user_factory?: scalar|Param|null, // Default: null + * token_factory?: scalar|Param|null, // Default: null + * persist_user?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * login_link?: array{ + * check_route?: scalar|Param|null, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|Param|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties?: list, + * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|Param|null, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|Param|null, // The user provider to load users from. + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * login_path?: scalar|Param|null, // Default: "/login" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_parameter?: scalar|Param|null, // Default: "_username" + * password_parameter?: scalar|Param|null, // Default: "_password" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_parameter?: scalar|Param|null, // Default: "_username" + * password_parameter?: scalar|Param|null, // Default: "_password" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_path?: scalar|Param|null, // Default: "username" + * password_path?: scalar|Param|null, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_path?: scalar|Param|null, // Default: "username" + * password_path?: scalar|Param|null, // Default: "password" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: null + * token_extractors?: string|list, + * token_handler?: string|array{ + * id?: scalar|Param|null, + * oidc_user_info?: string|array{ + * base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|Param|null, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri?: string|list, + * cache?: array{ + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience?: scalar|Param|null, // Audience set in the token, for validation purpose. + * issuers?: list, + * algorithm?: array, + * algorithms?: list, + * key?: scalar|Param|null, // Deprecated: The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. // JSON-encoded JWK used to sign the token (must contain a "kty" key). + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool|Param, // Default: false + * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false + * algorithms?: list, + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url?: scalar|Param|null, // CAS server validation URL + * prefix?: scalar|Param|null, // CAS prefix // Default: "cas" + * http_client?: scalar|Param|null, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|Param|null, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: "Secured Area" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * service?: scalar|Param|null, + * user_providers?: string|list, + * catch_exceptions?: bool|Param, // Default: true + * signature_properties?: list, + * token_provider?: string|array{ + * service?: scalar|Param|null, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: false + * connection?: scalar|Param|null, // Default: null + * }, + * }, + * token_verifier?: scalar|Param|null, // The service ID of a custom rememberme token verifier. + * name?: scalar|Param|null, // Default: "REMEMBERME" + * lifetime?: int|Param, // Default: 31536000 + * path?: scalar|Param|null, // Default: "/" + * domain?: scalar|Param|null, // Default: null + * secure?: true|false|"auto"|Param, // Default: null + * httponly?: bool|Param, // Default: true + * samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * always_remember_me?: bool|Param, // Default: false + * remember_me_parameter?: scalar|Param|null, // Default: "_remember_me" + * }, + * }>, * access_control?: list, - * attributes?: array, - * route?: scalar|Param|null, // Default: null - * methods?: string|list, - * allow_if?: scalar|Param|null, // Default: null - * roles?: string|list, - * }>, + * request_matcher?: scalar|Param|null, // Default: null + * requires_channel?: scalar|Param|null, // Default: null + * path?: scalar|Param|null, // Use the urldecoded format. // Default: null + * host?: scalar|Param|null, // Default: null + * port?: int|Param, // Default: null + * ips?: string|list, + * attributes?: array, + * route?: scalar|Param|null, // Default: null + * methods?: string|list, + * allow_if?: scalar|Param|null, // Default: null + * roles?: string|list, + * }>, * role_hierarchy?: array>, * } * @psalm-type TwigConfig = array{ * form_themes?: list, * globals?: array, + * id?: scalar|Param|null, + * type?: scalar|Param|null, + * value?: mixed, + * }>, * autoescape_service?: scalar|Param|null, // Default: null * autoescape_service_method?: scalar|Param|null, // Default: null * base_template_class?: scalar|Param|null, // Deprecated: The child node "base_template_class" at path "twig.base_template_class" is deprecated. @@ -1380,144 +1380,144 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * use_microseconds?: scalar|Param|null, // Default: true * channels?: list, * handlers?: array, - * }>, - * accepted_levels?: list, - * min_level?: scalar|Param|null, // Default: "DEBUG" - * max_level?: scalar|Param|null, // Default: "EMERGENCY" - * buffer_size?: scalar|Param|null, // Default: 0 - * flush_on_overflow?: bool|Param, // Default: false - * handler?: scalar|Param|null, - * url?: scalar|Param|null, - * exchange?: scalar|Param|null, - * exchange_name?: scalar|Param|null, // Default: "log" - * channel?: scalar|Param|null, // Default: null - * bot_name?: scalar|Param|null, // Default: "Monolog" - * use_attachment?: scalar|Param|null, // Default: true - * use_short_attachment?: scalar|Param|null, // Default: false - * include_extra?: scalar|Param|null, // Default: false - * icon_emoji?: scalar|Param|null, // Default: null - * webhook_url?: scalar|Param|null, - * exclude_fields?: list, - * token?: scalar|Param|null, - * region?: scalar|Param|null, - * source?: scalar|Param|null, - * use_ssl?: bool|Param, // Default: true - * user?: mixed, - * title?: scalar|Param|null, // Default: null - * host?: scalar|Param|null, // Default: null - * port?: scalar|Param|null, // Default: 514 - * config?: list, - * members?: list, - * connection_string?: scalar|Param|null, - * timeout?: scalar|Param|null, - * time?: scalar|Param|null, // Default: 60 - * deduplication_level?: scalar|Param|null, // Default: 400 - * store?: scalar|Param|null, // Default: null - * connection_timeout?: scalar|Param|null, - * persistent?: bool|Param, - * message_type?: scalar|Param|null, // Default: 0 - * parse_mode?: scalar|Param|null, // Default: null - * disable_webpage_preview?: bool|Param|null, // Default: null - * disable_notification?: bool|Param|null, // Default: null - * split_long_messages?: bool|Param, // Default: false - * delay_between_messages?: bool|Param, // Default: false - * topic?: int|Param, // Default: null - * factor?: int|Param, // Default: 1 - * tags?: string|list, - * console_formatter_options?: mixed, // Default: [] - * formatter?: scalar|Param|null, - * nested?: bool|Param, // Default: false - * publisher?: string|array{ - * id?: scalar|Param|null, - * hostname?: scalar|Param|null, - * port?: scalar|Param|null, // Default: 12201 - * chunk_size?: scalar|Param|null, // Default: 1420 - * encoder?: "json"|"compressed_json"|Param, - * }, - * mongodb?: string|array{ - * id?: scalar|Param|null, // ID of a MongoDB\Client service - * uri?: scalar|Param|null, - * username?: scalar|Param|null, - * password?: scalar|Param|null, - * database?: scalar|Param|null, // Default: "monolog" - * collection?: scalar|Param|null, // Default: "logs" - * }, - * elasticsearch?: string|array{ - * id?: scalar|Param|null, - * hosts?: list, - * host?: scalar|Param|null, - * port?: scalar|Param|null, // Default: 9200 - * transport?: scalar|Param|null, // Default: "Http" - * user?: scalar|Param|null, // Default: null - * password?: scalar|Param|null, // Default: null - * }, - * index?: scalar|Param|null, // Default: "monolog" - * document_type?: scalar|Param|null, // Default: "logs" - * ignore_error?: scalar|Param|null, // Default: false - * redis?: string|array{ - * id?: scalar|Param|null, - * host?: scalar|Param|null, - * password?: scalar|Param|null, // Default: null - * port?: scalar|Param|null, // Default: 6379 - * database?: scalar|Param|null, // Default: 0 - * key_name?: scalar|Param|null, // Default: "monolog_redis" - * }, - * predis?: string|array{ - * id?: scalar|Param|null, - * host?: scalar|Param|null, - * }, - * from_email?: scalar|Param|null, - * to_email?: string|list, - * subject?: scalar|Param|null, - * content_type?: scalar|Param|null, // Default: null - * headers?: list, - * mailer?: scalar|Param|null, // Default: null - * email_prototype?: string|array{ - * id?: scalar|Param|null, - * method?: scalar|Param|null, // Default: null - * }, - * verbosity_levels?: array{ - * VERBOSITY_QUIET?: scalar|Param|null, // Default: "ERROR" - * VERBOSITY_NORMAL?: scalar|Param|null, // Default: "WARNING" - * VERBOSITY_VERBOSE?: scalar|Param|null, // Default: "NOTICE" - * VERBOSITY_VERY_VERBOSE?: scalar|Param|null, // Default: "INFO" - * VERBOSITY_DEBUG?: scalar|Param|null, // Default: "DEBUG" - * }, - * channels?: string|array{ - * type?: scalar|Param|null, - * elements?: list, - * }, + * type?: scalar|Param|null, + * id?: scalar|Param|null, + * enabled?: bool|Param, // Default: true + * priority?: scalar|Param|null, // Default: 0 + * level?: scalar|Param|null, // Default: "DEBUG" + * bubble?: bool|Param, // Default: true + * interactive_only?: bool|Param, // Default: false + * app_name?: scalar|Param|null, // Default: null + * include_stacktraces?: bool|Param, // Default: false + * process_psr_3_messages?: array{ + * enabled?: bool|Param|null, // Default: null + * date_format?: scalar|Param|null, + * remove_used_context_fields?: bool|Param, + * }, + * path?: scalar|Param|null, // Default: "%kernel.logs_dir%/%kernel.environment%.log" + * file_permission?: scalar|Param|null, // Default: null + * use_locking?: bool|Param, // Default: false + * filename_format?: scalar|Param|null, // Default: "{filename}-{date}" + * date_format?: scalar|Param|null, // Default: "Y-m-d" + * ident?: scalar|Param|null, // Default: false + * logopts?: scalar|Param|null, // Default: 1 + * facility?: scalar|Param|null, // Default: "user" + * max_files?: scalar|Param|null, // Default: 0 + * action_level?: scalar|Param|null, // Default: "WARNING" + * activation_strategy?: scalar|Param|null, // Default: null + * stop_buffering?: bool|Param, // Default: true + * passthru_level?: scalar|Param|null, // Default: null + * excluded_http_codes?: list, * }>, + * accepted_levels?: list, + * min_level?: scalar|Param|null, // Default: "DEBUG" + * max_level?: scalar|Param|null, // Default: "EMERGENCY" + * buffer_size?: scalar|Param|null, // Default: 0 + * flush_on_overflow?: bool|Param, // Default: false + * handler?: scalar|Param|null, + * url?: scalar|Param|null, + * exchange?: scalar|Param|null, + * exchange_name?: scalar|Param|null, // Default: "log" + * channel?: scalar|Param|null, // Default: null + * bot_name?: scalar|Param|null, // Default: "Monolog" + * use_attachment?: scalar|Param|null, // Default: true + * use_short_attachment?: scalar|Param|null, // Default: false + * include_extra?: scalar|Param|null, // Default: false + * icon_emoji?: scalar|Param|null, // Default: null + * webhook_url?: scalar|Param|null, + * exclude_fields?: list, + * token?: scalar|Param|null, + * region?: scalar|Param|null, + * source?: scalar|Param|null, + * use_ssl?: bool|Param, // Default: true + * user?: mixed, + * title?: scalar|Param|null, // Default: null + * host?: scalar|Param|null, // Default: null + * port?: scalar|Param|null, // Default: 514 + * config?: list, + * members?: list, + * connection_string?: scalar|Param|null, + * timeout?: scalar|Param|null, + * time?: scalar|Param|null, // Default: 60 + * deduplication_level?: scalar|Param|null, // Default: 400 + * store?: scalar|Param|null, // Default: null + * connection_timeout?: scalar|Param|null, + * persistent?: bool|Param, + * message_type?: scalar|Param|null, // Default: 0 + * parse_mode?: scalar|Param|null, // Default: null + * disable_webpage_preview?: bool|Param|null, // Default: null + * disable_notification?: bool|Param|null, // Default: null + * split_long_messages?: bool|Param, // Default: false + * delay_between_messages?: bool|Param, // Default: false + * topic?: int|Param, // Default: null + * factor?: int|Param, // Default: 1 + * tags?: string|list, + * console_formatter_options?: mixed, // Default: [] + * formatter?: scalar|Param|null, + * nested?: bool|Param, // Default: false + * publisher?: string|array{ + * id?: scalar|Param|null, + * hostname?: scalar|Param|null, + * port?: scalar|Param|null, // Default: 12201 + * chunk_size?: scalar|Param|null, // Default: 1420 + * encoder?: "json"|"compressed_json"|Param, + * }, + * mongodb?: string|array{ + * id?: scalar|Param|null, // ID of a MongoDB\Client service + * uri?: scalar|Param|null, + * username?: scalar|Param|null, + * password?: scalar|Param|null, + * database?: scalar|Param|null, // Default: "monolog" + * collection?: scalar|Param|null, // Default: "logs" + * }, + * elasticsearch?: string|array{ + * id?: scalar|Param|null, + * hosts?: list, + * host?: scalar|Param|null, + * port?: scalar|Param|null, // Default: 9200 + * transport?: scalar|Param|null, // Default: "Http" + * user?: scalar|Param|null, // Default: null + * password?: scalar|Param|null, // Default: null + * }, + * index?: scalar|Param|null, // Default: "monolog" + * document_type?: scalar|Param|null, // Default: "logs" + * ignore_error?: scalar|Param|null, // Default: false + * redis?: string|array{ + * id?: scalar|Param|null, + * host?: scalar|Param|null, + * password?: scalar|Param|null, // Default: null + * port?: scalar|Param|null, // Default: 6379 + * database?: scalar|Param|null, // Default: 0 + * key_name?: scalar|Param|null, // Default: "monolog_redis" + * }, + * predis?: string|array{ + * id?: scalar|Param|null, + * host?: scalar|Param|null, + * }, + * from_email?: scalar|Param|null, + * to_email?: string|list, + * subject?: scalar|Param|null, + * content_type?: scalar|Param|null, // Default: null + * headers?: list, + * mailer?: scalar|Param|null, // Default: null + * email_prototype?: string|array{ + * id?: scalar|Param|null, + * method?: scalar|Param|null, // Default: null + * }, + * verbosity_levels?: array{ + * VERBOSITY_QUIET?: scalar|Param|null, // Default: "ERROR" + * VERBOSITY_NORMAL?: scalar|Param|null, // Default: "WARNING" + * VERBOSITY_VERBOSE?: scalar|Param|null, // Default: "NOTICE" + * VERBOSITY_VERY_VERBOSE?: scalar|Param|null, // Default: "INFO" + * VERBOSITY_DEBUG?: scalar|Param|null, // Default: "DEBUG" + * }, + * channels?: string|array{ + * type?: scalar|Param|null, + * elements?: list, + * }, + * }>, * } * @psalm-type DebugConfig = array{ * max_items?: int|Param, // Max number of displayed items past the first level, -1 means no limit. // Default: 2500 @@ -1557,52 +1557,52 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * } * @psalm-type LiipImagineConfig = array{ * resolvers?: array, - * get_options?: array, - * put_options?: array, - * proxies?: array, - * }, - * flysystem?: array{ - * filesystem_service?: scalar|Param|null, - * cache_prefix?: scalar|Param|null, // Default: "" - * root_url?: scalar|Param|null, - * visibility?: "public"|"private"|"noPredefinedVisibility"|Param, // Default: "public" - * }, - * }>, + * web_path?: array{ + * web_root?: scalar|Param|null, // Default: "%kernel.project_dir%/public" + * cache_prefix?: scalar|Param|null, // Default: "media/cache" + * }, + * aws_s3?: array{ + * bucket?: scalar|Param|null, + * cache?: scalar|Param|null, // Default: false + * use_psr_cache?: bool|Param, // Default: false + * acl?: scalar|Param|null, // Default: "public-read" + * cache_prefix?: scalar|Param|null, // Default: "" + * client_id?: scalar|Param|null, // Default: null + * client_config?: list, + * get_options?: array, + * put_options?: array, + * proxies?: array, + * }, + * flysystem?: array{ + * filesystem_service?: scalar|Param|null, + * cache_prefix?: scalar|Param|null, // Default: "" + * root_url?: scalar|Param|null, + * visibility?: "public"|"private"|"noPredefinedVisibility"|Param, // Default: "public" + * }, + * }>, * loaders?: array, + * allow_unresolvable_data_roots?: bool|Param, // Default: false + * bundle_resources?: array{ + * enabled?: bool|Param, // Default: false + * access_control_type?: "blacklist"|"whitelist"|Param, // Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist. // Default: "blacklist" + * access_control_list?: list, * }, - * filesystem?: array{ - * locator?: "filesystem"|"filesystem_insecure"|Param, // Using the "filesystem_insecure" locator is not recommended due to a less secure resolver mechanism, but is provided for those using heavily symlinked projects. // Default: "filesystem" - * data_root?: string|list, - * allow_unresolvable_data_roots?: bool|Param, // Default: false - * bundle_resources?: array{ - * enabled?: bool|Param, // Default: false - * access_control_type?: "blacklist"|"whitelist"|Param, // Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist. // Default: "blacklist" - * access_control_list?: list, - * }, - * }, - * flysystem?: array{ - * filesystem_service?: scalar|Param|null, - * }, - * asset_mapper?: array, - * chain?: array{ - * loaders?: list, - * }, - * }>, + * }, + * flysystem?: array{ + * filesystem_service?: scalar|Param|null, + * }, + * asset_mapper?: array, + * chain?: array{ + * loaders?: list, + * }, + * }>, * driver?: scalar|Param|null, // Default: "gd" * cache?: scalar|Param|null, // Default: "default" * cache_base_path?: scalar|Param|null, // Default: "" @@ -1627,18 +1627,18 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * redirect_response_code?: int|Param, // Default: 302 * }, * filter_sets?: array>, - * post_processors?: array>, - * }>, + * quality?: scalar|Param|null, + * jpeg_quality?: scalar|Param|null, + * png_compression_level?: scalar|Param|null, + * png_compression_filter?: scalar|Param|null, + * format?: scalar|Param|null, + * animated?: bool|Param, + * cache?: scalar|Param|null, + * data_loader?: scalar|Param|null, + * default_image?: scalar|Param|null, + * filters?: array>, + * post_processors?: array>, + * }>, * twig?: array{ * mode?: "none"|"lazy"|"legacy"|Param, // Twig mode: none/lazy/legacy (default) // Default: "legacy" * assets_version?: scalar|Param|null, // Default: null @@ -1853,8 +1853,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * clickjacking?: array{ * hosts?: list, * paths?: array, + * header?: scalar|Param|null, // Default: "DENY" + * }>, * content_types?: list, * }, * external_redirects?: array{ @@ -2019,12 +2019,12 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * cross_origin_isolation?: bool|array{ * enabled?: bool|Param, // Default: false * paths?: array, + * coep?: "unsafe-none"|"require-corp"|"credentialless"|Param, // Cross-Origin-Embedder-Policy (COEP) header value + * coop?: "unsafe-none"|"same-origin-allow-popups"|"same-origin"|"noopener-allow-popups"|Param, // Cross-Origin-Opener-Policy (COOP) header value + * corp?: "same-site"|"same-origin"|"cross-origin"|Param, // Cross-Origin-Resource-Policy (CORP) header value + * report_only?: bool|Param, // Use Report-Only headers instead of enforcing (applies to COEP and COOP only) // Default: false + * report_to?: scalar|Param|null, // Reporting endpoint name for violations (requires Reporting API configuration, applies to COEP and COOP only) // Default: null + * }>, * }, * } * @psalm-type TurboConfig = array{ @@ -2099,31 +2099,31 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * counter_checker?: scalar|Param|null, // This service will check if the counter is valid. By default it throws an exception (recommended). // Default: "Webauthn\\Counter\\ThrowExceptionIfInvalid" * top_origin_validator?: scalar|Param|null, // For cross origin (e.g. iframe), this service will be in charge of verifying the top origin. // Default: null * creation_profiles?: bool|array, - * public_key_credential_parameters?: list, - * attestation_conveyance?: scalar|Param|null, // Default: "none" - * conditional_create?: bool|Param, // Enable Conditional Create (auto-register) for this profile. When true, user presence can be false after password authentication. See https://github.com/w3c/webauthn/wiki/Explainer:-Conditional-Create // Default: false - * }>, - * request_profiles?: bool|array, - * }>, + * resident_key?: scalar|Param|null, // Default: "preferred" + * }, + * extensions?: array, + * public_key_credential_parameters?: list, + * attestation_conveyance?: scalar|Param|null, // Default: "none" + * conditional_create?: bool|Param, // Enable Conditional Create (auto-register) for this profile. When true, user presence can be false after password authentication. See https://github.com/w3c/webauthn/wiki/Explainer:-Conditional-Create // Default: false + * }>, + * request_profiles?: bool|array, + * }>, * client_override_policy?: array{ // Configuration for allowing client request values to override profile configuration * user_verification?: array{ * enabled?: bool|Param, // Whether to allow client requests to override the user verification requirement // Default: false @@ -2158,39 +2158,39 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * controllers?: bool|array{ * enabled?: bool|Param, // Default: false * creation?: array, - * allow_subdomains?: bool|Param, // Default: false - * secured_rp_ids?: array, - * }>, + * options_method?: scalar|Param|null, // Default: "POST" + * options_path?: scalar|Param|null, + * result_method?: scalar|Param|null, // Default: "POST" + * result_path?: scalar|Param|null, // Default: null + * host?: scalar|Param|null, // Default: null + * profile?: scalar|Param|null, // Default: "default" + * options_builder?: scalar|Param|null, // When set, corresponds to the ID of the Public Key Credential Creation Builder. The profile-based ebuilder is ignored. // Default: null + * user_entity_guesser?: scalar|Param|null, + * hide_existing_credentials?: scalar|Param|null, // In order to prevent username enumeration, the existing credentials can be hidden. This is highly recommended when the attestation ceremony is performed by anonymous users. // Default: false + * options_storage?: scalar|Param|null, // Deprecated: The child node "options_storage" at path "webauthn.controllers.creation..options_storage" is deprecated. Please use the root option "options_storage" instead. // Service responsible of the options/user entity storage during the ceremony // Default: null + * success_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Service\\DefaultSuccessHandler" + * failure_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Service\\DefaultFailureHandler" + * options_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultCreationOptionsHandler" + * allowed_origins?: array, + * allow_subdomains?: bool|Param, // Default: false + * secured_rp_ids?: array, + * }>, * request?: array, - * allow_subdomains?: bool|Param, // Default: false - * secured_rp_ids?: array, - * }>, + * options_method?: scalar|Param|null, // Default: "POST" + * options_path?: scalar|Param|null, + * result_method?: scalar|Param|null, // Default: "POST" + * result_path?: scalar|Param|null, // Default: null + * host?: scalar|Param|null, // Default: null + * profile?: scalar|Param|null, // Default: "default" + * options_builder?: scalar|Param|null, // When set, corresponds to the ID of the Public Key Credential Creation Builder. The profile-based ebuilder is ignored. // Default: null + * options_storage?: scalar|Param|null, // Deprecated: The child node "options_storage" at path "webauthn.controllers.request..options_storage" is deprecated. Please use the root option "options_storage" instead. // Service responsible of the options/user entity storage during the ceremony // Default: null + * success_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Service\\DefaultSuccessHandler" + * failure_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Service\\DefaultFailureHandler" + * options_handler?: scalar|Param|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultRequestOptionsHandler" + * allowed_origins?: array, + * allow_subdomains?: bool|Param, // Default: false + * secured_rp_ids?: array, + * }>, * }, * passkey_endpoints?: bool|array{ // Enable the .well-known/passkey-endpoints discovery endpoint as defined in the W3C Passkey Endpoints specification. * enabled?: bool|Param, // Default: false @@ -2210,109 +2210,109 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * } * @psalm-type NbgrpOneloginSamlConfig = array{ // nb:group OneLogin PHP Symfony Bundle configuration * onelogin_settings?: array/saml/" - * strict?: bool|Param, - * debug?: bool|Param, - * idp?: array{ - * entityId?: scalar|Param|null, - * singleSignOnService?: array{ - * url?: scalar|Param|null, - * binding?: scalar|Param|null, - * }, - * singleLogoutService?: array{ - * url?: scalar|Param|null, - * responseUrl?: scalar|Param|null, - * binding?: scalar|Param|null, - * }, - * x509cert?: scalar|Param|null, - * certFingerprint?: scalar|Param|null, - * certFingerprintAlgorithm?: "sha1"|"sha256"|"sha384"|"sha512"|Param, - * x509certMulti?: array{ - * signing?: list, - * encryption?: list, - * }, + * baseurl?: scalar|Param|null, // Default: "/saml/" + * strict?: bool|Param, + * debug?: bool|Param, + * idp?: array{ + * entityId?: scalar|Param|null, + * singleSignOnService?: array{ + * url?: scalar|Param|null, + * binding?: scalar|Param|null, * }, - * sp?: array{ - * entityId?: scalar|Param|null, // Default: "/saml/metadata" - * assertionConsumerService?: array{ - * url?: scalar|Param|null, // Default: "/saml/acs" - * binding?: scalar|Param|null, - * }, - * attributeConsumingService?: array{ - * serviceName?: scalar|Param|null, - * serviceDescription?: scalar|Param|null, - * requestedAttributes?: list, - * }>, - * }, - * singleLogoutService?: array{ - * url?: scalar|Param|null, // Default: "/saml/logout" - * binding?: scalar|Param|null, - * }, - * NameIDFormat?: scalar|Param|null, - * x509cert?: scalar|Param|null, - * privateKey?: scalar|Param|null, - * x509certNew?: scalar|Param|null, + * singleLogoutService?: array{ + * url?: scalar|Param|null, + * responseUrl?: scalar|Param|null, + * binding?: scalar|Param|null, * }, - * compress?: array{ - * requests?: bool|Param, - * responses?: bool|Param, + * x509cert?: scalar|Param|null, + * certFingerprint?: scalar|Param|null, + * certFingerprintAlgorithm?: "sha1"|"sha256"|"sha384"|"sha512"|Param, + * x509certMulti?: array{ + * signing?: list, + * encryption?: list, * }, - * security?: array{ - * nameIdEncrypted?: bool|Param, - * authnRequestsSigned?: bool|Param, - * logoutRequestSigned?: bool|Param, - * logoutResponseSigned?: bool|Param, - * signMetadata?: bool|Param, - * wantMessagesSigned?: bool|Param, - * wantAssertionsEncrypted?: bool|Param, - * wantAssertionsSigned?: bool|Param, - * wantNameId?: bool|Param, - * wantNameIdEncrypted?: bool|Param, - * requestedAuthnContext?: mixed, - * requestedAuthnContextComparison?: "exact"|"minimum"|"maximum"|"better"|Param, - * wantXMLValidation?: bool|Param, - * relaxDestinationValidation?: bool|Param, - * destinationStrictlyMatches?: bool|Param, - * allowRepeatAttributeName?: bool|Param, - * rejectUnsolicitedResponsesWithInResponseTo?: bool|Param, - * signatureAlgorithm?: "http://www.w3.org/2000/09/xmldsig#rsa-sha1"|"http://www.w3.org/2000/09/xmldsig#dsa-sha1"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"|Param, - * digestAlgorithm?: "http://www.w3.org/2000/09/xmldsig#sha1"|"http://www.w3.org/2001/04/xmlenc#sha256"|"http://www.w3.org/2001/04/xmldsig-more#sha384"|"http://www.w3.org/2001/04/xmlenc#sha512"|Param, - * encryption_algorithm?: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"|"http://www.w3.org/2001/04/xmlenc#aes128-cbc"|"http://www.w3.org/2001/04/xmlenc#aes192-cbc"|"http://www.w3.org/2001/04/xmlenc#aes256-cbc"|"http://www.w3.org/2009/xmlenc11#aes128-gcm"|"http://www.w3.org/2009/xmlenc11#aes192-gcm"|"http://www.w3.org/2009/xmlenc11#aes256-gcm"|Param, - * lowercaseUrlencoding?: bool|Param, + * }, + * sp?: array{ + * entityId?: scalar|Param|null, // Default: "/saml/metadata" + * assertionConsumerService?: array{ + * url?: scalar|Param|null, // Default: "/saml/acs" + * binding?: scalar|Param|null, * }, - * contactPerson?: array{ - * technical?: array{ - * givenName?: scalar|Param|null, - * emailAddress?: scalar|Param|null, - * }, - * support?: array{ - * givenName?: scalar|Param|null, - * emailAddress?: scalar|Param|null, - * }, - * administrative?: array{ - * givenName?: scalar|Param|null, - * emailAddress?: scalar|Param|null, - * }, - * billing?: array{ - * givenName?: scalar|Param|null, - * emailAddress?: scalar|Param|null, - * }, - * other?: array{ - * givenName?: scalar|Param|null, - * emailAddress?: scalar|Param|null, - * }, - * }, - * organization?: list, * }>, + * }, + * singleLogoutService?: array{ + * url?: scalar|Param|null, // Default: "/saml/logout" + * binding?: scalar|Param|null, + * }, + * NameIDFormat?: scalar|Param|null, + * x509cert?: scalar|Param|null, + * privateKey?: scalar|Param|null, + * x509certNew?: scalar|Param|null, + * }, + * compress?: array{ + * requests?: bool|Param, + * responses?: bool|Param, + * }, + * security?: array{ + * nameIdEncrypted?: bool|Param, + * authnRequestsSigned?: bool|Param, + * logoutRequestSigned?: bool|Param, + * logoutResponseSigned?: bool|Param, + * signMetadata?: bool|Param, + * wantMessagesSigned?: bool|Param, + * wantAssertionsEncrypted?: bool|Param, + * wantAssertionsSigned?: bool|Param, + * wantNameId?: bool|Param, + * wantNameIdEncrypted?: bool|Param, + * requestedAuthnContext?: mixed, + * requestedAuthnContextComparison?: "exact"|"minimum"|"maximum"|"better"|Param, + * wantXMLValidation?: bool|Param, + * relaxDestinationValidation?: bool|Param, + * destinationStrictlyMatches?: bool|Param, + * allowRepeatAttributeName?: bool|Param, + * rejectUnsolicitedResponsesWithInResponseTo?: bool|Param, + * signatureAlgorithm?: "http://www.w3.org/2000/09/xmldsig#rsa-sha1"|"http://www.w3.org/2000/09/xmldsig#dsa-sha1"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"|Param, + * digestAlgorithm?: "http://www.w3.org/2000/09/xmldsig#sha1"|"http://www.w3.org/2001/04/xmlenc#sha256"|"http://www.w3.org/2001/04/xmldsig-more#sha384"|"http://www.w3.org/2001/04/xmlenc#sha512"|Param, + * encryption_algorithm?: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"|"http://www.w3.org/2001/04/xmlenc#aes128-cbc"|"http://www.w3.org/2001/04/xmlenc#aes192-cbc"|"http://www.w3.org/2001/04/xmlenc#aes256-cbc"|"http://www.w3.org/2009/xmlenc11#aes128-gcm"|"http://www.w3.org/2009/xmlenc11#aes192-gcm"|"http://www.w3.org/2009/xmlenc11#aes256-gcm"|Param, + * lowercaseUrlencoding?: bool|Param, + * }, + * contactPerson?: array{ + * technical?: array{ + * givenName?: scalar|Param|null, + * emailAddress?: scalar|Param|null, + * }, + * support?: array{ + * givenName?: scalar|Param|null, + * emailAddress?: scalar|Param|null, + * }, + * administrative?: array{ + * givenName?: scalar|Param|null, + * emailAddress?: scalar|Param|null, + * }, + * billing?: array{ + * givenName?: scalar|Param|null, + * emailAddress?: scalar|Param|null, + * }, + * other?: array{ + * givenName?: scalar|Param|null, + * emailAddress?: scalar|Param|null, + * }, + * }, + * organization?: list, + * }>, * use_proxy_vars?: bool|Param, // Default: false * idp_parameter_name?: scalar|Param|null, // Default: "idp" * entity_manager_name?: scalar|Param|null, @@ -2346,11 +2346,11 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * auto_install?: bool|Param, // Default: false * fonts?: list, + * normal?: scalar|Param|null, + * bold?: scalar|Param|null, + * italic?: scalar|Param|null, + * bold_italic?: scalar|Param|null, + * }>, * } * @psalm-type KnpuOauth2ClientConfig = array{ * http_client?: scalar|Param|null, // Service id of HTTP client to use (must implement GuzzleHttp\ClientInterface) // Default: null @@ -2376,18 +2376,18 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * skip_same_as_origin?: bool|Param, // Default: true * }, * paths?: array, - * allow_headers?: list, - * allow_methods?: list, - * allow_private_network?: bool|Param, - * expose_headers?: list, - * max_age?: scalar|Param|null, // Default: 0 - * hosts?: list, - * origin_regex?: bool|Param, - * forced_allow_origin_value?: scalar|Param|null, // Default: null - * skip_same_as_origin?: bool|Param, - * }>, + * allow_credentials?: bool|Param, + * allow_origin?: list, + * allow_headers?: list, + * allow_methods?: list, + * allow_private_network?: bool|Param, + * expose_headers?: list, + * max_age?: scalar|Param|null, // Default: 0 + * hosts?: list, + * origin_regex?: bool|Param, + * forced_allow_origin_value?: scalar|Param|null, // Default: null + * skip_same_as_origin?: bool|Param, + * }>, * } * @psalm-type JbtronicsSettingsConfig = array{ * search_paths?: list, @@ -2518,13 +2518,13 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * persist_authorization?: bool|Param, // Persist the SwaggerUI Authorization in the localStorage. // Default: false * versions?: list, * api_keys?: array, + * name?: scalar|Param|null, // The name of the header or query parameter containing the api key. + * type?: "query"|"header"|Param, // Whether the api key should be a query parameter or a header. + * }>, * http_auth?: array, + * scheme?: scalar|Param|null, // The OpenAPI HTTP auth scheme, for example "bearer" + * bearerFormat?: scalar|Param|null, // The OpenAPI HTTP bearer format + * }>, * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] * }, * http_cache?: array{ @@ -2565,9 +2565,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * termsOfService?: scalar|Param|null, // A URL to the Terms of Service for the API. MUST be in the format of a URL. // Default: null * tags?: list, + * name?: scalar|Param|null, + * description?: scalar|Param|null, // Default: null + * }>, * license?: array{ * name?: scalar|Param|null, // The license name used for the API. // Default: null * url?: scalar|Param|null, // URL to the license used for the API. MUST be in the format of a URL. // Default: null @@ -2589,17 +2589,17 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * exception_to_status?: array, * formats?: array, - * }>, + * mime_types?: list, + * }>, * patch_formats?: array, - * }>, + * mime_types?: list, + * }>, * docs_formats?: array, - * }>, + * mime_types?: list, + * }>, * error_formats?: array, - * }>, + * mime_types?: list, + * }>, * jsonschema_formats?: list, * defaults?: array{ * uri_template?: mixed, @@ -2671,30 +2671,30 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * policy?: mixed, * middleware?: mixed, * parameters?: array - * }>, + * key?: mixed, + * schema?: mixed, + * open_api?: mixed, + * provider?: mixed, + * filter?: mixed, + * property?: mixed, + * description?: mixed, + * properties?: mixed, + * required?: mixed, + * priority?: mixed, + * hydra?: mixed, + * constraints?: mixed, + * security?: mixed, + * security_message?: mixed, + * extra_properties?: mixed, + * filter_context?: mixed, + * native_type?: mixed, + * cast_to_array?: mixed, + * cast_to_native_type?: mixed, + * cast_fn?: mixed, + * default?: mixed, + * filter_class?: mixed, + * ... + * }>, * strict_query_parameter_validation?: mixed, * hide_hydra_operation?: mixed, * json_stream?: mixed, @@ -2735,22 +2735,22 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * cache_retention?: "none"|"short"|"long"|Param, // Prompt cache retention policy for Anthropic models // Default: "short" * }, * azure?: array, + * api_key?: string|Param, + * base_url?: string|Param, + * deployment?: string|Param, + * api_version?: string|Param, // The used API version + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }>, * bedrock?: array, + * bedrock_runtime_client?: string|Param, // Service ID of the Bedrock runtime client to use // Default: null + * model_catalog?: string|Param, // Default: null + * }>, * cache?: array, + * platform?: string|Param, + * service?: string|Param, // The cache service id as defined under the "cache" configuration key // Default: "cache.app" + * cache_key?: string|Param, // Key used to store platform results, if not set, the current platform name will be used, the "prompt_cache_key" can be set during platform call to override this value + * ttl?: int|Param, + * }>, * cartesia?: array{ * api_key?: string|Param, * version?: string|Param, @@ -2783,23 +2783,23 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" * }, * failover?: array, - * rate_limiter?: string|Param, - * }>, + * platforms?: list, + * rate_limiter?: string|Param, + * }>, * gemini?: array{ * api_key?: string|Param, * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" * }, * generic?: array, + * base_url?: string|Param, + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * model_catalog?: string|Param, // Service ID of the model catalog to use + * supports_completions?: bool|Param, // Default: true + * supports_embeddings?: bool|Param, // Default: true + * completions_path?: string|Param, // Default: "/v1/chat/completions" + * embeddings_path?: string|Param, // Default: "/v1/embeddings" + * }>, * huggingface?: array{ * api_key?: string|Param, * provider?: string|Param, // Default: "hf-inference" @@ -2823,6 +2823,13 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * region?: scalar|Param|null, // The region for OpenAI API (EU, US, or null for default) // Default: null * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" * }, + * openresponses?: array, * openrouter?: array{ * api_key?: string|Param, * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" @@ -2852,328 +2859,330 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * }, * model?: array|\Symfony\AI\Platform\Capability|Param>, - * }>>, + * class?: string|Param, // The fully qualified class name of the model (must extend Symfony\AI\Platform\Model) // Default: "Symfony\\AI\\Platform\\Model" + * capabilities?: list|\Symfony\AI\Platform\Capability|Param>, + * }>>, * agent?: array, - * }, - * keep_tool_messages?: bool|Param, // Keep tool messages in the conversation history // Default: false - * include_sources?: bool|Param, // Include sources exposed by tools as part of the tool result metadata // Default: false - * fault_tolerant_toolbox?: bool|Param, // Continue the agent run even if a tool call fails // Default: true - * speech?: bool|array{ // Speech (TTS/STT) decorator configuration - * enabled?: bool|Param, // Default: true - * text_to_speech_platform?: string|Param, // Service name of the TTS platform (e.g. ai.platform.elevenlabs). // Default: null - * speech_to_text_platform?: string|Param, // Service name of the STT platform (e.g. ai.platform.openai). // Default: null - * tts_model?: string|Param, // Text-to-speech model name // Default: null - * tts_options?: mixed, // Provider-specific TTS options // Default: [] - * stt_model?: string|Param, // Speech-to-text model name // Default: null - * stt_options?: mixed, // Provider-specific STT options // Default: [] - * }, - * }>, + * platform?: string|Param, // Service name of platform // Default: "Symfony\\AI\\Platform\\PlatformInterface" + * model?: mixed, + * memory?: mixed, // Memory configuration: string for static memory, or array with "service" key for service reference // Default: null + * prompt?: string|array{ // The system prompt configuration + * text?: string|Param, // The system prompt text + * file?: string|Param, // Path to file containing the system prompt + * include_tools?: bool|Param, // Include tool definitions at the end of the system prompt // Default: false + * enable_translation?: bool|Param, // Enable translation for the system prompt // Default: false + * translation_domain?: string|Param, // The translation domain for the system prompt // Default: null + * }, + * tools?: bool|array{ + * enabled?: bool|Param, // Default: true + * services?: list, + * }, + * keep_tool_messages?: bool|Param, // Keep tool messages in the conversation history // Default: false + * include_sources?: bool|Param, // Include sources exposed by tools as part of the tool result metadata // Default: false + * fault_tolerant_toolbox?: bool|Param, // Continue the agent run even if a tool call fails // Default: true + * speech?: bool|array{ // Speech (TTS/STT) decorator configuration + * enabled?: bool|Param, // Default: true + * text_to_speech_platform?: string|Param, // Service name of the TTS platform (e.g. ai.platform.elevenlabs). // Default: null + * speech_to_text_platform?: string|Param, // Service name of the STT platform (e.g. ai.platform.openai). // Default: null + * tts_model?: string|Param, // Text-to-speech model name // Default: null + * tts_options?: mixed, // Provider-specific TTS options // Default: [] + * stt_model?: string|Param, // Speech-to-text model name // Default: null + * stt_options?: mixed, // Provider-specific STT options // Default: [] + * }, + * }>, * multi_agent?: array>, - * fallback?: string|Param, // Service ID of the fallback agent for unmatched requests - * }>, + * orchestrator?: string|Param, // Service ID of the orchestrator agent + * handoffs?: array>, + * fallback?: string|Param, // Service ID of the fallback agent for unmatched requests + * }>, * store?: array{ * azuresearch?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * api_version?: string|Param, + * index_name?: string|Param, // The name of the store will be used if the "index_name" option is not set + * http_client?: string|Param, // Default: "http_client" + * vector_field?: string|Param, // Default: "vector" + * }>, * cache?: array, + * service?: string|Param, // Default: "cache.app" + * cache_key?: string|Param, // The name of the store will be used if the key is not set. + * strategy?: string|Param, // Default: "cosine" + * }>, * chromadb?: array, + * client?: string|Param, // Default: "Codewithkyrian\\ChromaDB\\Client" + * collection?: string|Param, + * }>, * clickhouse?: array, + * dsn?: string|Param, + * http_client?: string|Param, + * database?: string|Param, + * table?: string|Param, + * }>, * cloudflare?: array, + * account_id?: string|Param, + * api_key?: string|Param, + * index_name?: string|Param, + * dimensions?: int|Param, // Default: 1536 + * metric?: string|Param, // Default: "cosine" + * endpoint?: string|Param, + * }>, * elasticsearch?: array, + * endpoint?: string|Param, + * index_name?: string|Param, + * vectors_field?: string|Param, // Default: "_vectors" + * dimensions?: int|Param, // Default: 1536 + * similarity?: string|Param, // Default: "cosine" + * http_client?: string|Param, // Default: "http_client" + * }>, * manticoresearch?: array, + * endpoint?: string|Param, + * table?: string|Param, + * field?: string|Param, // Default: "_vectors" + * type?: string|Param, // Default: "hnsw" + * similarity?: string|Param, // Default: "cosine" + * dimensions?: int|Param, // Default: 1536 + * quantization?: string|Param, + * }>, * mariadb?: array, + * connection?: string|Param, + * table_name?: string|Param, + * index_name?: string|Param, + * vector_field_name?: string|Param, + * setup_options?: array{ + * dimensions?: int|Param, + * }, + * distance?: "cosine"|"euclidean"|"distance"|Param, // Distance metric to use for vector similarity search // Default: "euclidean" + * }>, * meilisearch?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * index_name?: string|Param, + * http_client?: string|Param, // Default: "http_client" + * embedder?: string|Param, // Default: "default" + * vector_field?: string|Param, // Default: "_vectors" + * dimensions?: int|Param, // Default: 1536 + * semantic_ratio?: float|Param, // The ratio between semantic (vector) and full-text search (0.0 to 1.0). Default: 1.0 (100% semantic) // Default: 1.0 + * }>, * memory?: array, + * strategy?: string|Param, + * }>, * milvus?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * database?: string|Param, + * collection?: string|Param, + * vector_field?: string|Param, // Default: "_vectors" + * dimensions?: int|Param, // Default: 1536 + * metric_type?: string|Param, // Default: "COSINE" + * }>, * mongodb?: array, + * client?: string|Param, // Default: "MongoDB\\Client" + * database?: string|Param, + * collection?: string|Param, + * index_name?: string|Param, + * vector_field?: string|Param, // Default: "vector" + * bulk_write?: bool|Param, // Default: false + * setup_options?: array{ + * fields?: mixed, // Default: [] + * }, + * }>, * neo4j?: array, + * endpoint?: string|Param, + * username?: string|Param, + * password?: string|Param, + * database?: string|Param, + * vector_index_name?: string|Param, + * node_name?: string|Param, + * vector_field?: string|Param, // Default: "embeddings" + * dimensions?: int|Param, // Default: 1536 + * distance?: string|Param, // Default: "cosine" + * quantization?: bool|Param, + * }>, * opensearch?: array, + * endpoint?: string|Param, + * index_name?: string|Param, + * vectors_field?: string|Param, // Default: "_vectors" + * dimensions?: int|Param, // Default: 1536 + * space_type?: string|Param, // Default: "l2" + * http_client?: string|Param, // Default: "http_client" + * }>, * pinecone?: array, - * top_k?: int|Param, - * }>, + * client?: string|Param, // Default: "Probots\\Pinecone\\Client" + * index_name?: string|Param, + * namespace?: string|Param, + * filter?: list, + * top_k?: int|Param, + * }>, * postgres?: array, + * dsn?: string|Param, + * username?: string|Param, + * password?: string|Param, + * table_name?: string|Param, + * vector_field?: string|Param, // Default: "embedding" + * distance?: "cosine"|"inner_product"|"l1"|"l2"|Param, // Distance metric to use for vector similarity search // Default: "l2" + * lang?: string|Param, // Default: "english" + * dbal_connection?: string|Param, + * setup_options?: array{ + * vector_type?: string|Param, // Default: "vector" + * vector_size?: int|Param, // Default: 1536 + * index_method?: string|Param, // Default: "ivfflat" + * index_opclass?: string|Param, // Default: "vector_cosine_ops" + * }, + * }>, * qdrant?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * collection_name?: string|Param, // The name of the store will be used if the "collection_name" is not set + * http_client?: string|Param, // Default: "http_client" + * dimensions?: int|Param, // Default: 1536 + * distance?: string|Param, // Default: "Cosine" + * async?: bool|Param, // Default: false + * }>, * redis?: array, + * connection_parameters?: mixed, // see https://github.com/phpredis/phpredis?tab=readme-ov-file#example-1 + * client?: string|Param, // a service id of a Redis client + * index_name?: string|Param, + * key_prefix?: string|Param, // Default: "vector:" + * distance?: "COSINE"|"L2"|"IP"|Param, // Distance metric to use for vector similarity search // Default: "COSINE" + * }>, * s3vectors?: array, - * vector_bucket_name?: string|Param, - * index_name?: string|Param, - * filter?: array, - * top_k?: int|Param, // Default number of results to return // Default: 3 - * }>, + * client?: string|Param, // Service reference to an existing S3VectorsClient + * configuration?: array, + * vector_bucket_name?: string|Param, + * index_name?: string|Param, + * filter?: array, + * top_k?: int|Param, // Default number of results to return // Default: 3 + * }>, * sqlite?: array, + * dsn?: string|Param, + * connection?: string|Param, + * table_name?: string|Param, + * strategy?: string|Param, + * vec?: bool|Param, // Default: false + * distance?: "cosine"|"L2"|Param, // Default: "cosine" + * vector_dimension?: int|Param, // Default: 1536 + * }>, * supabase?: array, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * url?: string|Param, + * api_key?: string|Param, + * table?: string|Param, + * vector_field?: string|Param, // Default: "embedding" + * vector_dimension?: int|Param, // Default: 1536 + * function_name?: string|Param, // Default: "match_documents" + * }>, * surrealdb?: array, + * endpoint?: string|Param, + * username?: string|Param, + * password?: string|Param, + * namespace?: string|Param, + * database?: string|Param, + * table?: string|Param, + * vector_field?: string|Param, // Default: "_vectors" + * strategy?: string|Param, // Default: "cosine" + * dimensions?: int|Param, // Default: 1536 + * namespaced_user?: bool|Param, + * }>, * typesense?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * collection?: string|Param, + * vector_field?: string|Param, // Default: "_vectors" + * dimensions?: int|Param, // Default: 1536 + * }>, * weaviate?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * http_client?: string|Param, // Default: "http_client" + * collection?: string|Param, // The name of the store will be used if the "collection" is not set + * }>, * vektor?: array, + * storage_path?: string|Param, // Default: "%kernel.project_dir%/var/share" + * dimensions?: int|Param, // Default: 1536 + * }>, * }, * message_store?: array{ * cache?: array, + * service?: string|Param, // Default: "cache.app" + * key?: string|Param, // The name of the message store will be used if the key is not set + * ttl?: int|Param, + * }>, * cloudflare?: array, + * account_id?: string|Param, + * api_key?: string|Param, + * namespace?: string|Param, + * endpoint_url?: string|Param, // If the version of the Cloudflare API is updated, use this key to support it. + * }>, * doctrine?: array{ * dbal?: array, + * connection?: string|Param, + * table_name?: string|Param, // The name of the message store will be used if the table_name is not set + * }>, * }, * meilisearch?: array, + * endpoint?: string|Param, + * api_key?: string|Param, + * index_name?: string|Param, + * }>, * memory?: array, + * identifier?: string|Param, + * }>, * mongodb?: array, + * client?: string|Param, // Default: "MongoDB\\Client" + * database?: string|Param, + * collection?: string|Param, + * }>, * pogocache?: array, + * endpoint?: string|Param, + * password?: string|Param, + * key?: string|Param, + * }>, * redis?: array, + * connection_parameters?: mixed, // see https://github.com/phpredis/phpredis?tab=readme-ov-file#example-1 + * client?: string|Param, // a service id of a Redis client + * endpoint?: string|Param, + * index_name?: string|Param, + * }>, * session?: array, + * identifier?: string|Param, + * }>, * surrealdb?: array, + * endpoint?: string|Param, + * username?: string|Param, + * password?: string|Param, + * namespace?: string|Param, + * database?: string|Param, + * table?: string|Param, + * namespaced_user?: bool|Param, // Using a namespaced user is a good practice to prevent any undesired access to a specific table, see https://surrealdb.com/docs/surrealdb/reference-guide/security-best-practices + * }>, * }, * chat?: array, + * agent?: string|Param, + * message_store?: string|Param, + * }>, * vectorizer?: array, + * platform?: string|Param, // Service name of platform // Default: "Symfony\\AI\\Platform\\PlatformInterface" + * model?: mixed, + * }>, * indexer?: array, - * filters?: list, - * vectorizer?: scalar|Param|null, // Service name of vectorizer // Default: "Symfony\\AI\\Store\\Document\\VectorizerInterface" - * store?: string|Param, // Service name of store // Default: "Symfony\\AI\\Store\\StoreInterface" - * }>, + * loader?: string|Param, // Service name of loader // Default: null + * source?: mixed, // Source identifier (file path, URL, etc.) or array of sources // Default: null + * transformers?: list, + * filters?: list, + * vectorizer?: scalar|Param|null, // Service name of vectorizer // Default: "Symfony\\AI\\Store\\Document\\VectorizerInterface" + * store?: string|Param, // Service name of store // Default: "Symfony\\AI\\Store\\StoreInterface" + * }>, * retriever?: array, + * vectorizer?: scalar|Param|null, // Service name of vectorizer // Default: "Symfony\\AI\\Store\\Document\\VectorizerInterface" + * store?: string|Param, // Service name of store // Default: "Symfony\\AI\\Store\\StoreInterface" + * }>, * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index 223771c0..4b5e2b22 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -75,6 +75,15 @@ the parts you want to update. In the bulk actions dropdown select "Bulk info pro You will be redirected to a page, where you can select how part fields should be mapped to info provider fields, and the results will be shown. +## Browser plugin +There is a browser plugin available for [Chrome](https://chromewebstore.google.com/detail/part-db-page-submitter/bckkfkpidiiibmjdhjakleoagjmepioi) and [Firefox](https://addons.mozilla.org/de/firefox/addon/part-db-page-submitter/) +that allows to submit a website from your browser with one click to Part-DB, which then utilizes the Generic Web URL or the AI Web Provider to extract the part information from the page and pre-fill the part creation form. +The advantage is that it also works for pages behind logins, CAPTCHAs, or bot-blocking sites, as the plugin sends the already loaded page HTML to Part-DB. +The plugin is open source and available on [GitHub](https://github.com/Part-DB/browser-plugin). + +To use it install it in your browser, enable one or more of the web page providers in Part-DB and allow the plugin support +in Part-DB settings. After that you can submit any product page to Part-DB with one click and the part creation form will be pre-filled with the information from the page. + ## Data providers The system tries to be as flexible as possible, so many different information sources can be used. diff --git a/package.json b/package.json index 99636d37..f846f1d1 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,8 @@ "exports-loader": "^5.0.0", "json-formatter-js": "^2.3.4", "jszip": "^3.2.0", - "katex": "^0.16.0", - "marked": "^17.0.1", + "katex": "^0.17.0", + "marked": "^18.0.0", "marked-gfm-heading-id": "^4.1.1", "marked-mangle": "^1.0.1", "pdfmake": "^0.3.7", diff --git a/public/kicad/footprints.txt b/public/kicad/footprints.txt index 779a7751..5f691a41 100644 --- a/public/kicad/footprints.txt +++ b/public/kicad/footprints.txt @@ -1,4 +1,4 @@ -# Generated on Mon May 25 06:41:46 UTC 2026 +# Generated on Mon Jun 1 07:07:44 UTC 2026 # This file contains all footprints available in the offical KiCAD library Audio_Module:Reverb_BTDR-1H Audio_Module:Reverb_BTDR-1V diff --git a/public/kicad/symbols.txt b/public/kicad/symbols.txt index 46a15ee1..7b4fde9c 100644 --- a/public/kicad/symbols.txt +++ b/public/kicad/symbols.txt @@ -1,4 +1,4 @@ -# Generated on Mon May 25 06:42:27 UTC 2026 +# Generated on Mon Jun 1 07:08:24 UTC 2026 # This file contains all symbols available in the offical KiCAD library 4xxx:14528 4xxx:14529 @@ -2255,11 +2255,11 @@ Battery_Management:BQ76200PW Battery_Management:BQ76920PW Battery_Management:BQ76930DBT Battery_Management:BQ76940DBT -Battery_Management:BQ7695201PFBR -Battery_Management:BQ7695202PFBR -Battery_Management:BQ7695203PFBR -Battery_Management:BQ7695204PFBR -Battery_Management:BQ76952PFBR +Battery_Management:BQ7695201PFB +Battery_Management:BQ7695202PFB +Battery_Management:BQ7695203PFB +Battery_Management:BQ7695204PFB +Battery_Management:BQ76952PFB Battery_Management:BQ78350DBT Battery_Management:BQ78350DBT-R1 Battery_Management:CN3063 @@ -16037,6 +16037,7 @@ Power_Supervisor:TPS3831 Power_Supervisor:TPS3839DBZ Power_Supervisor:TPS3839DQN RF:0900PC15J0013 +RF:AD8302xRU RF:ADC-10-1R RF:ADCH-80 RF:ADCH-80A diff --git a/src/Controller/BrowserPluginController.php b/src/Controller/BrowserPluginController.php new file mode 100644 index 00000000..1bb95787 --- /dev/null +++ b/src/Controller/BrowserPluginController.php @@ -0,0 +1,139 @@ +. + */ + +declare(strict_types=1); + +namespace App\Controller; + +use App\Entity\UserSystem\User; +use App\Services\InfoProviderSystem\ProviderRegistry; +use App\Services\InfoProviderSystem\SubmittedPageStorage; +use App\Services\InfoProviderSystem\DTOs\BrowserSubmittedPage; +use App\Settings\InfoProviderSystem\BrowserPluginSettings; +use App\Settings\SystemSettings\CustomizationSettings; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * Provides the endpoint used by browser extensions to submit the current page's HTML to Part-DB, + * so that info providers can use it instead of fetching the URL themselves. + */ +#[Route('/tools/info_providers')] +class BrowserPluginController extends AbstractController +{ + public function __construct( + private readonly SubmittedPageStorage $browserHtmlStorage, + private readonly ProviderRegistry $providerRegistry, + private readonly CustomizationSettings $customizationSettings, + private readonly BrowserPluginSettings $browserPluginSettings, + ) { + } + + private const URL_PROVIDER_KEYS = ['generic_web', 'ai_web']; + + /** + * Returns instance info for the browser extension: logged-in username, instance name, and active URL providers. + * + * Response: { "username": "admin", "instance_name": "Part-DB", "url_providers": [{"id": "generic_web", "label": "Generic Web URL"}] } + */ + #[Route('/browser_info', name: 'browser_plugin_info', methods: ['GET'])] + public function getInfo(): JsonResponse + { + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + $this->throwIfDisabled(); + + $activeProviders = $this->providerRegistry->getActiveProviders(); + + $urlProviders = []; + foreach (self::URL_PROVIDER_KEYS as $key) { + if (isset($activeProviders[$key])) { + $urlProviders[] = [ + 'id' => $key, + 'label' => $activeProviders[$key]->getProviderInfo()['name'], + ]; + } + } + + $user = $this->getUser(); + if ($user instanceof User) { + $username = $user->getFullName(true); + } else { + $username = $user ? $user->getUserIdentifier() : "unknown"; + } + + return new JsonResponse([ + 'username' => $username, + 'instance_name' => $this->customizationSettings->instanceName, + 'url_providers' => $urlProviders, + ]); + } + + /** + * Accepts a JSON POST body with the HTML of the current page from a browser extension. + * Stores the HTML in the session via BrowserHtmlSessionStorage and returns a redirect URL + * pointing to the standard part-creation flow with use_browser_html=1. + * + * Expected JSON body: { "html": "", "url": "https://example.com/product", "provider": "generic_web" } + * The "provider" field is optional and defaults to "generic_web". Use "ai_web" for the AI extractor. + * Response: { "redirect_url": "https://partdb.example.com/en/part/from_info_provider/generic_web/https%3A%2F%2F.../create?use_browser_html=1&no_cache=1" } + */ + #[Route('/browser_html', name: 'browser_plugin_submit_html', methods: ['POST'])] + public function submitHtml(Request $request, + #[MapRequestPayload] + BrowserSubmittedPage $page + ): JsonResponse + { + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + $this->throwIfDisabled(); + + $payload = $request->getPayload(); + + $provider = $payload->get('provider', null); + + // The maprequestpayload already validates the URL and HTML content: + $token = $this->browserHtmlStorage->store($page); + + if ($provider !== null) { + $redirectUrl = $this->generateUrl('info_providers_create_part', [ + 'providerKey' => $provider, + 'providerId' => $page->url, + 'submitted_page_token' => $token, + ], UrlGeneratorInterface::ABSOLUTE_URL); + } + + return new JsonResponse([ + 'redirect_url' => $redirectUrl ?? null, + ]); + } + + public function throwIfDisabled(): void + { + if (!$this->browserPluginSettings->enabled) { + throw HttpException::fromStatusCode(451, "Browser plugin feature is disabled by the administrator, ask him to enable it in system settings."); + } + } +} diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index 817a6651..28c281d0 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -28,6 +28,7 @@ use App\Entity\Parts\Part; use App\Exceptions\OAuthReconnectRequiredException; use App\Form\InfoProviderSystem\FromURLFormType; use App\Form\InfoProviderSystem\PartSearchType; +use App\Services\InfoProviderSystem\SubmittedPageStorage; use App\Services\InfoProviderSystem\ExistingPartFinder; use App\Services\InfoProviderSystem\CreateFromUrlHelper; use App\Services\InfoProviderSystem\PartInfoRetriever; @@ -62,7 +63,8 @@ class InfoProviderController extends AbstractController private readonly PartInfoRetriever $infoRetriever, private readonly ExistingPartFinder $existingPartFinder, private readonly SettingsManagerInterface $settingsManager, - private readonly SettingsFormFactoryInterface $settingsFormFactory + private readonly SettingsFormFactoryInterface $settingsFormFactory, + private readonly SubmittedPageStorage $browserHtmlStorage, ) { @@ -221,7 +223,7 @@ class InfoProviderController extends AbstractController } #[Route('/from_url', name: 'info_providers_from_url')] - public function fromURL(Request $request, GenericWebProvider $provider, CreateFromUrlHelper $fromUrlHelper): Response + public function fromURL(Request $request, CreateFromUrlHelper $fromUrlHelper): Response { $this->denyAccessUnlessGranted('@info_providers.create_parts'); @@ -242,6 +244,12 @@ class InfoProviderController extends AbstractController $no_cache = $form->get('no_cache')->getData(); $skip_delegation = $form->get('skip_delegation')->getData(); + $submittedPageToken = $request->request->get('submitted_page_token', null); + if ($submittedPageToken !== null && $submittedPageToken !== '') { + $url = $this->browserHtmlStorage->retrieve($submittedPageToken)->url; + } + + try { //It's okay if we use the cached results here, as its just for convenience $searchResult = $this->infoRetriever->searchByKeyword( @@ -249,6 +257,7 @@ class InfoProviderController extends AbstractController providers: [$method], options: [ InfoProviderInterface::OPTION_SKIP_DELEGATION => $skip_delegation, + InfoProviderInterface::OPTION_SUBMITTED_PAGE_TOKEN => $submittedPageToken, ] ); @@ -262,6 +271,7 @@ class InfoProviderController extends AbstractController 'providerId' => $searchResult->provider_id, 'no_cache' => $no_cache ? 1 : null, 'skip_delegation' => $skip_delegation ? 1 : null, + 'submitted_page_token' => $submittedPageToken ?: null, ]); } } catch (ExceptionInterface $e) { @@ -272,6 +282,7 @@ class InfoProviderController extends AbstractController return $this->render('info_providers/from_url/from_url.html.twig', [ 'form' => $form, 'partDetail' => $partDetail, + 'recentBrowserPages' => $this->browserHtmlStorage->getRecentPages(), ]); } diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 735a48f8..c4c0e526 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -328,10 +328,12 @@ final class PartController extends AbstractController //Force info providers to not use cache, when retrieving part details for creating a new part, because otherwise we might end up with outdated information $no_cache = $request->query->getBoolean('no_cache', false); $skip_delegation = $request->query->getBoolean('skip_delegation', false); + $submitted_page_token = $request->query->getString('submitted_page_token'); $dto = $infoRetriever->getDetails($providerKey, $providerId, [ InfoProviderInterface::OPTION_NO_CACHE => $no_cache, InfoProviderInterface::OPTION_SKIP_DELEGATION => $skip_delegation, + InfoProviderInterface::OPTION_SUBMITTED_PAGE_TOKEN => $submitted_page_token, ]); $new_part = $infoRetriever->dtoToPart($dto); diff --git a/src/Controller/SettingsController.php b/src/Controller/SettingsController.php index 5fed1571..c4a52bc7 100644 --- a/src/Controller/SettingsController.php +++ b/src/Controller/SettingsController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Controller; +use Symfony\Component\Form\Extension\Core\Type\ResetType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use App\Settings\AppSettings; use Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface; @@ -50,7 +51,9 @@ class SettingsController extends AbstractController $settings = $this->settingsManager->createTemporaryCopy(AppSettings::class); //Create a form builder for the settings object - $builder = $this->settingsFormFactory->createSettingsFormBuilder($settings); + $builder = $this->settingsFormFactory->createSettingsFormBuilder($settings, formOptions: [ + 'warn_on_unsaved_changes' => true, + ]); //Add a submit button to the form $builder->add('submit', SubmitType::class, ['label' => 'save']); diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 2d5c4ebc..b5beeca0 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -37,6 +37,7 @@ use App\Services\EntityURLGenerator; use App\Services\Formatters\AmountFormatter; use App\Services\Formatters\MoneyFormatter; use App\Services\ProjectSystem\ProjectBuildHelper; +use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Query; @@ -93,14 +94,14 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface return htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit())); }, ]) - ->add('partId', TextColumn::class, [ - 'label' => $this->translator->trans('project.bom.part_id'), - 'visible' => true, - 'orderField' => 'part.id', - 'render' => function ($value, ProjectBOMEntry $context) { - return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : ''; - }, - ]) + ->add('partId', TextColumn::class, [ + 'label' => $this->translator->trans('project.bom.part_id'), + 'visible' => true, + 'orderField' => 'part.id', + 'render' => function ($value, ProjectBOMEntry $context) { + return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : ''; + }, + ]) ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), 'orderField' => 'NATSORT(part.name)', @@ -161,7 +162,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('part.table.manufacturingStatus'), 'data' => static fn(ProjectBOMEntry $context): ?ManufacturingStatus => $context->getPart()?->getManufacturingStatus(), 'orderField' => 'part.manufacturing_status', - 'class' => ManufacturingStatus::class, + 'class' => ManufacturingStatus::class, 'render' => function (?ManufacturingStatus $status, ProjectBOMEntry $context): string { if ($status === null) { return ''; @@ -212,7 +213,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { $price = $this->projectBuildHelper->getEntryUnitPrice($context); - return $this->moneyFormatter->format($price->toScale(2, RoundingMode::UP)->toFloat(), null, 2, true); + return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true); }, ]) ->add('ext_price', TextColumn::class, [ @@ -221,7 +222,8 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'render' => function ($value, ProjectBOMEntry $context) { $price = $this->projectBuildHelper->getEntryUnitPrice($context); return $this->moneyFormatter->format( - $price->multipliedBy($context->getQuantity())->toScale(2, RoundingMode::UP)->toFloat(), + $price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity())) + ->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true diff --git a/src/Doctrine/Types/BigDecimalType.php b/src/Doctrine/Types/BigDecimalType.php index a9f796dd..2712cce8 100644 --- a/src/Doctrine/Types/BigDecimalType.php +++ b/src/Doctrine/Types/BigDecimalType.php @@ -44,7 +44,7 @@ class BigDecimalType extends Type - return BigDecimal::of($value); + return BigDecimal::of(is_float($value) ? BigDecimal::fromFloatShortest($value) : $value); } /** diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index ce20caf8..4a811aa0 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -204,7 +204,7 @@ class Currency extends AbstractStructuralDBElement return null; } - return BigDecimal::one()->dividedBy($tmp, $tmp->getScale(), RoundingMode::HALF_UP); + return BigDecimal::one()->dividedBy($tmp, $tmp->getScale(), RoundingMode::HalfUp); } /** diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 553b07a3..58e02c64 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -195,10 +195,10 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[SerializedName('price_per_unit')] public function getPricePerUnit(float|string|BigDecimal $multiplier = 1.0): BigDecimal { - $tmp = BigDecimal::of($multiplier); + $tmp = is_float($multiplier) ? BigDecimal::fromFloatShortest($multiplier) : BigDecimal::of($multiplier); $tmp = $tmp->multipliedBy($this->price); - return $tmp->dividedBy($this->price_related_quantity, static::PRICE_PRECISION, RoundingMode::HALF_UP); + return $tmp->dividedBy(BigDecimal::fromFloatShortest($this->price_related_quantity), static::PRICE_PRECISION, RoundingMode::HalfUp); } /** @@ -317,7 +317,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface */ public function setPrice(BigDecimal $new_price): self { - $tmp = $new_price->toScale(self::PRICE_PRECISION, RoundingMode::HALF_UP); + $tmp = $new_price->toScale(self::PRICE_PRECISION, RoundingMode::HalfUp); //Only change the object, if the value changes, so that doctrine does not detect it as changed. if ((string) $tmp !== (string) $this->price) { $this->price = $tmp; diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index bf005882..54cb0406 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -57,6 +57,10 @@ class BaseEntityAdminForm extends AbstractType $resolver->setRequired('attachment_class'); $resolver->setRequired('parameter_class'); $resolver->setAllowedTypes('parameter_class', ['string', 'null']); + + $resolver->setDefaults([ + 'warn_on_unsaved_changes' => true, + ]); } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Extension/UnsavedChangesExtension.php b/src/Form/Extension/UnsavedChangesExtension.php new file mode 100644 index 00000000..1187eb19 --- /dev/null +++ b/src/Form/Extension/UnsavedChangesExtension.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\Extension; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Adds a `warn_on_unsaved_changes` option to any root form. When set to true, the Stimulus + * `common--dirty-form` controller attributes are merged into the form element's HTML + * attributes, enabling unsaved-change detection without any template boilerplate. + * + * Usage in a form type: + * + * public function configureOptions(OptionsResolver $resolver): void + * { + * $resolver->setDefaults(['warn_on_unsaved_changes' => true]); + * } + * + * Or per-instance from a controller: + * + * $form = $this->createForm(MyFormType::class, $data, ['warn_on_unsaved_changes' => true]); + */ +class UnsavedChangesExtension extends AbstractTypeExtension +{ + public function __construct(private readonly TranslatorInterface $translator) + { + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefault('warn_on_unsaved_changes', false); + $resolver->setAllowedTypes('warn_on_unsaved_changes', 'bool'); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if (!$options['warn_on_unsaved_changes'] || $view->parent !== null) { + return; + } + + $extraAttr = [ + 'data-controller' => 'common--dirty-form', + 'data-common--dirty-form-confirm-title-value' => $this->translator->trans('form.dirty_form.unsaved_changes.title'), + 'data-common--dirty-form-confirm-message-value' => $this->translator->trans('form.dirty_form.unsaved_changes.message'), + ]; + + // Merge data-action so existing actions on the form element are preserved. + $existingAction = $view->vars['attr']['data-action'] ?? ''; + $dirtyActions = 'submit->common--dirty-form#submit reset->common--dirty-form#resetDirtyState'; + $extraAttr['data-action'] = $existingAction !== '' ? $existingAction . ' ' . $dirtyActions : $dirtyActions; + + $view->vars['attr'] = array_merge($view->vars['attr'], $extraAttr); + } +} diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 6b929486..a31f2469 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -353,6 +353,7 @@ class PartBaseType extends AbstractType $resolver->setDefaults([ 'data_class' => Part::class, 'info_provider_dto' => null, + 'warn_on_unsaved_changes' => true, ]); $resolver->setAllowedTypes('info_provider_dto', [PartDetailDTO::class, 'null']); diff --git a/src/Form/Type/BigDecimalMoneyType.php b/src/Form/Type/BigDecimalMoneyType.php index 189416ff..1950391c 100644 --- a/src/Form/Type/BigDecimalMoneyType.php +++ b/src/Form/Type/BigDecimalMoneyType.php @@ -59,6 +59,16 @@ class BigDecimalMoneyType extends AbstractType implements DataTransformerInterfa return null; } - return BigDecimal::of($value); + if ($value instanceof BigDecimal) { + return $value; + } + if (is_float($value)) { + return BigDecimal::fromFloatShortest($value); + } + if (is_string($value)) { + return BigDecimal::of($value); + } + + throw new \InvalidArgumentException(sprintf('Expected a string, float or BigDecimal, got %s', get_debug_type($value))); } } diff --git a/src/Form/Type/BigDecimalNumberType.php b/src/Form/Type/BigDecimalNumberType.php index c225f0a4..04e8e655 100644 --- a/src/Form/Type/BigDecimalNumberType.php +++ b/src/Form/Type/BigDecimalNumberType.php @@ -59,6 +59,17 @@ class BigDecimalNumberType extends AbstractType implements DataTransformerInterf return null; } - return BigDecimal::of($value); + if ($value instanceof BigDecimal) { + return $value; + } + if (is_float($value)) { + return BigDecimal::fromFloatShortest($value); + } + if (is_string($value)) { + return BigDecimal::of($value); + } + + throw new \InvalidArgumentException(sprintf('Expected a string, float or BigDecimal, got %s', get_debug_type($value))); } + } diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 457a6e0b..6331dfb7 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -59,6 +59,10 @@ class UserAdminForm extends AbstractType $resolver->setDefault('parameter_class', false); $resolver->setDefault('validation_groups', ['Default', 'permissions:edit']); + + $resolver->setDefaults([ + 'warn_on_unsaved_changes' => true, + ]); } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Serializer/APIPlatform/SkippableItemNormalizer.php b/src/Serializer/APIPlatform/SkippableItemNormalizer.php index 5568c4cb..61873615 100644 --- a/src/Serializer/APIPlatform/SkippableItemNormalizer.php +++ b/src/Serializer/APIPlatform/SkippableItemNormalizer.php @@ -23,9 +23,13 @@ declare(strict_types=1); namespace App\Serializer\APIPlatform; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\ItemNotFoundException; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Serializer\ItemNormalizer; use Symfony\Component\DependencyInjection\Attribute\AsDecorator; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerAwareInterface; @@ -35,6 +39,10 @@ use Symfony\Component\Serializer\SerializerInterface; * This class decorates API Platform's ItemNormalizer to allow skipping the normalization process by setting the * DISABLE_ITEM_NORMALIZER context key to true. This is useful for all kind of serialization operations, where the API * Platform subsystem should not be used. + * + * It also works around a bug in API Platform's AbstractItemNormalizer where IRI strings for abstract resource classes + * with a discriminator map fail deserialization when objectToPopulate is null (the discriminator is checked before + * the IRI string check). See: https://github.com/Part-DB/Part-DB-server/issues/1370 */ #[AsDecorator("api_platform.serializer.normalizer.item")] class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface @@ -42,13 +50,44 @@ class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterf public const DISABLE_ITEM_NORMALIZER = 'DISABLE_ITEM_NORMALIZER'; - public function __construct(private readonly ItemNormalizer $inner) - { - + public function __construct( + private readonly ItemNormalizer $inner, + private readonly IriConverterInterface $iriConverter, + ) { } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + // API Platform's AbstractItemNormalizer has a bug: when objectToPopulate is null and data is an IRI + // string, it tries to resolve the discriminator class from [$iri_string] before reaching the IRI + // check (line 271). For abstract resource classes with a discriminator map (e.g. Attachment), this + // fails because the array has no _type key. Fix by resolving IRI strings directly. + // See: https://github.com/Part-DB/Part-DB-server/issues/1370 + if (is_string($data) || (is_array($data) && isset($data['@id']) && is_string($data['@id']))) { + if (is_array($data)) { + $iri = $data['@id']; + } else { + $iri = $data; + } + + try { + return $this->iriConverter->getResourceFromIri($iri, $context + ['fetch_data' => true]); + } catch (ItemNotFoundException $e) { + if (false === ($context['denormalize_throw_on_relation_not_found'] ?? true)) { + return null; + } + if (!isset($context['not_normalizable_value_exceptions'])) { + throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); + } + throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [$type], $context['deserialization_path'] ?? null, true, $e->getCode(), $e); + } catch (InvalidArgumentException $e) { + if (!isset($context['not_normalizable_value_exceptions'])) { + throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $iri), $e->getCode(), $e); + } + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Invalid IRI "%s".', $data), $data, [$type], $context['deserialization_path'] ?? null, true, $e->getCode(), $e); + } + } + return $this->inner->denormalize($data, $type, $format, $context); } @@ -87,4 +126,4 @@ class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterf 'object' => false ]; } -} \ No newline at end of file +} diff --git a/src/Services/Attachments/AttachmentManager.php b/src/Services/Attachments/AttachmentManager.php index 1075141b..c661b0f4 100644 --- a/src/Services/Attachments/AttachmentManager.php +++ b/src/Services/Attachments/AttachmentManager.php @@ -156,8 +156,8 @@ class AttachmentManager //Taken from: https://www.php.net/manual/de/function.filesize.php#106569 and slightly modified $sz = 'BKMGTP'; - $factor = (int) floor((strlen((string) $bytes) - 1) / 3); + $factor = min((int) floor((strlen((string) $bytes) - 1) / 3), strlen($sz) - 1); //Use real (10 based) SI prefixes - return sprintf("%.{$decimals}f", $bytes / 1000 ** $factor).@$sz[$factor]; + return sprintf("%.{$decimals}f", $bytes / 1000 ** $factor).$sz[$factor]; } } diff --git a/src/Services/Formatters/SIFormatter.php b/src/Services/Formatters/SIFormatter.php index b83501fa..1f25dbe6 100644 --- a/src/Services/Formatters/SIFormatter.php +++ b/src/Services/Formatters/SIFormatter.php @@ -59,10 +59,10 @@ class SIFormatter $prefixes_neg = ['', 'm', 'μ', 'n', 'p', 'f', 'a', 'z', 'y']; if ($magnitude >= 0) { - $nearest = (int) floor(abs($magnitude) / 3); + $nearest = min((int) floor(abs($magnitude) / 3), count($prefixes_pos) - 1); $symbol = $prefixes_pos[$nearest]; } else { - $nearest = (int) round(abs($magnitude) / 3); + $nearest = min((int) round(abs($magnitude) / 3), count($prefixes_neg) - 1); $symbol = $prefixes_neg[$nearest]; } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index 64127341..08b1c301 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -89,7 +89,7 @@ trait PKImportHelperTrait //Use mime type to determine the extension like PartKeepr does in legacy implementation (just use the second part of the mime type) //See UploadedFile.php:291 in PartKeepr (https://github.com/partkeepr/PartKeepr/blob/f6176c3354b24fa39ac8bc4328ee0df91de3d5b6/src/PartKeepr/UploadedFileBundle/Entity/UploadedFile.php#L291) if (!empty ($attachment_row['mimetype'])) { - $attachment_row['extension'] = explode('/', (string) $attachment_row['mimetype'])[1]; + $attachment_row['extension'] = explode('/', (string) $attachment_row['mimetype'])[1] ?? ''; } else { //If the mime type is empty, we use the original extension $attachment_row['extension'] = pathinfo((string) $attachment_row['originalname'], PATHINFO_EXTENSION); diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index cab5a49b..e63ec7f1 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -286,7 +286,7 @@ class PKPartImporter //Partkeepr stores the price per item, we need to convert it to the price per packaging unit $price_per_item = BigDecimal::of($partdistributor['price']); $packaging_unit = (float) ($partdistributor['packagingUnit'] ?? 1); - $pricedetail->setPrice($price_per_item->multipliedBy($packaging_unit)); + $pricedetail->setPrice($price_per_item->multipliedBy(BigDecimal::fromFloatShortest($packaging_unit))); $pricedetail->setPriceRelatedQuantity($packaging_unit); //We have to set the minimum discount quantity to the packaging unit (PartKeepr does not know this concept) //But in Part-DB the minimum discount qty have to be unique across a orderdetail diff --git a/src/Services/InfoProviderSystem/DTOs/BrowserSubmittedPage.php b/src/Services/InfoProviderSystem/DTOs/BrowserSubmittedPage.php new file mode 100644 index 00000000..0f4fbf5f --- /dev/null +++ b/src/Services/InfoProviderSystem/DTOs/BrowserSubmittedPage.php @@ -0,0 +1,50 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\DTOs; + +use Symfony\Component\Validator\Constraints as Assert; + +/** + * Represents a webpage submitted by the browser extension, held temporarily in the application cache. + */ +final readonly class BrowserSubmittedPage +{ + /** + * @var string A unique token for this page, derived from the URL and HTML content. Used to identify the page in the cache without storing the full HTML in the session. + */ + public string $token; + + public function __construct( + #[Assert\Url()] + #[Assert\NotBlank] + public string $url, + #[Assert\NotBlank] + #[Assert\Length(max: 5 * 1024 * 1024)] // Limit to 5 MB to prevent abuse + public string $html, + #[Assert\NotBlank] + public string $title, + public \DateTimeImmutable $submittedAt = new \DateTimeImmutable(), + ) { + $this->token = hash('xxh3', $url . '|' . $html); + } +} diff --git a/src/Services/InfoProviderSystem/PartInfoRetriever.php b/src/Services/InfoProviderSystem/PartInfoRetriever.php index 6c10f10e..f5ff144d 100644 --- a/src/Services/InfoProviderSystem/PartInfoRetriever.php +++ b/src/Services/InfoProviderSystem/PartInfoRetriever.php @@ -175,15 +175,15 @@ final class PartInfoRetriever */ public function dtoToPart(PartDetailDTO $search_result): Part { - return $this->createPart($search_result->provider_key, $search_result->provider_id); + return $this->dto_to_entity_converter->convertPart($search_result); } /** * Use the given details to create a part entity */ - public function createPart(string $provider_key, string $part_id): Part + public function createPart(string $provider_key, string $part_id, array $options): Part { - $details = $this->getDetails($provider_key, $part_id); + $details = $this->getDetails($provider_key, $part_id, $options); return $this->dto_to_entity_converter->convertPart($details); } diff --git a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php index 79f07be8..6539e69b 100644 --- a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php +++ b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php @@ -27,12 +27,11 @@ namespace App\Services\InfoProviderSystem\Providers; use App\Exceptions\ProviderIDNotSupportedException; use App\Helpers\RandomizeUseragentHttpClient; use App\Services\AI\AIPlatformRegistry; +use App\Services\InfoProviderSystem\SubmittedPageStorage; use App\Services\InfoProviderSystem\CreateFromUrlHelper; use App\Services\InfoProviderSystem\DTOJsonSchemaConverter; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Settings\InfoProviderSystem\AIExtractorSettings; -use Brick\Schema\SchemaReader; -use Imagine\Image\Format; use Jkphl\Micrometa; use League\HTMLToMarkdown\HtmlConverter; use Psr\Cache\CacheItemPoolInterface; @@ -62,6 +61,7 @@ final class AIWebProvider implements InfoProviderInterface private readonly DTOJsonSchemaConverter $jsonSchemaConverter, private readonly CacheItemPoolInterface $partInfoCache, private readonly CreateFromUrlHelper $createFromUrlHelper, + private readonly SubmittedPageStorage $browserHtmlStorage, ) { //Use NoPrivateNetworkHttpClient to prevent SSRF vulnerabilities, and RandomizeUseragentHttpClient to make it harder for servers to block us $this->httpClient = (new RandomizeUseragentHttpClient(new NoPrivateNetworkHttpClient($httpClient)))->withOptions( @@ -142,9 +142,17 @@ final class AIWebProvider implements InfoProviderInterface return $cacheItem->get(); } - // Fetch HTML content - $response = $this->httpClient->request('GET', $url); - $html = $response->getContent(); + // Use pre-fetched browser HTML if the option is set and a stored page is available for this URL + $html = null; + if (($token = ($options[self::OPTION_SUBMITTED_PAGE_TOKEN] ?? '')) !== '') { + $html = $this->browserHtmlStorage->retrieve($token)?->html; + } + + //Otherwise fetch it ourselves. + if ($html === null) { + $response = $this->httpClient->request('GET', $url); + $html = $response->getContent(); + } //Convert html to markdown, to provide a cleaner input to the LLM. $markdown = $this->htmlToMarkdown($html, $url); @@ -176,9 +184,20 @@ final class AIWebProvider implements InfoProviderInterface */ private function extractStructuredData(string $html, string $url): string { - //Only parse microdata, json-ld and rdfa, as they are the most common formats for structured data on product pages. Links and microformat only create clutter for the LLM - $micrometa = new Micrometa\Ports\Parser(Micrometa\Ports\Format::JSON_LD | Micrometa\Ports\Format::MICRODATA | Micrometa\Ports\Format::RDFA_LITE); - $items = $micrometa($url, $html); + try { + //Only parse microdata, json-ld and rdfa, as they are the most common formats for structured data on product pages. Links and microformat only create clutter for the LLM + $micrometa = new Micrometa\Ports\Parser(Micrometa\Ports\Format::JSON_LD | Micrometa\Ports\Format::MICRODATA | Micrometa\Ports\Format::RDFA_LITE); + $items = $micrometa($url, $html); + } catch (\RuntimeException $exception) { + //If parsing fails, try again without rdfa, as it seems to cause problems on pages like ebay + try { + $micrometa = new Micrometa\Ports\Parser(Micrometa\Ports\Format::JSON_LD | Micrometa\Ports\Format::MICRODATA); + $items = $micrometa($url, $html); + } catch (\RuntimeException $exception) { + //If it still fails, return empty structured data + return '{}'; + } + } return json_encode($items->toObject(), JSON_THROW_ON_ERROR); } diff --git a/src/Services/InfoProviderSystem/Providers/GenericWebProvider.php b/src/Services/InfoProviderSystem/Providers/GenericWebProvider.php index 06a9d4c1..45777f9e 100644 --- a/src/Services/InfoProviderSystem/Providers/GenericWebProvider.php +++ b/src/Services/InfoProviderSystem/Providers/GenericWebProvider.php @@ -25,6 +25,7 @@ namespace App\Services\InfoProviderSystem\Providers; use App\Exceptions\ProviderIDNotSupportedException; use App\Helpers\RandomizeUseragentHttpClient; +use App\Services\InfoProviderSystem\SubmittedPageStorage; use App\Services\InfoProviderSystem\CreateFromUrlHelper; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; @@ -57,6 +58,7 @@ class GenericWebProvider implements InfoProviderInterface public function __construct(HttpClientInterface $httpClient, private readonly GenericWebProviderSettings $settings, private readonly CreateFromUrlHelper $createFromUrlHelper, + private readonly SubmittedPageStorage $browserHtmlStorage, ) { //Use NoPrivateNetworkHttpClient to prevent SSRF vulnerabilities, and RandomizeUseragentHttpClient to make it harder for servers to block us @@ -294,9 +296,17 @@ class GenericWebProvider implements InfoProviderInterface } } - //Try to get the webpage content - $response = $this->httpClient->request('GET', $url); - $content = $response->getContent(); + // Use pre-fetched browser HTML if the option is set and a stored page is available for this URL + $content = null; + if (($token = ($options[self::OPTION_SUBMITTED_PAGE_TOKEN] ?? '')) !== '') { + $content = $this->browserHtmlStorage->retrieve($token)?->html; + } + + //Otherwise, fetch the page content ourselves + if ($content === null) { + $response = $this->httpClient->request('GET', $url); + $content = $response->getContent(); + } $dom = new Crawler($content); diff --git a/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php b/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php index a6e073a5..d3895795 100644 --- a/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php +++ b/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php @@ -30,6 +30,7 @@ interface InfoProviderInterface { public const OPTION_NO_CACHE = 'no_cache'; // if set to true, the provider should not use any cache and retrieve fresh data from the source public const OPTION_SKIP_DELEGATION = 'skip_delegation'; // if set to true, the provider should not delegate the request to other providers, even if it supports delegation. + public const OPTION_SUBMITTED_PAGE_TOKEN = 'submitted_page_token'; // if set to a non-empty string, the provider should use the browser-submitted page with the given token (and retrieve it from BrowserHtmlSessionStorage) /** * Get information about this provider diff --git a/src/Services/InfoProviderSystem/SubmittedPageStorage.php b/src/Services/InfoProviderSystem/SubmittedPageStorage.php new file mode 100644 index 00000000..5e623f57 --- /dev/null +++ b/src/Services/InfoProviderSystem/SubmittedPageStorage.php @@ -0,0 +1,131 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem; + +use App\Services\InfoProviderSystem\DTOs\BrowserSubmittedPage; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Stores browser-submitted pages for the browser extension feature. + * + * Each page is stored as a {@see BrowserSubmittedPage} DTO in the application cache with a short TTL. + * The session holds only a compact list of recently submitted URLs so that pages can be listed + * without bloating the session with HTML content. + */ +class SubmittedPageStorage +{ + private const CACHE_KEY_PREFIX = 'browser_plugin_html_'; + private const CACHE_TTL = 1800; // 30 minutes + private const SESSION_KEY = 'browser_plugin_recent_urls'; + private const MAX_RECENT = 10; + + public function __construct( + private readonly RequestStack $requestStack, + private readonly CacheItemPoolInterface $cache, + ) { + } + + /** + * Stores a submitted page in the cache and records its URL in the session's recent list. + * @return string The token under which the page was stored, derived from the URL and HTML. This token is used to retrieve the page later. It is the same value as $page->token. + */ + public function store(BrowserSubmittedPage $page): string + { + $item = $this->cache->getItem($this->cacheKey($page)); + $item->set($page); + $item->expiresAfter(self::CACHE_TTL); + $this->cache->save($item); + + $session = $this->requestStack->getSession(); + $tokens = array_values(array_filter( + $session->get(self::SESSION_KEY, []), + static fn(string $u): bool => $u !== $page->token, + )); + array_unshift($tokens, $page->token); + $session->set(self::SESSION_KEY, array_slice($tokens, 0, self::MAX_RECENT)); + + return $page->token; + } + + /** + * Retrieves the stored page via its token (which is derived from the URL and HTML). Returns null if not found or expired. + */ + public function retrieve(string $token): ?BrowserSubmittedPage + { + $item = $this->cache->getItem($this->cacheKey($token)); + if (!$item->isHit()) { + return null; + } + return $item->get(); + } + + /** + * Returns the list of recently submitted pages, newest first. + * Pages whose cache entry has expired are silently omitted. + * The list depends on the session and thus is per-browser and per-user. + * + * @return BrowserSubmittedPage[] + */ + public function getRecentPages(): array + { + $tokens = $this->requestStack->getSession()->get(self::SESSION_KEY, []); + $pages = []; + foreach ($tokens as $token) { + $page = $this->retrieve($token); + if ($page !== null) { + $pages[] = $page; + } + } + return $pages; + } + + /** + * Removes a page from both the cache and the recent list. + * @param BrowserSubmittedPage|string $page The page or its token to remove. + */ + public function remove(BrowserSubmittedPage|string $page): void + { + $this->cache->deleteItem($this->cacheKey($page)); + + $token = is_string($page) ? $page : $page->token; + + $session = $this->requestStack->getSession(); + //Remove the token from the recent list in the session: + $tokens = array_values(array_filter( + $session->get(self::SESSION_KEY, []), + static fn(string $u): bool => $u !== $token + )); + $session->set(self::SESSION_KEY, $tokens); + } + + private function cacheKey(BrowserSubmittedPage|string $token): string + { + if (!is_string($token)) { + $token = $token->token; + } + + return self::CACHE_KEY_PREFIX . $token; + } +} diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index 68d962bb..79d9f8e2 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -222,7 +222,7 @@ class TimeTravel if (isset($metadata->fieldMappings[$field])) { //We need to convert the string to a BigDecimal first if (!$data instanceof BigDecimal && ('big_decimal' === $metadata->getFieldMapping($field)->type)) { - $data = BigDecimal::of($data); + $data = is_float($data) ? BigDecimal::fromFloatShortest($data) : BigDecimal::of($data); } if (!$data instanceof \DateTimeInterface diff --git a/src/Services/Parts/PricedetailHelper.php b/src/Services/Parts/PricedetailHelper.php index b2e1340f..e40fc44d 100644 --- a/src/Services/Parts/PricedetailHelper.php +++ b/src/Services/Parts/PricedetailHelper.php @@ -170,7 +170,7 @@ class PricedetailHelper return null; } - return $avg->dividedBy($count, Pricedetail::PRICE_PRECISION, RoundingMode::HALF_UP); + return $avg->dividedBy($count, Pricedetail::PRICE_PRECISION, RoundingMode::HalfUp); } /** @@ -213,6 +213,6 @@ class PricedetailHelper $val_target = $val_base->multipliedBy($targetCurrency->getInverseExchangeRate()); } - return $val_target->toScale(Pricedetail::PRICE_PRECISION, RoundingMode::HALF_UP); + return $val_target->toScale(Pricedetail::PRICE_PRECISION, RoundingMode::HalfUp); } } diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index ee5b8c68..e011d980 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -190,7 +190,7 @@ final readonly class ProjectBuildHelper continue; } $has_price = true; - $total = $total->plus($unit_price->multipliedBy($entry->getQuantity())->multipliedBy($number_of_builds)); + $total = $total->plus($unit_price->multipliedBy(BigDecimal::fromFloatShortest($entry->getQuantity()))->multipliedBy($number_of_builds)); } return $has_price ? $total : null; @@ -206,7 +206,7 @@ final readonly class ProjectBuildHelper if ($total === null) { return null; } - return $total->dividedBy($number_of_builds, 10, RoundingMode::HALF_UP); + return $total->dividedBy($number_of_builds, 10, RoundingMode::HalfUp); } /** @@ -215,7 +215,7 @@ final readonly class ProjectBuildHelper public function roundedTotalBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal { return $this->calculateTotalBuildPrice($project, $number_of_builds, $currency) - ?->toScale(2, RoundingMode::UP); + ?->toScale(2, RoundingMode::Up); } /** @@ -224,7 +224,7 @@ final readonly class ProjectBuildHelper public function roundedUnitBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal { return $this->calculateUnitBuildPrice($project, $number_of_builds, $currency) - ?->toScale(2, RoundingMode::UP); + ?->toScale(2, RoundingMode::Up); } /** diff --git a/src/Services/System/GitVersionInfoProvider.php b/src/Services/System/GitVersionInfoProvider.php index 01925ff8..927326e5 100644 --- a/src/Services/System/GitVersionInfoProvider.php +++ b/src/Services/System/GitVersionInfoProvider.php @@ -62,6 +62,9 @@ final readonly class GitVersionInfoProvider { if (is_file($this->getGitDirectory() . '/HEAD')) { $git = file($this->getGitDirectory() . '/HEAD'); + if ($git === false) { + return null; + } $head = explode('/', $git[0], 3); if (!isset($head[2])) { diff --git a/src/Services/Tools/ExchangeRateUpdater.php b/src/Services/Tools/ExchangeRateUpdater.php index 6eb7ec13..ccc3f19f 100644 --- a/src/Services/Tools/ExchangeRateUpdater.php +++ b/src/Services/Tools/ExchangeRateUpdater.php @@ -44,15 +44,15 @@ class ExchangeRateUpdater try { //Try it in the direction QUOTE/BASE first, as most providers provide rates in this direction $rate = $this->swap->latest($currency->getIsoCode().'/'.$this->localizationSettings->baseCurrency); - $effective_rate = BigDecimal::of($rate->getValue()); + $effective_rate = BigDecimal::fromFloatShortest($rate->getValue()); } catch (UnsupportedCurrencyPairException|UnsupportedExchangeQueryException $exception) { //Otherwise try to get it inverse and calculate it ourselfes, from the format "BASE/QUOTE" $rate = $this->swap->latest($this->localizationSettings->baseCurrency.'/'.$currency->getIsoCode()); //The rate says how many quote units are worth one base unit //So we need to invert it to get the exchange rate - $rate_bd = BigDecimal::of($rate->getValue()); - $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + $rate_bd = BigDecimal::fromFloatShortest($rate->getValue()); + $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HalfUp); } $currency->setExchangeRate($effective_rate); diff --git a/src/Settings/InfoProviderSystem/BrowserPluginSettings.php b/src/Settings/InfoProviderSystem/BrowserPluginSettings.php new file mode 100644 index 00000000..1ad5c50b --- /dev/null +++ b/src/Settings/InfoProviderSystem/BrowserPluginSettings.php @@ -0,0 +1,40 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(name: "browser_plugin", label: new TM("settings.ips.browser_plugin"), description: new TM("settings.ips.browser_plugin.description"))] +#[SettingsIcon("fa-cloud-arrow-up")] +class BrowserPluginSettings +{ + #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), description: new TM("settings.ips.browser_plugin.enabled.help"), + envVar: "bool:BROWSER_PLUGIN_ENABLED", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $enabled = false; +} diff --git a/src/Settings/InfoProviderSystem/InfoProviderSettings.php b/src/Settings/InfoProviderSystem/InfoProviderSettings.php index 3e2a27ef..96de19cb 100644 --- a/src/Settings/InfoProviderSystem/InfoProviderSettings.php +++ b/src/Settings/InfoProviderSystem/InfoProviderSettings.php @@ -37,6 +37,9 @@ class InfoProviderSettings #[EmbeddedSettings] public ?InfoProviderGeneralSettings $general = null; + #[EmbeddedSettings] + public ?BrowserPluginSettings $browserPlugin = null; + #[EmbeddedSettings] public ?GenericWebProviderSettings $genericWebProvider = null; diff --git a/templates/info_providers/from_url/from_url.html.twig b/templates/info_providers/from_url/from_url.html.twig index 49d4b116..3146c5a5 100644 --- a/templates/info_providers/from_url/from_url.html.twig +++ b/templates/info_providers/from_url/from_url.html.twig @@ -33,5 +33,31 @@ {{ form_row(form.submit) }} + + {% if recentBrowserPages is not empty %} +
+ +
+ +
+

{% trans %}browser_plugin.recent_pages.help{% endtrans %}

+
+ {% for page in recentBrowserPages %} + + {% endfor %} +
+
+
+ {% endif %} + {{ form_end(form) }} {% endblock %} diff --git a/tests/API/Endpoints/PartEndpointTest.php b/tests/API/Endpoints/PartEndpointTest.php index 8d66d362..c8c087c5 100644 --- a/tests/API/Endpoints/PartEndpointTest.php +++ b/tests/API/Endpoints/PartEndpointTest.php @@ -69,4 +69,52 @@ final class PartEndpointTest extends CrudEndpointTestCase { $this->_testDeleteItem(1); } -} \ No newline at end of file + + public function testMasterPictureAttachmentPatchWithIRI(): void + { + $client = static::createAuthenticatedClient(); + + // Create a new attachment with a picture URL for Part 1 + $response = $client->request('POST', '/api/attachments', ['json' => [ + 'name' => 'Test Picture', + 'url' => 'http://example.com/test.jpg', + '_type' => 'Part', + 'element' => '/api/parts/1', + 'attachment_type' => '/api/attachment_types/1', + ]]); + self::assertResponseIsSuccessful(); + $attachmentIri = $response->toArray()['@id']; + + // Now PATCH Part 1 to set master_picture_attachment + $client->request('PATCH', '/api/parts/1', [ + 'json' => ['master_picture_attachment' => $attachmentIri], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ]); + self::assertResponseIsSuccessful(); + self::assertJsonContains(['master_picture_attachment' => ['@id' => $attachmentIri]]); + } + + public function testMasterPictureAttachmentPatchWithArray(): void + { + $client = static::createAuthenticatedClient(); + + // Create a new attachment with a picture URL for Part 1 + $response = $client->request('POST', '/api/attachments', ['json' => [ + 'name' => 'Test Picture', + 'url' => 'http://example.com/test.jpg', + '_type' => 'Part', + 'element' => '/api/parts/1', + 'attachment_type' => '/api/attachment_types/1', + ]]); + self::assertResponseIsSuccessful(); + $attachmentIri = $response->toArray()['@id']; + + // Now PATCH Part 1 to set master_picture_attachment + $client->request('PATCH', '/api/parts/1', [ + 'json' => ['master_picture_attachment' => ['@id' => $attachmentIri, '_type' => 'Part']], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ]); + self::assertResponseIsSuccessful(); + self::assertJsonContains(['master_picture_attachment' => ['@id' => $attachmentIri]]); + } +} diff --git a/tests/Controller/AuthorizationTest.php b/tests/Controller/AuthorizationTest.php new file mode 100644 index 00000000..4e211301 --- /dev/null +++ b/tests/Controller/AuthorizationTest.php @@ -0,0 +1,222 @@ +. + */ + +namespace App\Tests\Controller; + +use App\Entity\UserSystem\User; +use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\HttpFoundation\Response; + +/** + * Verifies the HTTP access-control boundaries: + * + * The app has an "anonymous" fixture user with readonly permissions, so truly + * public read routes return 200 even without a session. Write-protected routes + * return 401 for unauthenticated requests (not a 302 redirect). + * + * Users: admin (all-allow), user (editor preset), noread (no group/no perms) + */ +#[Group('DB')] +#[Group('slow')] +final class AuthorizationTest extends WebTestCase +{ + // ----------------------------------------------------------------------- + // Data providers + // ----------------------------------------------------------------------- + + /** + * Routes readable by the anonymous user — unauthenticated requests get 200. + */ + public static function publicReadRoutesProvider(): \Generator + { + yield 'homepage' => ['/en/']; + yield 'part view' => ['/en/part/1']; + yield 'statistics' => ['/en/statistics']; + yield 'select category' => ['/en/select_api/category']; + yield 'typeahead tags' => ['/en/typeahead/tags/search/test']; + } + + /** + * Write-protected routes — unauthenticated gets 401 (not 302). + */ + public static function writeProtectedRoutesProvider(): \Generator + { + yield 'part edit' => ['/en/part/1/edit']; + yield 'part new' => ['/en/part/new']; + yield 'user edit' => ['/en/user/1/edit']; + yield 'log list' => ['/en/log/']; + yield 'server info' => ['/en/tools/server_infos']; + } + + /** + * Routes the `noread` user (no group = no permissions) must be denied. + */ + public static function noreadDeniedRoutesProvider(): \Generator + { + yield 'part view' => ['/en/part/1']; + yield 'part edit' => ['/en/part/1/edit']; + yield 'part new' => ['/en/part/new']; + yield 'log list' => ['/en/log/']; + yield 'server info' => ['/en/tools/server_infos']; + yield 'select category' => ['/en/select_api/category']; + yield 'typeahead tags' => ['/en/typeahead/tags/search/test']; + } + + /** + * Routes the `user` (editor preset) must have access to. + */ + public static function editorAllowedRoutesProvider(): \Generator + { + yield 'homepage' => ['/en/']; + yield 'part view' => ['/en/part/1']; + yield 'part edit' => ['/en/part/1/edit']; + yield 'part new' => ['/en/part/new']; + yield 'select cat' => ['/en/select_api/category']; + yield 'typeahead' => ['/en/typeahead/tags/search/test']; + } + + /** + * Admin-only routes the `user` (editor) must be denied. + */ + public static function editorDeniedRoutesProvider(): \Generator + { + yield 'user edit' => ['/en/user/1/edit']; + yield 'log list' => ['/en/log/']; + yield 'server info' => ['/en/tools/server_infos']; + } + + /** + * Routes the `admin` user must be able to reach. + */ + public static function adminAllowedRoutesProvider(): \Generator + { + yield 'user edit' => ['/en/user/1/edit']; + yield 'log list' => ['/en/log/']; + yield 'server info' => ['/en/tools/server_infos']; + yield 'part view' => ['/en/part/1']; + yield 'part edit' => ['/en/part/1/edit']; + yield 'statistics' => ['/en/statistics']; + } + + // ----------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------- + + private function loginAs(string $username): KernelBrowser + { + $client = static::createClient(); + $em = static::getContainer()->get(EntityManagerInterface::class); + $user = $em->getRepository(User::class)->findOneBy(['name' => $username]); + if ($user === null) { + $this->markTestSkipped("Fixture user '$username' not found."); + } + $client->loginUser($user); + $client->followRedirects(false); + return $client; + } + + private function assertDenied(KernelBrowser $client, string $url): void + { + $client->request('GET', $url); + $code = $client->getResponse()->getStatusCode(); + $this->assertTrue( + $code === Response::HTTP_FORBIDDEN || $code === Response::HTTP_UNAUTHORIZED || $client->getResponse()->isRedirect(), + "Expected 401/403/redirect on $url, got $code" + ); + } + + // ----------------------------------------------------------------------- + // Unauthenticated: public reads + // ----------------------------------------------------------------------- + + #[DataProvider('publicReadRoutesProvider')] + public function testUnauthenticatedCanReadPublicRoutes(string $url): void + { + $client = static::createClient(); + $client->request('GET', $url); + // Anonymous user (readonly group) can access read-only content + $this->assertResponseIsSuccessful(); + } + + // ----------------------------------------------------------------------- + // Unauthenticated: write routes → 401 + // ----------------------------------------------------------------------- + + #[DataProvider('writeProtectedRoutesProvider')] + public function testUnauthenticatedIsUnauthorizedOnWriteRoutes(string $url): void + { + $client = static::createClient(); + $client->followRedirects(false); + $client->request('GET', $url); + + $code = $client->getResponse()->getStatusCode(); + $this->assertTrue( + $code === Response::HTTP_UNAUTHORIZED || $client->getResponse()->isRedirect(), + "Expected 401 or redirect on $url for unauthenticated request, got $code" + ); + } + + // ----------------------------------------------------------------------- + // noread user: denied everywhere + // ----------------------------------------------------------------------- + + #[DataProvider('noreadDeniedRoutesProvider')] + public function testNoreadUserIsDenied(string $url): void + { + $this->assertDenied($this->loginAs('noread'), $url); + } + + // ----------------------------------------------------------------------- + // Editor user + // ----------------------------------------------------------------------- + + #[DataProvider('editorAllowedRoutesProvider')] + public function testEditorCanAccess(string $url): void + { + $client = $this->loginAs('user'); + $client->request('GET', $url); + $this->assertResponseIsSuccessful(); + } + + #[DataProvider('editorDeniedRoutesProvider')] + public function testEditorIsDeniedOnAdminRoutes(string $url): void + { + $this->assertDenied($this->loginAs('user'), $url); + } + + // ----------------------------------------------------------------------- + // Admin user: can access everything + // ----------------------------------------------------------------------- + + #[DataProvider('adminAllowedRoutesProvider')] + public function testAdminCanAccessAllRoutes(string $url): void + { + $client = $this->loginAs('admin'); + $client->request('GET', $url); + $this->assertResponseIsSuccessful(); + } +} diff --git a/tests/Controller/BrowserPluginControllerTest.php b/tests/Controller/BrowserPluginControllerTest.php new file mode 100644 index 00000000..8af82ce9 --- /dev/null +++ b/tests/Controller/BrowserPluginControllerTest.php @@ -0,0 +1,247 @@ +. + */ + +declare(strict_types=1); + +namespace App\Tests\Controller; + +use App\Entity\UserSystem\User; +use App\Settings\InfoProviderSystem\BrowserPluginSettings; +use PHPUnit\Framework\Attributes\Group; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\HttpFoundation\Response; + +#[Group("slow")] +#[Group("DB")] +final class BrowserPluginControllerTest extends WebTestCase +{ + // --- GET /browser_info --- + + public function testGetInfoReturns401WhenNotAuthenticated(): void + { + $client = static::createClient(); + $client->request('GET', '/en/tools/info_providers/browser_info'); + + self::assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED); + } + + public function testGetInfoReturnsForbiddenForUnprivilegedUser(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'noread'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('GET', '/en/tools/info_providers/browser_info'); + + $this->assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN); + } + + public function testGetInfoReturns451WhenPluginDisabled(): void + { + $client = static::createClient(); + $this->loginAsUser($client, 'admin'); + // BrowserPluginSettings::$enabled defaults to false + + $client->request('GET', '/en/tools/info_providers/browser_info'); + + self::assertResponseStatusCodeSame(451); + } + + public function testGetInfoReturnsJsonWithExpectedKeys(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('GET', '/en/tools/info_providers/browser_info'); + + self::assertResponseStatusCodeSame(Response::HTTP_OK); + self::assertResponseHeaderSame('Content-Type', 'application/json'); + + $data = json_decode((string) $client->getResponse()->getContent(), true); + $this->assertArrayHasKey('username', $data); + $this->assertArrayHasKey('instance_name', $data); + $this->assertArrayHasKey('url_providers', $data); + $this->assertIsString($data['username']); + $this->assertIsString($data['instance_name']); + $this->assertIsArray($data['url_providers']); + $this->assertNotEmpty($data['username']); + $this->assertNotEmpty($data['instance_name']); + } + + public function testGetInfoUrlProvidersHaveIdAndLabel(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('GET', '/en/tools/info_providers/browser_info'); + + self::assertResponseStatusCodeSame(Response::HTTP_OK); + $data = json_decode((string) $client->getResponse()->getContent(), true); + + foreach ($data['url_providers'] as $provider) { + $this->assertArrayHasKey('id', $provider); + $this->assertArrayHasKey('label', $provider); + $this->assertIsString($provider['id']); + $this->assertIsString($provider['label']); + $this->assertNotEmpty($provider['id']); + $this->assertNotEmpty($provider['label']); + } + } + + // --- POST /browser_html --- + + public function testSubmitHtmlReturns401WhenNotAuthenticated(): void + { + $client = static::createClient(); + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode(['url' => 'https://example.com', 'html' => '', 'title' => 'Test'])); + + self::assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED); + } + + public function testSubmitHtmlReturns451WhenPluginDisabled(): void + { + $client = static::createClient(); + $this->loginAsUser($client, 'admin'); + // BrowserPluginSettings::$enabled defaults to false + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode(['url' => 'https://example.com', 'html' => '', 'title' => 'Test'])); + + self::assertResponseStatusCodeSame(451); + } + + public function testSubmitHtmlWithValidDataAndProvider(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode([ + 'url' => 'https://example.com/product/123', + 'html' => 'Product page', + 'title' => 'Some Product', + 'provider' => 'generic_web', + ])); + + self::assertResponseStatusCodeSame(Response::HTTP_OK); + $data = json_decode((string) $client->getResponse()->getContent(), true); + $this->assertArrayHasKey('redirect_url', $data); + $this->assertNotNull($data['redirect_url']); + $this->assertStringContainsString('generic_web', (string) $data['redirect_url']); + } + + public function testSubmitHtmlWithoutProviderReturnsNullRedirectUrl(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode([ + 'url' => 'https://example.com/product/123', + 'html' => 'Product page', + 'title' => 'Some Product', + ])); + + self::assertResponseStatusCodeSame(Response::HTTP_OK); + $data = json_decode((string) $client->getResponse()->getContent(), true); + $this->assertArrayHasKey('redirect_url', $data); + $this->assertNull($data['redirect_url']); + } + + public function testSubmitHtmlWithInvalidJsonReturns400(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], 'this is not valid json {'); + + self::assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); + } + + public function testSubmitHtmlWithMissingUrlReturns422(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode(['html' => '', 'title' => 'Test'])); + + self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); + } + + public function testSubmitHtmlWithMissingHtmlReturns422(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode(['url' => 'https://example.com', 'title' => 'Test'])); + + self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); + } + + public function testSubmitHtmlWithInvalidUrlReturns422(): void + { + $client = static::createClient(); + $client->disableReboot(); + $this->loginAsUser($client, 'admin'); + static::getContainer()->get(BrowserPluginSettings::class)->enabled = true; + + $client->request('POST', '/en/tools/info_providers/browser_html', [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode(['url' => 'not-a-url', 'html' => '', 'title' => 'Test'])); + + self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); + } + + private function loginAsUser(mixed $client, string $username): void + { + $entityManager = static::getContainer()->get('doctrine')->getManager(); + $user = $entityManager->getRepository(User::class)->findOneBy(['name' => $username]); + if (!$user) { + $this->markTestSkipped("User '{$username}' not found in fixtures"); + } + $client->loginUser($user); + } +} diff --git a/tests/Controller/SelectApiControllerTest.php b/tests/Controller/SelectApiControllerTest.php new file mode 100644 index 00000000..b07053b9 --- /dev/null +++ b/tests/Controller/SelectApiControllerTest.php @@ -0,0 +1,152 @@ +. + */ + +namespace App\Tests\Controller; + +use App\Entity\UserSystem\User; +use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +/** + * Tests the SelectAPIController endpoints used by select2 widgets. + * These JSON endpoints back every structural-entity dropdown in the UI. + */ +#[Group('DB')] +#[Group('slow')] +final class SelectApiControllerTest extends WebTestCase +{ + public static function endpointProvider(): \Generator + { + yield 'category' => ['/en/select_api/category']; + yield 'footprint' => ['/en/select_api/footprint']; + yield 'manufacturer' => ['/en/select_api/manufacturer']; + yield 'measurement_unit' => ['/en/select_api/measurement_unit']; + yield 'project' => ['/en/select_api/project']; + yield 'storage_location' => ['/en/select_api/storage_location']; + yield 'label_profiles' => ['/en/select_api/label_profiles']; + } + + private function adminClient(): KernelBrowser + { + $client = static::createClient(); + $em = static::getContainer()->get(EntityManagerInterface::class); + $admin = $em->getRepository(User::class)->findOneBy(['name' => 'admin']); + if ($admin === null) { + $this->markTestSkipped('Fixture user admin not found.'); + } + $client->loginUser($admin); + return $client; + } + + // ----------------------------------------------------------------------- + // Response format + // ----------------------------------------------------------------------- + + #[DataProvider('endpointProvider')] + public function testEndpointReturns200WithJsonContentType(string $url): void + { + $client = $this->adminClient(); + $client->request('GET', $url); + + $this->assertResponseIsSuccessful(); + $this->assertResponseHeaderSame('content-type', 'application/json'); + } + + #[DataProvider('endpointProvider')] + public function testEndpointReturnsValidJsonArray(string $url): void + { + $client = $this->adminClient(); + $client->request('GET', $url); + + $body = $client->getResponse()->getContent(); + $decoded = json_decode($body, true); + + $this->assertIsArray($decoded, "Response from $url is not a valid JSON array"); + } + + #[DataProvider('endpointProvider')] + public function testEachEntryHasTextAndValueKeys(string $url): void + { + $client = $this->adminClient(); + $client->request('GET', $url); + + $decoded = json_decode($client->getResponse()->getContent(), true); + // Some endpoints include an empty "select none" entry at index 0; all entries must have text + value + foreach ($decoded as $entry) { + $this->assertArrayHasKey('text', $entry, "Entry in $url missing 'text' key"); + $this->assertArrayHasKey('value', $entry, "Entry in $url missing 'value' key"); + } + } + + // ----------------------------------------------------------------------- + // Access control + // ----------------------------------------------------------------------- + + #[DataProvider('endpointProvider')] + public function testUnauthenticatedCanReadSelectApi(string $url): void + { + // The anonymous user (readonly group) has read access to structural entities, + // so these endpoints return 200 even without a session. + $client = static::createClient(); + $client->request('GET', $url); + $this->assertResponseIsSuccessful(); + } + + #[DataProvider('endpointProvider')] + public function testNoreadUserIsDenied(string $url): void + { + $client = static::createClient(); + $em = static::getContainer()->get(EntityManagerInterface::class); + $noread = $em->getRepository(User::class)->findOneBy(['name' => 'noread']); + if ($noread === null) { + $this->markTestSkipped('Fixture user noread not found.'); + } + $client->loginUser($noread); + $client->followRedirects(false); + $client->request('GET', $url); + + $response = $client->getResponse(); + $this->assertTrue( + $response->getStatusCode() === 403 || $response->isRedirect(), + "Expected 403 or redirect for noread user on $url, got " . $response->getStatusCode() + ); + } + + #[DataProvider('endpointProvider')] + public function testEditorUserCanAccess(string $url): void + { + $client = static::createClient(); + $em = static::getContainer()->get(EntityManagerInterface::class); + $user = $em->getRepository(User::class)->findOneBy(['name' => 'user']); + if ($user === null) { + $this->markTestSkipped('Fixture user user not found.'); + } + $client->loginUser($user); + $client->request('GET', $url); + + $this->assertResponseIsSuccessful(); + } +} diff --git a/tests/Controller/TypeaheadControllerTest.php b/tests/Controller/TypeaheadControllerTest.php new file mode 100644 index 00000000..ce2747fa --- /dev/null +++ b/tests/Controller/TypeaheadControllerTest.php @@ -0,0 +1,162 @@ +. + */ + +namespace App\Tests\Controller; + +use App\Entity\UserSystem\User; +use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +/** + * Tests the TypeaheadController JSON endpoints that back autocomplete widgets in the UI. + */ +#[Group('DB')] +#[Group('slow')] +final class TypeaheadControllerTest extends WebTestCase +{ + public static function endpointProvider(): \Generator + { + yield 'tags search' => ['/en/typeahead/tags/search/test']; + yield 'parameters part search' => ['/en/typeahead/parameters/part/search/voltage']; + yield 'parameters category search' => ['/en/typeahead/parameters/category/search/NPN']; + yield 'builtin resources' => ['/en/typeahead/builtInResources/search?query=DIP']; + yield 'parts search' => ['/en/typeahead/parts/search/res']; + } + + public static function partsReadEndpointProvider(): \Generator + { + // These require @parts.read — noread user must be denied + yield 'tags search' => ['/en/typeahead/tags/search/test']; + yield 'parameters part search' => ['/en/typeahead/parameters/part/search/voltage']; + yield 'parts search' => ['/en/typeahead/parts/search/res']; + } + + private function loginClient(string $username): KernelBrowser + { + $client = static::createClient(); + $em = static::getContainer()->get(EntityManagerInterface::class); + $user = $em->getRepository(User::class)->findOneBy(['name' => $username]); + if ($user === null) { + $this->markTestSkipped("Fixture user '$username' not found."); + } + $client->loginUser($user); + return $client; + } + + // ----------------------------------------------------------------------- + // Response format + // ----------------------------------------------------------------------- + + #[DataProvider('endpointProvider')] + public function testEndpointReturnsSuccessfulJsonForAdmin(string $url): void + { + $client = $this->loginClient('admin'); + $client->request('GET', $url); + + $this->assertResponseIsSuccessful(); + $this->assertJson($client->getResponse()->getContent()); + } + + #[DataProvider('endpointProvider')] + public function testEndpointReturnsJsonArray(string $url): void + { + $client = $this->loginClient('admin'); + $client->request('GET', $url); + + $decoded = json_decode($client->getResponse()->getContent(), true); + $this->assertIsArray($decoded, "Response from $url should be a JSON array"); + } + + // ----------------------------------------------------------------------- + // Tags search: result structure + // ----------------------------------------------------------------------- + + public function testTagsSearchReturnsStrings(): void + { + $client = $this->loginClient('admin'); + $client->request('GET', '/en/typeahead/tags/search/a'); + + $tags = json_decode($client->getResponse()->getContent(), true); + $this->assertIsArray($tags); + foreach ($tags as $tag) { + $this->assertIsString($tag, 'Each tag entry should be a plain string'); + } + } + + // ----------------------------------------------------------------------- + // Parts search: result structure + // ----------------------------------------------------------------------- + + public function testPartsSearchReturnsArrayWithExpectedKeys(): void + { + $client = $this->loginClient('admin'); + $client->request('GET', '/en/typeahead/parts/search/test'); + + $parts = json_decode($client->getResponse()->getContent(), true); + $this->assertIsArray($parts); + // Each result must have at least id and name + foreach ($parts as $part) { + $this->assertArrayHasKey('id', $part); + $this->assertArrayHasKey('name', $part); + } + } + + // ----------------------------------------------------------------------- + // Access control + // ----------------------------------------------------------------------- + + #[DataProvider('endpointProvider')] + public function testUnauthenticatedCanAccessTypeahead(string $url): void + { + // Anonymous user (readonly group) has @parts.read, so these endpoints return 200. + $client = static::createClient(); + $client->request('GET', $url); + $this->assertResponseIsSuccessful(); + } + + #[DataProvider('partsReadEndpointProvider')] + public function testNoreadUserIsDenied(string $url): void + { + $client = $this->loginClient('noread'); + $client->followRedirects(false); + $client->request('GET', $url); + + $response = $client->getResponse(); + $this->assertTrue( + $response->getStatusCode() === 403 || $response->isRedirect(), + "Expected 403 or redirect for noread user on $url, got " . $response->getStatusCode() + ); + } + + #[DataProvider('endpointProvider')] + public function testEditorUserCanAccess(string $url): void + { + $client = $this->loginClient('user'); + $client->request('GET', $url); + + $this->assertResponseIsSuccessful(); + } +} diff --git a/tests/EventSubscriber/MaintenanceModeSubscriberTest.php b/tests/EventSubscriber/MaintenanceModeSubscriberTest.php new file mode 100644 index 00000000..0d975ee0 --- /dev/null +++ b/tests/EventSubscriber/MaintenanceModeSubscriberTest.php @@ -0,0 +1,103 @@ +. + */ + +namespace App\Tests\EventSubscriber; + +use App\EventSubscriber\MaintenanceModeSubscriber; +use App\Services\System\UpdateExecutor; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +final class MaintenanceModeSubscriberTest extends TestCase +{ + private function makeSubscriber(bool $maintenanceActive): MaintenanceModeSubscriber + { + $executor = $this->createMock(UpdateExecutor::class); + $executor->method('isMaintenanceMode')->willReturn($maintenanceActive); + $executor->method('getMaintenanceInfo')->willReturn( + $maintenanceActive ? ['reason' => 'Test update', 'enabled_at' => date('Y-m-d H:i:s')] : null + ); + return new MaintenanceModeSubscriber($executor); + } + + private function makeEvent(string $url = 'http://example.com/'): RequestEvent + { + $kernel = $this->createMock(HttpKernelInterface::class); + $request = Request::create($url); + return new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + } + + public function testNoMaintenanceModeDoesNotSetResponse(): void + { + $subscriber = $this->makeSubscriber(false); + $event = $this->makeEvent(); + + $subscriber->onKernelRequest($event); + + // When not in maintenance mode, no response is ever set regardless of SAPI + $this->assertFalse($event->hasResponse()); + } + + public function testCliRequestIsNeverBlocked(): void + { + // Tests run from CLI (PHP_SAPI === 'cli'), so maintenance mode never blocks CLI requests. + // This verifies the intentional behaviour: maintenance mode only affects web requests. + $subscriber = $this->makeSubscriber(true); + $event = $this->makeEvent(); + + $subscriber->onKernelRequest($event); + + // CLI requests pass through even with maintenance active + $this->assertFalse($event->hasResponse()); + } + + public function testSubRequestIsIgnored(): void + { + $subscriber = $this->makeSubscriber(true); + $kernel = $this->createMock(HttpKernelInterface::class); + $request = Request::create('http://example.com/'); + $event = new RequestEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST); + + $subscriber->onKernelRequest($event); + + $this->assertFalse($event->hasResponse()); + } + + public function testSubscriberListensToKernelRequest(): void + { + $events = MaintenanceModeSubscriber::getSubscribedEvents(); + $this->assertArrayHasKey(KernelEvents::REQUEST, $events); + } + + public function testSubscriberListensWithHighPriority(): void + { + $events = MaintenanceModeSubscriber::getSubscribedEvents(); + $config = $events[KernelEvents::REQUEST]; + // Config is ['methodName', priority] + $priority = is_array($config) ? (int) ($config[1] ?? 0) : 0; + $this->assertGreaterThan(0, $priority, 'Maintenance subscriber should run with high priority'); + } +} diff --git a/tests/EventSubscriber/RedirectToHttpsSubscriberTest.php b/tests/EventSubscriber/RedirectToHttpsSubscriberTest.php new file mode 100644 index 00000000..ec782b66 --- /dev/null +++ b/tests/EventSubscriber/RedirectToHttpsSubscriberTest.php @@ -0,0 +1,101 @@ +. + */ + +namespace App\Tests\EventSubscriber; + +use App\EventSubscriber\RedirectToHttpsSubscriber; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Http\HttpUtils; + +final class RedirectToHttpsSubscriberTest extends TestCase +{ + private function makeEvent(string $url, bool $isMainRequest = true): RequestEvent + { + $kernel = $this->createMock(HttpKernelInterface::class); + $request = Request::create($url); + return new RequestEvent($kernel, $request, $isMainRequest ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::SUB_REQUEST); + } + + public function testHttpRequestIsRedirectedToHttpsWhenEnabled(): void + { + $subscriber = new RedirectToHttpsSubscriber(true, new HttpUtils()); + $event = $this->makeEvent('http://example.com/some/path'); + + $subscriber->onKernelRequest($event); + + $this->assertTrue($event->hasResponse()); + $response = $event->getResponse(); + $this->assertStringStartsWith('https://', $response->getTargetUrl()); + } + + public function testHttpsRequestIsNotRedirectedWhenEnabled(): void + { + $subscriber = new RedirectToHttpsSubscriber(true, new HttpUtils()); + $event = $this->makeEvent('https://example.com/some/path'); + + $subscriber->onKernelRequest($event); + + $this->assertFalse($event->hasResponse()); + } + + public function testHttpRequestIsNotRedirectedWhenDisabled(): void + { + $subscriber = new RedirectToHttpsSubscriber(false, new HttpUtils()); + $event = $this->makeEvent('http://example.com/some/path'); + + $subscriber->onKernelRequest($event); + + $this->assertFalse($event->hasResponse()); + } + + public function testSubRequestIsNotRedirected(): void + { + $subscriber = new RedirectToHttpsSubscriber(true, new HttpUtils()); + $event = $this->makeEvent('http://example.com/', false); + + $subscriber->onKernelRequest($event); + + $this->assertFalse($event->hasResponse()); + } + + public function testRedirectUrlPreservesPath(): void + { + $subscriber = new RedirectToHttpsSubscriber(true, new HttpUtils()); + $event = $this->makeEvent('http://example.com/admin/parts?q=test'); + + $subscriber->onKernelRequest($event); + + $this->assertTrue($event->hasResponse()); + $this->assertStringContainsString('/admin/parts', $event->getResponse()->getTargetUrl()); + $this->assertStringContainsString('q=test', $event->getResponse()->getTargetUrl()); + } + + public function testSubscriberListensToKernelRequestEvent(): void + { + $events = RedirectToHttpsSubscriber::getSubscribedEvents(); + $this->assertArrayHasKey('kernel.request', $events); + } +} diff --git a/tests/Services/Cache/ElementCacheTagGeneratorTest.php b/tests/Services/Cache/ElementCacheTagGeneratorTest.php new file mode 100644 index 00000000..f747441f --- /dev/null +++ b/tests/Services/Cache/ElementCacheTagGeneratorTest.php @@ -0,0 +1,67 @@ +. + */ + +namespace App\Tests\Services\Cache; + +use App\Entity\Parts\Part; +use App\Services\Cache\ElementCacheTagGenerator; +use PHPUnit\Framework\TestCase; + +final class ElementCacheTagGeneratorTest extends TestCase +{ + private ElementCacheTagGenerator $service; + + protected function setUp(): void + { + $this->service = new ElementCacheTagGenerator(); + } + + public function testClassNameIsConvertedToTag(): void + { + $tag = $this->service->getElementTypeCacheTag(Part::class); + // Backslashes must be replaced by underscores + $this->assertStringNotContainsString('\\', $tag); + $this->assertSame(str_replace('\\', '_', Part::class), $tag); + } + + public function testObjectInputGivesSameResultAsClassName(): void + { + $part = new Part(); + $tagFromObject = $this->service->getElementTypeCacheTag($part); + $tagFromClass = $this->service->getElementTypeCacheTag(Part::class); + $this->assertSame($tagFromClass, $tagFromObject); + } + + public function testResultIsCached(): void + { + $tag1 = $this->service->getElementTypeCacheTag(Part::class); + $tag2 = $this->service->getElementTypeCacheTag(Part::class); + $this->assertSame($tag1, $tag2); + } + + public function testNonExistentClassThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->service->getElementTypeCacheTag('App\\NonExistent\\Foo'); + } +} diff --git a/tests/Services/Cache/UserCacheKeyGeneratorTest.php b/tests/Services/Cache/UserCacheKeyGeneratorTest.php new file mode 100644 index 00000000..23583db4 --- /dev/null +++ b/tests/Services/Cache/UserCacheKeyGeneratorTest.php @@ -0,0 +1,110 @@ +. + */ + +namespace App\Tests\Services\Cache; + +use App\Entity\UserSystem\User; +use App\Services\Cache\UserCacheKeyGenerator; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +final class UserCacheKeyGeneratorTest extends TestCase +{ + private function makeGenerator(?User $loggedInUser, ?Request $request = null): UserCacheKeyGenerator + { + $security = $this->createMock(Security::class); + $security->method('getUser')->willReturn($loggedInUser); + + $requestStack = $this->createMock(RequestStack::class); + $requestStack->method('getCurrentRequest')->willReturn($request); + + return new UserCacheKeyGenerator($security, $requestStack); + } + + private function makeUserWithId(int $id): User + { + $user = new User(); + $ref = new \ReflectionProperty(User::class, 'id'); + $ref->setValue($user, $id); + return $user; + } + + public function testAnonymousUserKeyContainsAnonymousId(): void + { + $service = $this->makeGenerator(null); + $key = $service->generateKey(); + $this->assertStringContainsString((string) User::ID_ANONYMOUS, $key); + } + + public function testExplicitAnonymousUserGivesSameKeyAsNull(): void + { + $anonUser = $this->makeUserWithId(User::ID_ANONYMOUS); + $anonUser->setName('anonymous'); + + $service = $this->makeGenerator(null); + $keyFromNull = $service->generateKey(null); + $keyFromAnon = $service->generateKey($anonUser); + $this->assertSame($keyFromNull, $keyFromAnon); + } + + public function testKeyForRealUserContainsUserId(): void + { + $user = $this->makeUserWithId(42); + $service = $this->makeGenerator(null); + + $key = $service->generateKey($user); + $this->assertStringContainsString('42', $key); + $this->assertStringNotContainsString((string) User::ID_ANONYMOUS, $key); + } + + public function testLocaleFromRequestIsIncludedInKey(): void + { + $request = Request::create('/'); + $request->setLocale('de'); + + $service = $this->makeGenerator(null, $request); + $key = $service->generateKey(); + $this->assertStringContainsString('de', $key); + } + + public function testDifferentUsersProduceDifferentKeys(): void + { + $service = $this->makeGenerator(null); + + $user1 = $this->makeUserWithId(10); + $user2 = $this->makeUserWithId(20); + + $this->assertNotSame($service->generateKey($user1), $service->generateKey($user2)); + } + + public function testCurrentlyLoggedInUserIsUsedWhenNoExplicitUser(): void + { + $loggedIn = $this->makeUserWithId(99); + $service = $this->makeGenerator($loggedIn); + + $key = $service->generateKey(); + $this->assertStringContainsString('99', $key); + } +} diff --git a/tests/Services/EntityURLGeneratorTest.php b/tests/Services/EntityURLGeneratorTest.php new file mode 100644 index 00000000..f21511e0 --- /dev/null +++ b/tests/Services/EntityURLGeneratorTest.php @@ -0,0 +1,113 @@ +. + */ + +namespace App\Tests\Services; + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; +use App\Entity\Parts\StorageLocation; +use App\Entity\Parts\Supplier; +use App\Entity\UserSystem\User; +use App\Exceptions\EntityNotSupportedException; +use App\Services\EntityURLGenerator; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +final class EntityURLGeneratorTest extends WebTestCase +{ + private static EntityURLGenerator $service; + + public static function setUpBeforeClass(): void + { + self::bootKernel(); + self::$service = self::getContainer()->get(EntityURLGenerator::class); + } + + private function entityWithId(string $class, int $id): AbstractDBElement + { + $entity = new $class(); + $ref = new \ReflectionProperty(AbstractDBElement::class, 'id'); + $ref->setValue($entity, $id); + return $entity; + } + + public function testInfoUrlForPartContainsPartPath(): void + { + $part = $this->entityWithId(Part::class, 1); + $url = self::$service->infoURL($part); + $this->assertStringContainsString('part', $url); + $this->assertStringContainsString('1', $url); + } + + public function testEditUrlForCategoryContainsCategoryPath(): void + { + $category = $this->entityWithId(Category::class, 5); + $url = self::$service->editURL($category); + $this->assertStringContainsString('category', $url); + $this->assertStringContainsString('5', $url); + } + + public function testListPartsUrlForSupplierContainsSupplierPath(): void + { + $supplier = $this->entityWithId(Supplier::class, 7); + $url = self::$service->listPartsURL($supplier); + $this->assertStringContainsString('supplier', $url); + } + + public function testGetUrlWithInfoTypeCallsInfoUrl(): void + { + $part = $this->entityWithId(Part::class, 3); + $url = self::$service->getURL($part, 'info'); + $this->assertStringContainsString('part', $url); + } + + public function testGetUrlWithEditTypeCallsEditUrl(): void + { + $manufacturer = $this->entityWithId(Manufacturer::class, 2); + $url = self::$service->getURL($manufacturer, 'edit'); + $this->assertStringContainsString('manufacturer', $url); + } + + public function testGetUrlWithUnknownTypeThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $part = $this->entityWithId(Part::class, 1); + self::$service->getURL($part, 'unsupported_type'); + } + + public function testInfoUrlForUserContainsUserPath(): void + { + $user = $this->entityWithId(User::class, 10); + $url = self::$service->editURL($user); + $this->assertStringContainsString('user', $url); + } + + public function testListPartsUrlForStorelocationContainsStorelocationPath(): void + { + $loc = $this->entityWithId(StorageLocation::class, 4); + $url = self::$service->listPartsURL($loc); + $this->assertStringContainsString('store', $url); + } +} diff --git a/tests/Services/Formatters/MarkdownParserTest.php b/tests/Services/Formatters/MarkdownParserTest.php new file mode 100644 index 00000000..0b27972f --- /dev/null +++ b/tests/Services/Formatters/MarkdownParserTest.php @@ -0,0 +1,86 @@ +. + */ + +namespace App\Tests\Services\Formatters; + +use App\Services\Formatters\MarkdownParser; +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class MarkdownParserTest extends TestCase +{ + private MarkdownParser $service; + + protected function setUp(): void + { + $translator = $this->createMock(TranslatorInterface::class); + $translator->method('trans')->willReturn('Loading...'); + $this->service = new MarkdownParser($translator); + } + + public function testOutputContainsDataMarkdownAttribute(): void + { + $result = $this->service->markForRendering('**hello**'); + $this->assertStringContainsString('data-markdown=', $result); + $this->assertStringContainsString('data-controller="common--markdown"', $result); + } + + public function testMarkdownContentIsHtmlescapedInAttribute(): void + { + $result = $this->service->markForRendering(''); + // The raw < should not appear unescaped inside the attribute + $this->assertStringNotContainsString('