diff --git a/README.md b/README.md index 3c738025..b857711f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![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) @@ -61,8 +62,7 @@ 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. -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. +* Retrieve part information from arbitrary shop websites, using either conventional data extraction from structured metadata, or AI based data extraction * 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 d8b69897..6ceb272e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.12.0 +2.11.1 diff --git a/assets/controllers/common/dirty_form_controller.js b/assets/controllers/common/dirty_form_controller.js deleted file mode 100644 index aad2e6b0..00000000 --- a/assets/controllers/common/dirty_form_controller.js +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 3fcaa5e3..9ff78651 100644 --- a/composer.json +++ b/composer.json @@ -15,11 +15,11 @@ "amphp/http-client": "^5.1", "api-platform/doctrine-orm": "^4.1", "api-platform/json-api": "^4.0.0", - "api-platform/mcp": "^v4.3.6", + "api-platform/mcp": "4.3.x-dev", "api-platform/symfony": "^4.0.0", "api-platform/metadata": "4.3.x-dev", "beberlei/doctrineextensions": "^1.2", - "brick/math": "^0.17.0", + "brick/math": "^0.14.8", "brick/schema": "^0.2.0", "composer/ca-bundle": "^1.5", "composer/package-versions-deprecated": "^1.11.99.5", @@ -42,7 +42,7 @@ "league/html-to-markdown": "^5.0.1", "liip/imagine-bundle": "^2.2", "maennchen/zipstream-php": "2.1", - "mcp/sdk": "v0.5.0", + "mcp/sdk": "v0.5.0 as 0.4.0", "nbgrp/onelogin-saml-bundle": "^v2.0.2", "nelexa/zip": "^4.0", "nelmio/cors-bundle": "^2.3", @@ -60,9 +60,9 @@ "scheb/2fa-trusted-device": "^v7.11.0", "shivas/versioning-bundle": "^4.0", "spatie/db-dumper": "^3.3.1", - "symfony/ai-bundle": "^0.9.0", - "symfony/ai-lm-studio-platform": "^0.9.0", - "symfony/ai-open-router-platform": "^0.9.0", + "symfony/ai-bundle": "^0.8.0", + "symfony/ai-lm-studio-platform": "^0.8.0", + "symfony/ai-open-router-platform": "^0.8.0", "symfony/apache-pack": "^1.0", "symfony/asset": "7.4.*", "symfony/console": "7.4.*", @@ -76,7 +76,7 @@ "symfony/http-client": "7.4.*", "symfony/http-kernel": "7.4.*", "symfony/mailer": "7.4.*", - "symfony/mcp-bundle": "^v0.9.0.0", + "symfony/mcp-bundle": "^0.8.0", "symfony/monolog-bundle": "^4.0", "symfony/process": "7.4.*", "symfony/property-access": "7.4.*", diff --git a/composer.lock b/composer.lock index c409386c..98a9924e 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": "9171608b0a2f6caa65f4eca3b66ae351", + "content-hash": "79f9618efcdf9891b62843fb5c3fb8de", "packages": [ { "name": "amphp/amp", @@ -456,16 +456,16 @@ }, { "name": "amphp/http-client", - "version": "v5.3.6", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/amphp/http-client.git", - "reference": "ca155026acafa74a612d776a97202d53077fee86" + "reference": "75ad21574fd632594a2dd914496647816d5106bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http-client/zipball/ca155026acafa74a612d776a97202d53077fee86", - "reference": "ca155026acafa74a612d776a97202d53077fee86", + "url": "https://api.github.com/repos/amphp/http-client/zipball/75ad21574fd632594a2dd914496647816d5106bc", + "reference": "75ad21574fd632594a2dd914496647816d5106bc", "shasum": "" }, "require": { @@ -493,8 +493,9 @@ "amphp/phpunit-util": "^3", "ext-json": "*", "kelunik/link-header-rfc5988": "^1", + "laminas/laminas-diactoros": "^2.3", "phpunit/phpunit": "^9", - "psalm/phar": "6.16.1" + "psalm/phar": "~5.23" }, "suggest": { "amphp/file": "Required for file request bodies and HTTP archive logging", @@ -541,7 +542,7 @@ ], "support": { "issues": "https://github.com/amphp/http-client/issues", - "source": "https://github.com/amphp/http-client/tree/v5.3.6" + "source": "https://github.com/amphp/http-client/tree/v5.3.4" }, "funding": [ { @@ -549,7 +550,7 @@ "type": "github" } ], - "time": "2026-05-15T23:29:38+00:00" + "time": "2025-08-16T20:41:23+00:00" }, { "name": "amphp/parser", @@ -976,22 +977,22 @@ }, { "name": "api-platform/doctrine-common", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-common.git", - "reference": "089b196c2f8e4d14333aaa3c6db33356e8fd8be0" + "reference": "2072247e3c8126d815f20324e7aaa97c2b5ee889" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/089b196c2f8e4d14333aaa3c6db33356e8fd8be0", - "reference": "089b196c2f8e4d14333aaa3c6db33356e8fd8be0", + "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/2072247e3c8126d815f20324e7aaa97c2b5ee889", + "reference": "2072247e3c8126d815f20324e7aaa97c2b5ee889", "shasum": "" }, "require": { "api-platform/metadata": "^4.2.6", "api-platform/state": "^4.2.4", - "doctrine/collections": "^2.1 || ^3.0", + "doctrine/collections": "^2.1", "doctrine/common": "^3.2.2", "doctrine/persistence": "^3.2 || ^4.0", "php": ">=8.2" @@ -1060,22 +1061,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-common/tree/v4.3.6" + "source": "https://github.com/api-platform/doctrine-common/tree/v4.3.4" }, - "time": "2026-05-04T13:25:58+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/doctrine-orm", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-orm.git", - "reference": "095a4c56cdd9986208100dedd5d28be50a4830ba" + "reference": "3dc88ee48ffcdb6eee45ec1d3e9f25ea2aad4eaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/095a4c56cdd9986208100dedd5d28be50a4830ba", - "reference": "095a4c56cdd9986208100dedd5d28be50a4830ba", + "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/3dc88ee48ffcdb6eee45ec1d3e9f25ea2aad4eaa", + "reference": "3dc88ee48ffcdb6eee45ec1d3e9f25ea2aad4eaa", "shasum": "" }, "require": { @@ -1149,13 +1150,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-orm/tree/v4.3.6" + "source": "https://github.com/api-platform/doctrine-orm/tree/v4.3.4" }, - "time": "2026-05-07T11:45:31+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/documentation", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/documentation.git", @@ -1212,13 +1213,13 @@ ], "description": "API Platform documentation controller.", "support": { - "source": "https://github.com/api-platform/documentation/tree/v4.3.6" + "source": "https://github.com/api-platform/documentation/tree/v4.3.4" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/http-cache", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/http-cache.git", @@ -1292,22 +1293,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/http-cache/tree/v4.3.6" + "source": "https://github.com/api-platform/http-cache/tree/v4.3.4" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/hydra", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/hydra.git", - "reference": "317a696e396b80ba87de2560679c362923ef0a14" + "reference": "9b0a677b21ee4f2ec255386a84bdcf1d12ea7bc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/hydra/zipball/317a696e396b80ba87de2560679c362923ef0a14", - "reference": "317a696e396b80ba87de2560679c362923ef0a14", + "url": "https://api.github.com/repos/api-platform/hydra/zipball/9b0a677b21ee4f2ec255386a84bdcf1d12ea7bc4", + "reference": "9b0a677b21ee4f2ec255386a84bdcf1d12ea7bc4", "shasum": "" }, "require": { @@ -1379,22 +1380,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/hydra/tree/v4.3.6" + "source": "https://github.com/api-platform/hydra/tree/v4.3.4" }, - "time": "2026-05-11T11:50:19+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/json-api", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/json-api.git", - "reference": "3a562e7f1bb1bc802e58eff674a20b78fe107275" + "reference": "30e399ea2266403d04fd93df83c6983cf0a30e5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/json-api/zipball/3a562e7f1bb1bc802e58eff674a20b78fe107275", - "reference": "3a562e7f1bb1bc802e58eff674a20b78fe107275", + "url": "https://api.github.com/repos/api-platform/json-api/zipball/30e399ea2266403d04fd93df83c6983cf0a30e5d", + "reference": "30e399ea2266403d04fd93df83c6983cf0a30e5d", "shasum": "" }, "require": { @@ -1461,13 +1462,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/json-api/tree/v4.3.6" + "source": "https://github.com/api-platform/json-api/tree/v4.3.4" }, - "time": "2026-05-22T11:06:32+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/json-schema", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/json-schema.git", @@ -1542,13 +1543,13 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/json-schema/tree/v4.3.6" + "source": "https://github.com/api-platform/json-schema/tree/v4.3.4" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/jsonld", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/jsonld.git", @@ -1622,22 +1623,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/jsonld/tree/v4.3.6" + "source": "https://github.com/api-platform/jsonld/tree/v4.3.4" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/mcp", - "version": "v4.3.6", + "version": "4.3.x-dev", "source": { "type": "git", "url": "https://github.com/api-platform/mcp.git", - "reference": "35177126b8beb69169e7a33a325e00dbbdbbd7bf" + "reference": "407c1039e8e022f7baed7d2cfbf63f5f09957f6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/mcp/zipball/35177126b8beb69169e7a33a325e00dbbdbbd7bf", - "reference": "35177126b8beb69169e7a33a325e00dbbdbbd7bf", + "url": "https://api.github.com/repos/api-platform/mcp/zipball/407c1039e8e022f7baed7d2cfbf63f5f09957f6b", + "reference": "407c1039e8e022f7baed7d2cfbf63f5f09957f6b", "shasum": "" }, "require": { @@ -1699,9 +1700,9 @@ ], "support": { "issues": "https://github.com/api-platform/mcp/issues", - "source": "https://github.com/api-platform/mcp/tree/v4.3.6" + "source": "https://github.com/api-platform/mcp/tree/4.3" }, - "time": "2026-05-11T06:02:06+00:00" + "time": "2026-05-06T12:07:59+00:00" }, { "name": "api-platform/metadata", @@ -1709,12 +1710,12 @@ "source": { "type": "git", "url": "https://github.com/api-platform/metadata.git", - "reference": "e9e8a7b85d2d513edff3b108072f8ab23a9d6344" + "reference": "52b367f046c5d202629e9441aece39b0e6b37838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/metadata/zipball/e9e8a7b85d2d513edff3b108072f8ab23a9d6344", - "reference": "e9e8a7b85d2d513edff3b108072f8ab23a9d6344", + "url": "https://api.github.com/repos/api-platform/metadata/zipball/52b367f046c5d202629e9441aece39b0e6b37838", + "reference": "52b367f046c5d202629e9441aece39b0e6b37838", "shasum": "" }, "require": { @@ -1799,11 +1800,11 @@ "support": { "source": "https://github.com/api-platform/metadata/tree/4.3" }, - "time": "2026-05-22T12:00:17+00:00" + "time": "2026-05-06T12:07:59+00:00" }, { "name": "api-platform/openapi", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/openapi.git", @@ -1888,22 +1889,22 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/openapi/tree/v4.3.6" + "source": "https://github.com/api-platform/openapi/tree/v4.3.4" }, "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/serializer", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/serializer.git", - "reference": "2c4f996bb6e5fef49106df0c48d0c1954e10998b" + "reference": "bd7c26cc8e6858abc9661d677c15eaf4c61e08e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/serializer/zipball/2c4f996bb6e5fef49106df0c48d0c1954e10998b", - "reference": "2c4f996bb6e5fef49106df0c48d0c1954e10998b", + "url": "https://api.github.com/repos/api-platform/serializer/zipball/bd7c26cc8e6858abc9661d677c15eaf4c61e08e3", + "reference": "bd7c26cc8e6858abc9661d677c15eaf4c61e08e3", "shasum": "" }, "require": { @@ -1912,7 +1913,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.37 || ^7.4.9 || ^8.0.9", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", "symfony/validator": "^6.4.11 || ^7.0 || ^8.0" }, "require-dev": { @@ -1982,22 +1983,22 @@ "serializer" ], "support": { - "source": "https://github.com/api-platform/serializer/tree/v4.3.6" + "source": "https://github.com/api-platform/serializer/tree/v4.3.4" }, - "time": "2026-05-12T10:07:44+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/state", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/state.git", - "reference": "6e3f6d75e605ba7171a7590c82da5126979a936b" + "reference": "dda8789e95b1627a6427edb48f9024b306fdf5ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/state/zipball/6e3f6d75e605ba7171a7590c82da5126979a936b", - "reference": "6e3f6d75e605ba7171a7590c82da5126979a936b", + "url": "https://api.github.com/repos/api-platform/state/zipball/dda8789e95b1627a6427edb48f9024b306fdf5ff", + "reference": "dda8789e95b1627a6427edb48f9024b306fdf5ff", "shasum": "" }, "require": { @@ -2005,7 +2006,7 @@ "php": ">=8.2", "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^3.1", - "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", "symfony/serializer": "^6.4 || ^7.0 || ^8.0", "symfony/translation-contracts": "^3.0" }, @@ -2079,22 +2080,22 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/state/tree/v4.3.6" + "source": "https://github.com/api-platform/state/tree/v4.3.4" }, - "time": "2026-05-22T12:02:28+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/symfony", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/symfony.git", - "reference": "13308ad99dd1479e70fe79c20519d8135df8e7b9" + "reference": "532063884e3f91a8a831322a572220cc55501a2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/symfony/zipball/13308ad99dd1479e70fe79c20519d8135df8e7b9", - "reference": "13308ad99dd1479e70fe79c20519d8135df8e7b9", + "url": "https://api.github.com/repos/api-platform/symfony/zipball/532063884e3f91a8a831322a572220cc55501a2f", + "reference": "532063884e3f91a8a831322a572220cc55501a2f", "shasum": "" }, "require": { @@ -2111,7 +2112,6 @@ "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", @@ -2208,28 +2208,28 @@ "symfony" ], "support": { - "source": "https://github.com/api-platform/symfony/tree/v4.3.6" + "source": "https://github.com/api-platform/symfony/tree/v4.3.4" }, - "time": "2026-05-18T09:34:32+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "api-platform/validator", - "version": "v4.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/api-platform/validator.git", - "reference": "6df6804799f8831469d2602d0845a0316e81fbab" + "reference": "22693bc3d3538af700cf274b99c834c37b1d1a68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/validator/zipball/6df6804799f8831469d2602d0845a0316e81fbab", - "reference": "6df6804799f8831469d2602d0845a0316e81fbab", + "url": "https://api.github.com/repos/api-platform/validator/zipball/22693bc3d3538af700cf274b99c834c37b1d1a68", + "reference": "22693bc3d3538af700cf274b99c834c37b1d1a68", "shasum": "" }, "require": { "api-platform/metadata": "^4.3", "php": ">=8.2", - "symfony/http-kernel": "^6.4.13 || ^7.1 || ^8.0", + "symfony/http-kernel": "^6.4 || ^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", @@ -2284,9 +2284,9 @@ "validator" ], "support": { - "source": "https://github.com/api-platform/validator/tree/v4.3.6" + "source": "https://github.com/api-platform/validator/tree/v4.3.4" }, - "time": "2026-05-07T11:45:31+00:00" + "time": "2026-04-30T12:21:24+00:00" }, { "name": "beberlei/assert", @@ -2419,22 +2419,23 @@ }, { "name": "brick/math", - "version": "0.17.2", + "version": "0.14.8", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "8189e751995f9e15729c1aa2f89fa8f166ffe818" + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/8189e751995f9e15729c1aa2f89fa8f166ffe818", - "reference": "8189e751995f9e15729c1aa2f89fa8f166ffe818", + "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { + "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -2466,7 +2467,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.17.2" + "source": "https://github.com/brick/math/tree/0.14.8" }, "funding": [ { @@ -2474,7 +2475,7 @@ "type": "github" } ], - "time": "2026-05-25T20:34:43+00:00" + "time": "2026-02-10T14:33:43+00:00" }, { "name": "brick/schema", @@ -2589,16 +2590,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.12", + "version": "1.5.11", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", - "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6", "shasum": "" }, "require": { @@ -2645,7 +2646,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.12" + "source": "https://github.com/composer/ca-bundle/tree/1.5.11" }, "funding": [ { @@ -2657,7 +2658,7 @@ "type": "github" } ], - "time": "2026-05-19T11:26:22+00:00" + "time": "2026-03-30T09:16:10+00:00" }, { "name": "composer/package-versions-deprecated", @@ -4062,16 +4063,16 @@ }, { "name": "doctrine/orm", - "version": "3.6.7", + "version": "3.6.4", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c" + "reference": "156f3b5a984e7eaa72d440bb6de1d3b6f8d2d6fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/bc217c0e19c3a9eadfa67697143b87c9ba01272c", - "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c", + "url": "https://api.github.com/repos/doctrine/orm/zipball/156f3b5a984e7eaa72d440bb6de1d3b6f8d2d6fd", + "reference": "156f3b5a984e7eaa72d440bb6de1d3b6f8d2d6fd", "shasum": "" }, "require": { @@ -4144,9 +4145,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.6.7" + "source": "https://github.com/doctrine/orm/tree/3.6.4" }, - "time": "2026-05-25T16:45:47+00:00" + "time": "2026-05-07T07:04:34+00:00" }, { "name": "doctrine/persistence", @@ -4713,16 +4714,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.4", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "aec528da477062d3af11f51e6b33402be233b21f" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aec528da477062d3af11f51e6b33402be233b21f", - "reference": "aec528da477062d3af11f51e6b33402be233b21f", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { @@ -4740,9 +4741,8 @@ "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.52 || ^9.6.34", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -4820,7 +4820,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.4" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -4836,20 +4836,20 @@ "type": "tidelift" } ], - "time": "2026-05-22T19:00:53+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.4.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -4857,7 +4857,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.52 || ^9.6.34" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -4903,7 +4903,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.4.1" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -4919,20 +4919,20 @@ "type": "tidelift" } ], - "time": "2026-05-20T22:57:30+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.10.2", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a1bbdc172f32a25fe999965b65b6e71fd87da9ed" + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a1bbdc172f32a25fe999965b65b6e71fd87da9ed", - "reference": "a1bbdc172f32a25fe999965b65b6e71fd87da9ed", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", "shasum": "" }, "require": { @@ -4947,9 +4947,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "1.1.0", + "http-interop/http-factory-tests": "0.9.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.52 || ^9.6.34" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -5020,7 +5020,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.10.2" + "source": "https://github.com/guzzle/psr7/tree/2.9.0" }, "funding": [ { @@ -5036,7 +5036,7 @@ "type": "tidelift" } ], - "time": "2026-05-25T22:58:15+00:00" + "time": "2026-03-10T16:41:02+00:00" }, { "name": "hshn/base64-encoded-file", @@ -7903,16 +7903,16 @@ }, { "name": "nette/utils", - "version": "v4.1.4", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", - "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", "shasum": "" }, "require": { @@ -7988,9 +7988,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.4" + "source": "https://github.com/nette/utils/tree/v4.1.3" }, - "time": "2026-05-11T20:49:54+00:00" + "time": "2026-02-13T03:05:33+00:00" }, { "name": "nikolaposa/version", @@ -8253,21 +8253,21 @@ }, { "name": "onelogin/php-saml", - "version": "4.3.2", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e" + "reference": "b009f160e4ac11f49366a45e0d45706b48429353" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/26b3a47349415e5b7aa300ba4ab7fc316c65f19e", - "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/b009f160e4ac11f49366a45e0d45706b48429353", + "reference": "b009f160e4ac11f49366a45e0d45706b48429353", "shasum": "" }, "require": { "php": ">=7.3", - "robrichards/xmlseclibs": "^3.1.5" + "robrichards/xmlseclibs": ">=3.1.4" }, "require-dev": { "pdepend/pdepend": "^2.8.0", @@ -8313,7 +8313,7 @@ "type": "github" } ], - "time": "2026-05-07T22:38:04+00:00" + "time": "2025-12-09T10:50:49+00:00" }, { "name": "opis/json-schema", @@ -10135,16 +10135,16 @@ }, { "name": "revolt/event-loop", - "version": "v1.0.9", + "version": "v1.0.8", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "44061cf513e53c6200372fc935ac42271566295d" + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d", - "reference": "44061cf513e53c6200372fc935ac42271566295d", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", "shasum": "" }, "require": { @@ -10154,7 +10154,7 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^9", - "psalm/phar": "6.16.*" + "psalm/phar": "^5.15" }, "type": "library", "extra": { @@ -10201,9 +10201,9 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.9" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" }, - "time": "2026-05-16T17:55:38+00:00" + "time": "2025-08-27T21:33:23+00:00" }, { "name": "rhukster/dom-sanitizer", @@ -11193,21 +11193,21 @@ }, { "name": "symfony/ai-bundle", - "version": "v0.9.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-bundle.git", - "reference": "77fd1b513174770acf49abd68effa995fa518f7c" + "reference": "847365e0f885f8814421e9c94f03ce19e0b54bbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-bundle/zipball/77fd1b513174770acf49abd68effa995fa518f7c", - "reference": "77fd1b513174770acf49abd68effa995fa518f7c", + "url": "https://api.github.com/repos/symfony/ai-bundle/zipball/847365e0f885f8814421e9c94f03ce19e0b54bbc", + "reference": "847365e0f885f8814421e9c94f03ce19e0b54bbc", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-platform": "^0.9", + "symfony/ai-platform": "^0.8", "symfony/clock": "^7.3|^8.0", "symfony/config": "^7.3|^8.0", "symfony/console": "^7.3|^8.0", @@ -11222,74 +11222,72 @@ "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^11.5.53", - "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/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/expression-language": "^7.3|^8.0", "symfony/security-core": "^7.3|^8.0", "symfony/translation": "^7.3|^8.0", @@ -11327,7 +11325,7 @@ ], "description": "Integration bundle for Symfony AI components", "support": { - "source": "https://github.com/symfony/ai-bundle/tree/v0.9.0" + "source": "https://github.com/symfony/ai-bundle/tree/v0.8.0" }, "funding": [ { @@ -11347,25 +11345,25 @@ "type": "tidelift" } ], - "time": "2026-05-16T08:40:45+00:00" + "time": "2026-04-20T21:23:24+00:00" }, { "name": "symfony/ai-generic-platform", - "version": "v0.9.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-generic-platform.git", - "reference": "8887d12b8ea97d079c5c97de4aebb19f42c58dc5" + "reference": "2e358c0e88c676fad0b61b3df715f9822d29a7e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-generic-platform/zipball/8887d12b8ea97d079c5c97de4aebb19f42c58dc5", - "reference": "8887d12b8ea97d079c5c97de4aebb19f42c58dc5", + "url": "https://api.github.com/repos/symfony/ai-generic-platform/zipball/2e358c0e88c676fad0b61b3df715f9822d29a7e3", + "reference": "2e358c0e88c676fad0b61b3df715f9822d29a7e3", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-platform": "^0.9", + "symfony/ai-platform": "^0.8", "symfony/http-client": "^7.3|^8.0" }, "require-dev": { @@ -11412,7 +11410,7 @@ "platform" ], "support": { - "source": "https://github.com/symfony/ai-generic-platform/tree/v0.9.0" + "source": "https://github.com/symfony/ai-generic-platform/tree/v0.8.0" }, "funding": [ { @@ -11432,26 +11430,26 @@ "type": "tidelift" } ], - "time": "2026-05-16T01:01:33+00:00" + "time": "2026-04-20T21:23:24+00:00" }, { "name": "symfony/ai-lm-studio-platform", - "version": "v0.9.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-lm-studio-platform.git", - "reference": "9e53e56c8c3a04dddb955088b40904e747ec3981" + "reference": "ad1c046dd9e7d6e474bc86554443e2d9400a7826" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-lm-studio-platform/zipball/9e53e56c8c3a04dddb955088b40904e747ec3981", - "reference": "9e53e56c8c3a04dddb955088b40904e747ec3981", + "url": "https://api.github.com/repos/symfony/ai-lm-studio-platform/zipball/ad1c046dd9e7d6e474bc86554443e2d9400a7826", + "reference": "ad1c046dd9e7d6e474bc86554443e2d9400a7826", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-generic-platform": "^0.9", - "symfony/ai-platform": "^0.9", + "symfony/ai-generic-platform": "^0.8", + "symfony/ai-platform": "^0.8", "symfony/http-client": "^7.3|^8.0" }, "require-dev": { @@ -11499,7 +11497,7 @@ "platform" ], "support": { - "source": "https://github.com/symfony/ai-lm-studio-platform/tree/v0.9.0" + "source": "https://github.com/symfony/ai-lm-studio-platform/tree/v0.8.0" }, "funding": [ { @@ -11519,34 +11517,33 @@ "type": "tidelift" } ], - "time": "2026-05-16T01:01:33+00:00" + "time": "2026-04-20T21:23:24+00:00" }, { "name": "symfony/ai-open-router-platform", - "version": "v0.9.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/symfony/ai-open-router-platform.git", - "reference": "7e2b560c86f618cd5d33f9f0c581d83bebc9802f" + "reference": "eb5ed3176b78bc489bf325c5d6bc4efc255804be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-open-router-platform/zipball/7e2b560c86f618cd5d33f9f0c581d83bebc9802f", - "reference": "7e2b560c86f618cd5d33f9f0c581d83bebc9802f", + "url": "https://api.github.com/repos/symfony/ai-open-router-platform/zipball/eb5ed3176b78bc489bf325c5d6bc4efc255804be", + "reference": "eb5ed3176b78bc489bf325c5d6bc4efc255804be", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/ai-generic-platform": "^0.9", - "symfony/ai-platform": "^0.9", + "symfony/ai-generic-platform": "^0.8", + "symfony/ai-platform": "^0.8", "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", - "symfony/console": "^7.4|^8.0" + "phpunit/phpunit": "^11.5.53" }, "type": "symfony-ai-platform", "extra": { @@ -11586,7 +11583,7 @@ "platform" ], "support": { - "source": "https://github.com/symfony/ai-open-router-platform/tree/v0.9.0" + "source": "https://github.com/symfony/ai-open-router-platform/tree/v0.8.0" }, "funding": [ { @@ -11606,20 +11603,20 @@ "type": "tidelift" } ], - "time": "2026-05-16T01:01:33+00:00" + "time": "2026-04-20T21:23:24+00:00" }, { "name": "symfony/ai-platform", - "version": "v0.9.0", + "version": "v0.8.1", "source": { "type": "git", "url": "https://github.com/symfony/ai-platform.git", - "reference": "fb55ebdf20bbe30af6752a0ce6a25abc56b2b625" + "reference": "86ed9396f53cad02b5d1ca8092956ea74f65823f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ai-platform/zipball/fb55ebdf20bbe30af6752a0ce6a25abc56b2b625", - "reference": "fb55ebdf20bbe30af6752a0ce6a25abc56b2b625", + "url": "https://api.github.com/repos/symfony/ai-platform/zipball/86ed9396f53cad02b5d1ca8092956ea74f65823f", + "reference": "86ed9396f53cad02b5d1ca8092956ea74f65823f", "shasum": "" }, "require": { @@ -11718,7 +11715,7 @@ "voyage" ], "support": { - "source": "https://github.com/symfony/ai-platform/tree/v0.9.0" + "source": "https://github.com/symfony/ai-platform/tree/v0.8.1" }, "funding": [ { @@ -11738,7 +11735,7 @@ "type": "tidelift" } ], - "time": "2026-05-15T19:15:50+00:00" + "time": "2026-04-20T21:28:38+00:00" }, { "name": "symfony/apache-pack", @@ -11841,16 +11838,16 @@ }, { "name": "symfony/cache", - "version": "v7.4.12", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "902d621e0b6ef0ebeaa133770b5c339a19328589" + "reference": "8c5fbb4b5bc7a878f7ce66f1b7e29653c404984b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/902d621e0b6ef0ebeaa133770b5c339a19328589", - "reference": "902d621e0b6ef0ebeaa133770b5c339a19328589", + "url": "https://api.github.com/repos/symfony/cache/zipball/8c5fbb4b5bc7a878f7ce66f1b7e29653c404984b", + "reference": "8c5fbb4b5bc7a878f7ce66f1b7e29653c404984b", "shasum": "" }, "require": { @@ -11921,7 +11918,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.12" + "source": "https://github.com/symfony/cache/tree/v7.4.10" }, "funding": [ { @@ -11941,7 +11938,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-05-05T08:23:16+00:00" }, { "name": "symfony/cache-contracts", @@ -12182,16 +12179,16 @@ }, { "name": "symfony/console", - "version": "v7.4.11", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075" + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ed0107e43ab452aa77ae99e005b95e56b556e075", - "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075", + "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", "shasum": "" }, "require": { @@ -12256,7 +12253,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.11" + "source": "https://github.com/symfony/console/tree/v7.4.9" }, "funding": [ { @@ -12276,7 +12273,7 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:04:42+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/css-selector", @@ -12617,16 +12614,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.12", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b59b59122690976550fd142c23fab62c84738db6" + "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b59b59122690976550fd142c23fab62c84738db6", - "reference": "b59b59122690976550fd142c23fab62c84738db6", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2918e7c2ba964defca1f5b69c6f74886529e2dc8", + "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8", "shasum": "" }, "require": { @@ -12665,7 +12662,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.12" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.8" }, "funding": [ { @@ -12685,20 +12682,20 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/dotenv", - "version": "v7.4.11", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "82e9b1355c68ef7b96397dbd34cc75a92eebae7c" + "reference": "ba757a8564a0ccac1a26a859b83295645020ea68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/82e9b1355c68ef7b96397dbd34cc75a92eebae7c", - "reference": "82e9b1355c68ef7b96397dbd34cc75a92eebae7c", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/ba757a8564a0ccac1a26a859b83295645020ea68", + "reference": "ba757a8564a0ccac1a26a859b83295645020ea68", "shasum": "" }, "require": { @@ -12743,7 +12740,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v7.4.11" + "source": "https://github.com/symfony/dotenv/tree/v7.4.9" }, "funding": [ { @@ -12763,7 +12760,7 @@ "type": "tidelift" } ], - "time": "2026-05-11T13:02:51+00:00" + "time": "2026-04-29T13:21:53+00:00" }, { "name": "symfony/error-handler", @@ -13082,16 +13079,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.11", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50" + "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50", - "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/dcd8f96bcdc0f128ec406c765cc066c6035d1be3", + "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3", "shasum": "" }, "require": { @@ -13128,7 +13125,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.11" + "source": "https://github.com/symfony/filesystem/tree/v7.4.9" }, "funding": [ { @@ -13148,7 +13145,7 @@ "type": "tidelift" } ], - "time": "2026-05-11T16:38:44+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { "name": "symfony/finder", @@ -13396,16 +13393,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.4.11", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "637f5cac1ac2698a012b41610215bf366004295f" + "reference": "4b9cb207d72b2e4793f28a3c62ea0865098bea20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/637f5cac1ac2698a012b41610215bf366004295f", - "reference": "637f5cac1ac2698a012b41610215bf366004295f", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/4b9cb207d72b2e4793f28a3c62ea0865098bea20", + "reference": "4b9cb207d72b2e4793f28a3c62ea0865098bea20", "shasum": "" }, "require": { @@ -13530,7 +13527,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.11" + "source": "https://github.com/symfony/framework-bundle/tree/v7.4.10" }, "funding": [ { @@ -13550,7 +13547,7 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:04:42+00:00" + "time": "2026-05-05T11:48:54+00:00" }, { "name": "symfony/http-client", @@ -13819,16 +13816,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.4.12", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d" + "reference": "23486f59234c6fd6e8f1bec97124f3829d686627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", - "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/23486f59234c6fd6e8f1bec97124f3829d686627", + "reference": "23486f59234c6fd6e8f1bec97124f3829d686627", "shasum": "" }, "require": { @@ -13914,7 +13911,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.12" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.10" }, "funding": [ { @@ -13934,7 +13931,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T09:27:11+00:00" + "time": "2026-05-06T12:07:34+00:00" }, { "name": "symfony/intl", @@ -14028,16 +14025,16 @@ }, { "name": "symfony/mailer", - "version": "v7.4.12", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "5cefb712a25f320579615ba9e1942abaeade7dff" + "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/5cefb712a25f320579615ba9e1942abaeade7dff", - "reference": "5cefb712a25f320579615ba9e1942abaeade7dff", + "url": "https://api.github.com/repos/symfony/mailer/zipball/f6ea532250b476bfc1b56699b388a1bdbf168f62", + "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62", "shasum": "" }, "require": { @@ -14088,7 +14085,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.12" + "source": "https://github.com/symfony/mailer/tree/v7.4.8" }, "funding": [ { @@ -14108,29 +14105,28 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/mcp-bundle", - "version": "v0.9.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/symfony/mcp-bundle.git", - "reference": "654f639e94f4d7694771e6628380a9ff04c9d9c1" + "reference": "b8db100e64fcb2d651e3bfd6c6ab70ad3599833d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mcp-bundle/zipball/654f639e94f4d7694771e6628380a9ff04c9d9c1", - "reference": "654f639e94f4d7694771e6628380a9ff04c9d9c1", + "url": "https://api.github.com/repos/symfony/mcp-bundle/zipball/b8db100e64fcb2d651e3bfd6c6ab70ad3599833d", + "reference": "b8db100e64fcb2d651e3bfd6c6ab70ad3599833d", "shasum": "" }, "require": { - "mcp/sdk": "^0.5", + "mcp/sdk": "^0.4", "php-http/discovery": "^1.20", "symfony/config": "^7.3|^8.0", "symfony/console": "^7.3|^8.0", "symfony/dependency-injection": "^7.3|^8.0", - "symfony/finder": "^7.3|^8.0", "symfony/framework-bundle": "^7.3|^8.0", "symfony/http-foundation": "^7.3|^8.0", "symfony/http-kernel": "^7.3|^8.0", @@ -14173,7 +14169,7 @@ ], "description": "Symfony integration bundle for Model Context Protocol (via official mcp/sdk)", "support": { - "source": "https://github.com/symfony/mcp-bundle/tree/v0.9.0" + "source": "https://github.com/symfony/mcp-bundle/tree/v0.8.0" }, "funding": [ { @@ -14193,20 +14189,20 @@ "type": "tidelift" } ], - "time": "2026-05-15T23:41:17+00:00" + "time": "2026-04-12T00:28:34+00:00" }, { "name": "symfony/mime", - "version": "v7.4.12", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470" + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b198dd66c211c97119bcaaff7c13431dbbb5e470", - "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470", + "url": "https://api.github.com/repos/symfony/mime/zipball/2d550c4758ba4c47519a6667c36553d535705b0c", + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c", "shasum": "" }, "require": { @@ -14262,7 +14258,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.12" + "source": "https://github.com/symfony/mime/tree/v7.4.9" }, "funding": [ { @@ -14282,20 +14278,20 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-04-29T13:21:53+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v7.4.12", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "20bb2345ac7a9dd57724b6b7ada92c6d7d67b4b8" + "reference": "20366bceee51838144a14805204bb792cb3d09f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/20bb2345ac7a9dd57724b6b7ada92c6d7d67b4b8", - "reference": "20bb2345ac7a9dd57724b6b7ada92c6d7d67b4b8", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/20366bceee51838144a14805204bb792cb3d09f2", + "reference": "20366bceee51838144a14805204bb792cb3d09f2", "shasum": "" }, "require": { @@ -14345,7 +14341,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.12" + "source": "https://github.com/symfony/monolog-bridge/tree/v7.4.9" }, "funding": [ { @@ -14365,7 +14361,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-04-29T13:21:53+00:00" }, { "name": "symfony/monolog-bundle", @@ -14751,16 +14747,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", "shasum": "" }, "require": { @@ -14809,7 +14805,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" }, "funding": [ { @@ -14829,20 +14825,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T05:58:03+00:00" + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.38.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "445c90e341fccda10311019cf82ff73bb7343945" + "reference": "3510b63d07376b04e57e27e82607d468bb134f78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/445c90e341fccda10311019cf82ff73bb7343945", - "reference": "445c90e341fccda10311019cf82ff73bb7343945", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/3510b63d07376b04e57e27e82607d468bb134f78", + "reference": "3510b63d07376b04e57e27e82607d468bb134f78", "shasum": "" }, "require": { @@ -14897,7 +14893,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.38.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.37.0" }, "funding": [ { @@ -14917,20 +14913,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T11:52:53+00:00" + "time": "2026-04-10T16:50:15+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.38.1", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "dc21118016c039a66235cf93d96b435ffb282412" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", - "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -14984,7 +14980,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" }, "funding": [ { @@ -15004,20 +15000,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T15:22:23+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { @@ -15069,7 +15065,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" }, "funding": [ { @@ -15089,20 +15085,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:48:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.38.1", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "8339098cae28673c15cce00d80734af0453054e2" + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", - "reference": "8339098cae28673c15cce00d80734af0453054e2", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", "shasum": "" }, "require": { @@ -15149,7 +15145,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" }, "funding": [ { @@ -15169,20 +15165,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.38.1", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", "shasum": "" }, "require": { @@ -15229,7 +15225,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" }, "funding": [ { @@ -15249,20 +15245,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-04-10T18:47:49+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", "shasum": "" }, "require": { @@ -15309,7 +15305,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" }, "funding": [ { @@ -15329,7 +15325,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T02:25:22+00:00" + "time": "2026-04-26T13:10:57+00:00" }, { "name": "symfony/polyfill-uuid", @@ -15416,16 +15412,16 @@ }, { "name": "symfony/process", - "version": "v7.4.11", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0" + "reference": "60f19cd3badc8de688421e21e4305eba50f8089a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d9593c9efa40499eb078b81144de42cbc28a31f0", - "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0", + "url": "https://api.github.com/repos/symfony/process/zipball/60f19cd3badc8de688421e21e4305eba50f8089a", + "reference": "60f19cd3badc8de688421e21e4305eba50f8089a", "shasum": "" }, "require": { @@ -15457,7 +15453,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.11" + "source": "https://github.com/symfony/process/tree/v7.4.8" }, "funding": [ { @@ -15477,7 +15473,7 @@ "type": "tidelift" } ], - "time": "2026-05-11T16:55:21+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/property-access", @@ -15814,16 +15810,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.12", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204" + "reference": "287771d8bc86eacb30678dd10eda6c64a859951f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", - "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", + "url": "https://api.github.com/repos/symfony/routing/zipball/287771d8bc86eacb30678dd10eda6c64a859951f", + "reference": "287771d8bc86eacb30678dd10eda6c64a859951f", "shasum": "" }, "require": { @@ -15875,7 +15871,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.12" + "source": "https://github.com/symfony/routing/tree/v7.4.9" }, "funding": [ { @@ -15895,20 +15891,20 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/runtime", - "version": "v7.4.12", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "0b032fa77359745db793df5aff626779180c5f3b" + "reference": "6d792a64fec1eae2f011cfe9ab5978a9eab3071e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/0b032fa77359745db793df5aff626779180c5f3b", - "reference": "0b032fa77359745db793df5aff626779180c5f3b", + "url": "https://api.github.com/repos/symfony/runtime/zipball/6d792a64fec1eae2f011cfe9ab5978a9eab3071e", + "reference": "6d792a64fec1eae2f011cfe9ab5978a9eab3071e", "shasum": "" }, "require": { @@ -15921,7 +15917,6 @@ "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" @@ -15959,7 +15954,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.4.12" + "source": "https://github.com/symfony/runtime/tree/v7.4.8" }, "funding": [ { @@ -15979,20 +15974,20 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/security-bundle", - "version": "v7.4.12", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "6f6f859b437fb95028addfa21b417d25daca86d5" + "reference": "6f73fdfd9ad23bf24b6f6c8d35be3ea6853d91af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/6f6f859b437fb95028addfa21b417d25daca86d5", - "reference": "6f6f859b437fb95028addfa21b417d25daca86d5", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/6f73fdfd9ad23bf24b6f6c8d35be3ea6853d91af", + "reference": "6f73fdfd9ad23bf24b6f6c8d35be3ea6853d91af", "shasum": "" }, "require": { @@ -16071,7 +16066,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.12" + "source": "https://github.com/symfony/security-bundle/tree/v7.4.8" }, "funding": [ { @@ -16091,20 +16086,20 @@ "type": "tidelift" } ], - "time": "2026-05-15T07:14:02+00:00" + "time": "2026-03-30T13:54:39+00:00" }, { "name": "symfony/security-core", - "version": "v7.4.12", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "efff84605474ec682c7d9c6278088811e6f3caaa" + "reference": "23e0cd6615661e33e53faf714bf6a130c2f75c25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/efff84605474ec682c7d9c6278088811e6f3caaa", - "reference": "efff84605474ec682c7d9c6278088811e6f3caaa", + "url": "https://api.github.com/repos/symfony/security-core/zipball/23e0cd6615661e33e53faf714bf6a130c2f75c25", + "reference": "23e0cd6615661e33e53faf714bf6a130c2f75c25", "shasum": "" }, "require": { @@ -16162,7 +16157,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.4.12" + "source": "https://github.com/symfony/security-core/tree/v7.4.8" }, "funding": [ { @@ -16182,7 +16177,7 @@ "type": "tidelift" } ], - "time": "2026-05-15T06:48:59+00:00" + "time": "2026-03-31T07:00:19+00:00" }, { "name": "symfony/security-csrf", @@ -16260,16 +16255,16 @@ }, { "name": "symfony/security-http", - "version": "v7.4.12", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "1fc7ca636cbd2cad29b42cc13c9fd0c681c6efee" + "reference": "a34991b13899de1f953df245395aa2196f9bc113" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/1fc7ca636cbd2cad29b42cc13c9fd0c681c6efee", - "reference": "1fc7ca636cbd2cad29b42cc13c9fd0c681c6efee", + "url": "https://api.github.com/repos/symfony/security-http/zipball/a34991b13899de1f953df245395aa2196f9bc113", + "reference": "a34991b13899de1f953df245395aa2196f9bc113", "shasum": "" }, "require": { @@ -16328,7 +16323,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.4.12" + "source": "https://github.com/symfony/security-http/tree/v7.4.9" }, "funding": [ { @@ -16348,7 +16343,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/serializer", @@ -16682,16 +16677,16 @@ }, { "name": "symfony/string", - "version": "v7.4.11", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15" + "reference": "114ac57257d75df748eda23dd003878080b8e688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", - "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", "shasum": "" }, "require": { @@ -16749,7 +16744,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.11" + "source": "https://github.com/symfony/string/tree/v7.4.8" }, "funding": [ { @@ -16769,7 +16764,7 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:04:42+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/translation", @@ -16955,16 +16950,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.4.12", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "81663873d946531129c76c65e80b681ce99c0e89" + "reference": "ac43e7e59298ed1ce98c8d228b651d46e907d02c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/81663873d946531129c76c65e80b681ce99c0e89", - "reference": "81663873d946531129c76c65e80b681ce99c0e89", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ac43e7e59298ed1ce98c8d228b651d46e907d02c", + "reference": "ac43e7e59298ed1ce98c8d228b651d46e907d02c", "shasum": "" }, "require": { @@ -16980,7 +16975,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.37|>7,<7.4.9|>8.0,<8.0.9", + "symfony/mime": "<6.4.36|>7,<7.4.8|>8.0,<8.0.8", "symfony/serializer": "<6.4", "symfony/translation": "<6.4", "symfony/workflow": "<6.4" @@ -17001,7 +16996,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.37|^7.4.9|^8.0.9", + "symfony/mime": "^6.4.36|^7.4.8|^8.0.8", "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/routing": "^6.4|^7.0|^8.0", @@ -17046,7 +17041,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.12" + "source": "https://github.com/symfony/twig-bridge/tree/v7.4.8" }, "funding": [ { @@ -17066,7 +17061,7 @@ "type": "tidelift" } ], - "time": "2026-04-29T17:13:54+00:00" + "time": "2026-03-30T15:17:09+00:00" }, { "name": "symfony/twig-bundle", @@ -17940,16 +17935,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.12", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "8b6952b56ca6417f25f7a65758cadd0ce02edc51" + "reference": "c660d6538545a3e8e65a5621ee3d7a6d352892c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/8b6952b56ca6417f25f7a65758cadd0ce02edc51", - "reference": "8b6952b56ca6417f25f7a65758cadd0ce02edc51", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c660d6538545a3e8e65a5621ee3d7a6d352892c7", + "reference": "c660d6538545a3e8e65a5621ee3d7a6d352892c7", "shasum": "" }, "require": { @@ -17992,7 +17987,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.12" + "source": "https://github.com/symfony/yaml/tree/v7.4.10" }, "funding": [ { @@ -18012,7 +18007,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-05-05T08:01:55+00:00" }, { "name": "symplify/easy-coding-standard", @@ -18066,16 +18061,16 @@ }, { "name": "tecnickcom/tc-lib-barcode", - "version": "2.7.0", + "version": "2.4.39", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-barcode.git", - "reference": "4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb" + "reference": "11886fb5a44ec0f6e77302439e9ebf55034383fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb", - "reference": "4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/11886fb5a44ec0f6e77302439e9ebf55034383fa", + "reference": "11886fb5a44ec0f6e77302439e9ebf55034383fa", "shasum": "" }, "require": { @@ -18083,13 +18078,15 @@ "ext-date": "*", "ext-gd": "*", "ext-pcre": "*", - "php": ">=8.2", - "tecnickcom/tc-lib-color": "^2.7" + "php": ">=8.1", + "tecnickcom/tc-lib-color": "^2.5" }, "require-dev": { "pdepend/pdepend": "^2.16", "phpcompatibility/php-compatibility": "^10.0.0@dev", - "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5" + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5 || ^10.5", + "squizlabs/php_codesniffer": "^4.0" }, "type": "library", "autoload": { @@ -18161,30 +18158,32 @@ "type": "github" } ], - "time": "2026-05-22T07:09:18+00:00" + "time": "2026-05-01T19:04:12+00:00" }, { "name": "tecnickcom/tc-lib-color", - "version": "2.8.0", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-color.git", - "reference": "6947cc9fffe23a21642279b8ab73a43f3311c5f9" + "reference": "136d522f1640723e490b79171e910e647403d971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/6947cc9fffe23a21642279b8ab73a43f3311c5f9", - "reference": "6947cc9fffe23a21642279b8ab73a43f3311c5f9", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/136d522f1640723e490b79171e910e647403d971", + "reference": "136d522f1640723e490b79171e910e647403d971", "shasum": "" }, "require": { "ext-pcre": "*", - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { "pdepend/pdepend": "^2.16", "phpcompatibility/php-compatibility": "^10.0.0@dev", - "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5" + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5 || ^10.5", + "squizlabs/php_codesniffer": "^4.0" }, "type": "library", "autoload": { @@ -18229,7 +18228,7 @@ "type": "github" } ], - "time": "2026-05-22T06:55:57+00:00" + "time": "2026-05-01T19:02:25+00:00" }, { "name": "thecodingmachine/safe", @@ -18479,16 +18478,16 @@ }, { "name": "twig/cssinliner-extra", - "version": "v3.26.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "1b0dc906bbad7226c967bd325e99cccb1a850c4b" + "reference": "c25fa18b09a418e4d1454ec291f9406f630675ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/1b0dc906bbad7226c967bd325e99cccb1a850c4b", - "reference": "1b0dc906bbad7226c967bd325e99cccb1a850c4b", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/c25fa18b09a418e4d1454ec291f9406f630675ba", + "reference": "c25fa18b09a418e4d1454ec291f9406f630675ba", "shasum": "" }, "require": { @@ -18532,7 +18531,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.26.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.24.0" }, "funding": [ { @@ -18544,7 +18543,7 @@ "type": "tidelift" } ], - "time": "2026-05-15T13:14:14+00:00" + "time": "2025-12-02T14:45:16+00:00" }, { "name": "twig/extra-bundle", @@ -18690,16 +18689,16 @@ }, { "name": "twig/inky-extra", - "version": "v3.26.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "0a8b24f0d0247bf6dc5e2af0c0ab09c0c4e5343e" + "reference": "6bdca65a38167f7bd0ad7ea04819098d465a5cc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/0a8b24f0d0247bf6dc5e2af0c0ab09c0c4e5343e", - "reference": "0a8b24f0d0247bf6dc5e2af0c0ab09c0c4e5343e", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/6bdca65a38167f7bd0ad7ea04819098d465a5cc4", + "reference": "6bdca65a38167f7bd0ad7ea04819098d465a5cc4", "shasum": "" }, "require": { @@ -18744,7 +18743,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.26.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.24.0" }, "funding": [ { @@ -18756,20 +18755,20 @@ "type": "tidelift" } ], - "time": "2026-05-15T13:14:14+00:00" + "time": "2025-12-02T14:45:16+00:00" }, { "name": "twig/intl-extra", - "version": "v3.26.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "98f5ad5bff13230fcd2d834d9e79b50adf3ccda9" + "reference": "32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/98f5ad5bff13230fcd2d834d9e79b50adf3ccda9", - "reference": "98f5ad5bff13230fcd2d834d9e79b50adf3ccda9", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f", + "reference": "32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f", "shasum": "" }, "require": { @@ -18808,7 +18807,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.26.0" + "source": "https://github.com/twigphp/intl-extra/tree/v3.24.0" }, "funding": [ { @@ -18820,20 +18819,20 @@ "type": "tidelift" } ], - "time": "2026-05-19T20:44:48+00:00" + "time": "2026-01-17T13:57:47+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.26.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "e3f3fd0836eb6c39457da22c8a76abaac62692b9" + "reference": "67a11120356e034a5bbc70c5b9b9a4d0f31ca06e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/e3f3fd0836eb6c39457da22c8a76abaac62692b9", - "reference": "e3f3fd0836eb6c39457da22c8a76abaac62692b9", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/67a11120356e034a5bbc70c5b9b9a4d0f31ca06e", + "reference": "67a11120356e034a5bbc70c5b9b9a4d0f31ca06e", "shasum": "" }, "require": { @@ -18880,7 +18879,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.26.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.24.0" }, "funding": [ { @@ -18892,7 +18891,7 @@ "type": "tidelift" } ], - "time": "2026-05-15T13:14:02+00:00" + "time": "2026-02-07T08:07:38+00:00" }, { "name": "twig/string-extra", @@ -18963,16 +18962,16 @@ }, { "name": "twig/twig", - "version": "v3.26.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "1fcae487b180d78e6351f4e0afa91f9eab96a2bc" + "reference": "a6769aefb305efef849dc25c9fd1653358c148f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/1fcae487b180d78e6351f4e0afa91f9eab96a2bc", - "reference": "1fcae487b180d78e6351f4e0afa91f9eab96a2bc", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6769aefb305efef849dc25c9fd1653358c148f0", + "reference": "a6769aefb305efef849dc25c9fd1653358c148f0", "shasum": "" }, "require": { @@ -19027,7 +19026,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.26.0" + "source": "https://github.com/twigphp/Twig/tree/v3.24.0" }, "funding": [ { @@ -19039,7 +19038,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:31:59+00:00" + "time": "2026-03-17T21:31:11+00:00" }, { "name": "ua-parser/uap-php", @@ -19177,16 +19176,16 @@ }, { "name": "web-auth/webauthn-lib", - "version": "5.3.4", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "dbb2d7a03db5893da2ef1f2898063ab8f7792838" + "reference": "a272f254c056fb3d6c80a4801d3c7c5fedc6a08d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/dbb2d7a03db5893da2ef1f2898063ab8f7792838", - "reference": "dbb2d7a03db5893da2ef1f2898063ab8f7792838", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/a272f254c056fb3d6c80a4801d3c7c5fedc6a08d", + "reference": "a272f254c056fb3d6c80a4801d3c7c5fedc6a08d", "shasum": "" }, "require": { @@ -19247,7 +19246,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/5.3.4" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.3.2" }, "funding": [ { @@ -19259,20 +19258,20 @@ "type": "patreon" } ], - "time": "2026-05-18T11:59:46+00:00" + "time": "2026-05-01T12:14:37+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "5.3.4", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "7bf9d0e5e1f6d6bcad97c6bd93dc11a61d6fbd83" + "reference": "1d20af98b50810e8776c52b671201b6bb73ea981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/7bf9d0e5e1f6d6bcad97c6bd93dc11a61d6fbd83", - "reference": "7bf9d0e5e1f6d6bcad97c6bd93dc11a61d6fbd83", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/1d20af98b50810e8776c52b671201b6bb73ea981", + "reference": "1d20af98b50810e8776c52b671201b6bb73ea981", "shasum": "" }, "require": { @@ -19330,7 +19329,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.3.4" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.3.2" }, "funding": [ { @@ -19342,20 +19341,20 @@ "type": "patreon" } ], - "time": "2026-05-24T09:55:30+00:00" + "time": "2026-05-04T08:08:16+00:00" }, { "name": "webmozart/assert", - "version": "2.4.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", - "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", "shasum": "" }, "require": { @@ -19371,11 +19370,7 @@ }, "type": "library", "extra": { - "psalm": { - "pluginClass": "Webmozart\\Assert\\PsalmPlugin" - }, "branch-alias": { - "dev-master": "2.0-dev", "dev-feature/2-0": "2.0-dev" } }, @@ -19406,9 +19401,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.4.0" + "source": "https://github.com/webmozarts/assert/tree/2.3.0" }, - "time": "2026-05-20T13:07:01+00:00" + "time": "2026-04-11T10:33:05+00:00" }, { "name": "willdurand/negotiation", @@ -20045,11 +20040,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.56", + "version": "2.1.54", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93a603c9fc3be8c3c93bbc8d22170ad766685537", - "reference": "93a603c9fc3be8c3c93bbc8d22170ad766685537", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd", "shasum": "" }, "require": { @@ -20094,7 +20089,7 @@ "type": "github" } ], - "time": "2026-05-26T17:04:57+00:00" + "time": "2026-04-29T13:31:09+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -20226,16 +20221,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.18", + "version": "2.0.17", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "a12176b639dec54e8bfd0a5ebf5fc36ffe003b5d" + "reference": "fdd0cb5f08d1980c612d6f259d825ea644ed03f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/a12176b639dec54e8bfd0a5ebf5fc36ffe003b5d", - "reference": "a12176b639dec54e8bfd0a5ebf5fc36ffe003b5d", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/fdd0cb5f08d1980c612d6f259d825ea644ed03f4", + "reference": "fdd0cb5f08d1980c612d6f259d825ea644ed03f4", "shasum": "" }, "require": { @@ -20294,9 +20289,9 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.18" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.17" }, - "time": "2026-05-18T14:51:49+00:00" + "time": "2026-05-10T08:14:07+00:00" }, { "name": "phpunit/php-code-coverage", @@ -20757,16 +20752,16 @@ }, { "name": "rector/rector", - "version": "2.4.4", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "4661c582a20f03df585d2e3fdc4af1b83d67a091" + "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/4661c582a20f03df585d2e3fdc4af1b83d67a091", - "reference": "4661c582a20f03df585d2e3fdc4af1b83d67a091", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946", + "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946", "shasum": "" }, "require": { @@ -20805,7 +20800,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.4.4" + "source": "https://github.com/rectorphp/rector/tree/2.4.2" }, "funding": [ { @@ -20813,7 +20808,7 @@ "type": "github" } ], - "time": "2026-05-20T19:30:21+00:00" + "time": "2026-04-16T13:07:34+00:00" }, { "name": "roave/security-advisories", @@ -20821,12 +20816,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "11be66e3adc8bf2c9805209a598ad4b42ee3c0d6" + "reference": "5a7fa9fd3ce6eefbc9b982d6c78d0aa15d328d6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/11be66e3adc8bf2c9805209a598ad4b42ee3c0d6", - "reference": "11be66e3adc8bf2c9805209a598ad4b42ee3c0d6", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5a7fa9fd3ce6eefbc9b982d6c78d0aa15d328d6c", + "reference": "5a7fa9fd3ce6eefbc9b982d6c78d0aa15d328d6c", "shasum": "" }, "conflict": { @@ -20941,13 +20936,13 @@ "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", - "ci4-cms-erp/ci4ms": "<=0.31.8", + "ci4-cms-erp/ci4ms": "<=0.31.7", "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.22", + "cockpit-hq/cockpit": "<2.14", + "code16/sharp": "<9.20", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.10", "codeigniter4/framework": "<4.6.2", @@ -20957,7 +20952,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.28|>=2.3,<2.9.8", + "composer/composer": "<2.2.27|>=2.3,<2.9.6", "concrete5/concrete5": "<9.4.8", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", @@ -20967,7 +20962,7 @@ "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|==5", + "coreshop/core-shop": "<4.1.9", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", "couleurcitron/tarteaucitron-wp": "<0.3", @@ -21015,7 +21010,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": "<=23.0.2", + "dolibarr/dolibarr": "<=22.0.4", "dompdf/dompdf": "<2.0.4", "doublethreedigital/guest-entries": "<3.1.2", "dreamfactory/df-core": "<1.0.4", @@ -21072,7 +21067,6 @@ "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", @@ -21137,7 +21131,6 @@ "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", @@ -21146,10 +21139,10 @@ "funadmin/funadmin": "<=7.1.0.0-RC6", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "georgringer/news": "<11.4.4|>=12,<12.3.2|>=13,<13.0.2|>=14,<14.0.3", + "georgringer/news": "<1.3.3", "geshi/geshi": "<=1.0.9.1", "getformwork/formwork": "<=2.3.3", - "getgrav/grav": "<=2.0.0.0-RC1", + "getgrav/grav": "<2.0.0.0-beta4", "getgrav/grav-plugin-api": "<1.0.0.0-beta15", "getgrav/grav-plugin-form": "<9.1", "getkirby/cms": "<4.9|>=5,<5.4", @@ -21254,14 +21247,13 @@ "kimai/kimai": "<=2.55", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<=1.7", + "knplabs/knp-snappy": "<=1.4.2", "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", @@ -21310,7 +21302,7 @@ "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", "manogi/nova-tiptap": "<=3.2.6", - "mantisbt/mantisbt": "<2.28.2", + "mantisbt/mantisbt": "<2.28.1", "marcwillmann/turn": "<0.3.3", "markhuot/craftql": "<=1.3.7", "marshmallow/nova-tiptap": "<5.7", @@ -21346,7 +21338,6 @@ "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", @@ -21450,7 +21441,7 @@ "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<4.1.3", + "phpmyfaq/phpmyfaq": "<=4.1.1", "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", @@ -21498,7 +21489,7 @@ "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", - "pterodactyl/panel": "<1.12.3", + "pterodactyl/panel": "<1.12.1", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", @@ -21541,11 +21532,9 @@ "scheb/two-factor-bundle": "<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", - "setasign/fpdi": "<2.6.7", + "setasign/fpdi": "<2.6.4", "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", @@ -21577,7 +21566,6 @@ "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", @@ -21610,14 +21598,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.22|>=6,<6.18.1", + "statamic/cms": "<5.73.21|>=6,<6.15", "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", @@ -21635,52 +21623,42 @@ "symbiote/silverstripe-seed": "<6.0.3", "symbiote/silverstripe-versionedfiles": "<=2.0.3", "symfont/process": ">=0", - "symfony/cache": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", "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|>=7.4,<7.4.12|>=8,<8.0.12", + "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/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": ">=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/mime": ">=4.3,<4.3.8", "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|>=1.17.1,<1.38.1", - "symfony/polyfill-intl-idn": ">=1.17.1,<1.38.1", + "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,<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/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "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,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "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/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", "symfony/translation": ">=2,<2.0.17", - "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/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "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|>=7.2.9,<7.4.12|>=8,<8.0.12", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/webhook": ">=6.3,<6.3.8", - "symfony/yaml": ">=2,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", "symphonycms/symphony-2": "<2.6.4", "t3/dce": "<0.11.5|>=2.2,<2.6.2", "t3g/svg-sanitizer": "<1.0.3", @@ -21694,7 +21672,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<4.1.3", + "thorsten/phpmyfaq": "<=4.1.1", "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", @@ -21702,20 +21680,16 @@ "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": "<5.6.2|>=6,<6.6.1|>=7,<7.0.1", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", "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/cssinliner-extra": "<3.26", - "twig/intl-extra": "<3.26", - "twig/markdown-extra": "<3.26", - "twig/twig": "<3.26", + "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", "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", @@ -21757,7 +21731,7 @@ "uvdesk/core-framework": "<=1.1.1", "vanilla/safecurl": "<0.9.2", "verbb/comments": "<1.5.5", - "verbb/formie": "<2.2.20|>=3.0.0.0-beta1,<3.1.24", + "verbb/formie": "<=2.1.43", "verbb/image-resizer": "<2.0.9", "verbb/knock-knock": "<1.2.8", "verot/class.upload.php": "<=2.1.6", @@ -21805,12 +21779,12 @@ "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", "yansongda/pay": "<=3.7.19", - "yeswiki/yeswiki": "<4.6.4", + "yeswiki/yeswiki": "<=4.6", "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.31", - "yiisoft/yii2": "<2.0.55", + "yiisoft/yii2": "<2.0.52", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<=2.0.45", @@ -21900,7 +21874,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T19:41:38+00:00" + "time": "2026-05-08T23:22:52+00:00" }, { "name": "sebastian/cli-parser", @@ -22014,7 +21988,6 @@ "type": "github" } ], - "abandoned": true, "time": "2025-03-19T07:56:08+00:00" }, { @@ -22071,7 +22044,6 @@ "type": "github" } ], - "abandoned": true, "time": "2024-07-03T04:45:54+00:00" }, { @@ -23276,16 +23248,16 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v7.4.12", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "558fe81a383302318d9b92f7661deb731153c86e" + "reference": "36dd8b8c05da059925c5804641aad9159e5b73e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/558fe81a383302318d9b92f7661deb731153c86e", - "reference": "558fe81a383302318d9b92f7661deb731153c86e", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/36dd8b8c05da059925c5804641aad9159e5b73e8", + "reference": "36dd8b8c05da059925c5804641aad9159e5b73e8", "shasum": "" }, "require": { @@ -23342,7 +23314,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.12" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.9" }, "funding": [ { @@ -23362,7 +23334,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "theseer/tokenizer", @@ -23415,9 +23387,17 @@ "time": "2025-11-17T20:03:58+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "mcp/sdk", + "version": "0.5.0.0", + "alias": "0.4.0", + "alias_normalized": "0.4.0.0" + } + ], "minimum-stability": "stable", "stability-flags": { + "api-platform/mcp": 20, "api-platform/metadata": 20, "roave/security-advisories": 20 }, diff --git a/config/reference.php b/config/reference.php index 91694bd9..8d95c97f 100644 --- a/config/reference.php +++ b/config/reference.php @@ -2823,13 +2823,6 @@ 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" @@ -2964,7 +2957,6 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * 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 @@ -3027,7 +3019,6 @@ use Symfony\Component\Config\Loader\ParamConfigurator as 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" diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index 4b5e2b22..223771c0 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -75,15 +75,6 @@ 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 f846f1d1..99636d37 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.17.0", - "marked": "^18.0.0", + "katex": "^0.16.0", + "marked": "^17.0.1", "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..c893ad4b 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 May 4 05:40:05 UTC 2026 # This file contains all footprints available in the offical KiCAD library Audio_Module:Reverb_BTDR-1H Audio_Module:Reverb_BTDR-1V @@ -11883,8 +11883,6 @@ Package_DFN_QFN:Texas_VQFN-HR-20_3x2.5mm_P0.5mm_RQQ0011A Package_DFN_QFN:Texas_VQFN-RHL-20 Package_DFN_QFN:Texas_VQFN-RHL-20_ThermalVias Package_DFN_QFN:Texas_VQFN-RNR0011A-11 -Package_DFN_QFN:Texas_WQFN-40-1EP_3x6mm_P0.4mm_EP1.7x4.5mm -Package_DFN_QFN:Texas_WQFN-40-1EP_3x6mm_P0.4mm_EP1.7x4.5mm_ThermalVias Package_DFN_QFN:Texas_WQFN-MR-100_3x3-DapStencil Package_DFN_QFN:Texas_WQFN-MR-100_ThermalVias_3x3-DapStencil Package_DFN_QFN:Texas_X2QFN-12_1.6x1.6mm_P0.4mm @@ -12958,7 +12956,6 @@ Package_SON:MPS_VSON-6_1x1.5mm_P0.5mm Package_SON:MicroCrystal_C7_SON-8_1.5x3.2mm_P0.9mm Package_SON:Microchip_USON-10-1EP_3x3mm_P0.5mm_EP1.8x2.5mm Package_SON:Microchip_USON-10-1EP_3x3mm_P0.5mm_EP1.8x2.5mm_ThermalVias -Package_SON:NXP_LSON-16-1EP_3.5x4.5mm_P0.5mm_EP2x3.8mm Package_SON:NXP_XSON-16 Package_SON:Nexperia_HUSON-12_USON-12-1EP_1.35x2.5mm_P0.4mm_EP0.4x2mm Package_SON:Nexperia_HUSON-16_USON-16-1EP_1.35x3.3mm_P0.4mm_EP0.4x2.8mm diff --git a/public/kicad/symbols.txt b/public/kicad/symbols.txt index 46a15ee1..f41aa152 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 May 4 05:40:43 UTC 2026 # This file contains all symbols available in the offical KiCAD library 4xxx:14528 4xxx:14529 @@ -7545,7 +7545,6 @@ Driver_FET:ZXGD3003E6 Driver_FET:ZXGD3004E6 Driver_FET:ZXGD3006E6 Driver_FET:ZXGD3009E6 -Driver_LED:AL5819W6 Driver_LED:AL8860MP Driver_LED:AL8860WT Driver_LED:AP3019AKTR @@ -7693,7 +7692,6 @@ Driver_Motor:DRV8311P Driver_Motor:DRV8311S Driver_Motor:DRV8412 Driver_Motor:DRV8432 -Driver_Motor:DRV8434PWP Driver_Motor:DRV8461SPWP Driver_Motor:DRV8662 Driver_Motor:DRV8800PWP @@ -19324,9 +19322,6 @@ Regulator_Switching:MAX15062C Regulator_Switching:MAX1522 Regulator_Switching:MAX1523 Regulator_Switching:MAX1524 -Regulator_Switching:MAX15462A -Regulator_Switching:MAX15462B -Regulator_Switching:MAX15462C Regulator_Switching:MAX17501AxTB Regulator_Switching:MAX17501BxTB Regulator_Switching:MAX17501ExTB @@ -21306,7 +21301,6 @@ Timer_RTC:MCP79512-xMS Timer_RTC:MCP79520-xMS Timer_RTC:MCP79521-xMS Timer_RTC:MCP79522-xMS -Timer_RTC:PCA2131 Timer_RTC:PCF85063ATL Timer_RTC:PCF8523T Timer_RTC:PCF8523TK diff --git a/src/Controller/BrowserPluginController.php b/src/Controller/BrowserPluginController.php deleted file mode 100644 index 1bb95787..00000000 --- a/src/Controller/BrowserPluginController.php +++ /dev/null @@ -1,139 +0,0 @@ -. - */ - -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 28c281d0..817a6651 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -28,7 +28,6 @@ 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; @@ -63,8 +62,7 @@ class InfoProviderController extends AbstractController private readonly PartInfoRetriever $infoRetriever, private readonly ExistingPartFinder $existingPartFinder, private readonly SettingsManagerInterface $settingsManager, - private readonly SettingsFormFactoryInterface $settingsFormFactory, - private readonly SubmittedPageStorage $browserHtmlStorage, + private readonly SettingsFormFactoryInterface $settingsFormFactory ) { @@ -223,7 +221,7 @@ class InfoProviderController extends AbstractController } #[Route('/from_url', name: 'info_providers_from_url')] - public function fromURL(Request $request, CreateFromUrlHelper $fromUrlHelper): Response + public function fromURL(Request $request, GenericWebProvider $provider, CreateFromUrlHelper $fromUrlHelper): Response { $this->denyAccessUnlessGranted('@info_providers.create_parts'); @@ -244,12 +242,6 @@ 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( @@ -257,7 +249,6 @@ class InfoProviderController extends AbstractController providers: [$method], options: [ InfoProviderInterface::OPTION_SKIP_DELEGATION => $skip_delegation, - InfoProviderInterface::OPTION_SUBMITTED_PAGE_TOKEN => $submittedPageToken, ] ); @@ -271,7 +262,6 @@ 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) { @@ -282,7 +272,6 @@ 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 c4c0e526..735a48f8 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -328,12 +328,10 @@ 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 c4a52bc7..5fed1571 100644 --- a/src/Controller/SettingsController.php +++ b/src/Controller/SettingsController.php @@ -23,7 +23,6 @@ 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; @@ -51,9 +50,7 @@ class SettingsController extends AbstractController $settings = $this->settingsManager->createTemporaryCopy(AppSettings::class); //Create a form builder for the settings object - $builder = $this->settingsFormFactory->createSettingsFormBuilder($settings, formOptions: [ - 'warn_on_unsaved_changes' => true, - ]); + $builder = $this->settingsFormFactory->createSettingsFormBuilder($settings); //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 b5beeca0..2d5c4ebc 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -37,7 +37,6 @@ 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; @@ -94,14 +93,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)', @@ -162,7 +161,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 ''; @@ -213,7 +212,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, [ @@ -222,8 +221,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'render' => function ($value, ProjectBOMEntry $context) { $price = $this->projectBuildHelper->getEntryUnitPrice($context); return $this->moneyFormatter->format( - $price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity())) - ->toScale(2, RoundingMode::Up)->toFloat(), + $price->multipliedBy($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 2712cce8..a9f796dd 100644 --- a/src/Doctrine/Types/BigDecimalType.php +++ b/src/Doctrine/Types/BigDecimalType.php @@ -44,7 +44,7 @@ class BigDecimalType extends Type - return BigDecimal::of(is_float($value) ? BigDecimal::fromFloatShortest($value) : $value); + return BigDecimal::of($value); } /** diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 4a811aa0..ce20caf8 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::HalfUp); + return BigDecimal::one()->dividedBy($tmp, $tmp->getScale(), RoundingMode::HALF_UP); } /** diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 58e02c64..553b07a3 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 = is_float($multiplier) ? BigDecimal::fromFloatShortest($multiplier) : BigDecimal::of($multiplier); + $tmp = BigDecimal::of($multiplier); $tmp = $tmp->multipliedBy($this->price); - return $tmp->dividedBy(BigDecimal::fromFloatShortest($this->price_related_quantity), static::PRICE_PRECISION, RoundingMode::HalfUp); + return $tmp->dividedBy($this->price_related_quantity, static::PRICE_PRECISION, RoundingMode::HALF_UP); } /** @@ -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::HalfUp); + $tmp = $new_price->toScale(self::PRICE_PRECISION, RoundingMode::HALF_UP); //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 54cb0406..bf005882 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -57,10 +57,6 @@ 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 deleted file mode 100644 index 1187eb19..00000000 --- a/src/Form/Extension/UnsavedChangesExtension.php +++ /dev/null @@ -1,84 +0,0 @@ -. - */ - -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 a31f2469..6b929486 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -353,7 +353,6 @@ 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 1950391c..189416ff 100644 --- a/src/Form/Type/BigDecimalMoneyType.php +++ b/src/Form/Type/BigDecimalMoneyType.php @@ -59,16 +59,6 @@ class BigDecimalMoneyType extends AbstractType implements DataTransformerInterfa return null; } - 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))); + return BigDecimal::of($value); } } diff --git a/src/Form/Type/BigDecimalNumberType.php b/src/Form/Type/BigDecimalNumberType.php index 04e8e655..c225f0a4 100644 --- a/src/Form/Type/BigDecimalNumberType.php +++ b/src/Form/Type/BigDecimalNumberType.php @@ -59,17 +59,6 @@ class BigDecimalNumberType extends AbstractType implements DataTransformerInterf return null; } - 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))); + return BigDecimal::of($value); } - } diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 6331dfb7..457a6e0b 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -59,10 +59,6 @@ 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 61873615..5568c4cb 100644 --- a/src/Serializer/APIPlatform/SkippableItemNormalizer.php +++ b/src/Serializer/APIPlatform/SkippableItemNormalizer.php @@ -23,13 +23,9 @@ 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\Exception\NotNormalizableValueException; -use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerAwareInterface; @@ -39,10 +35,6 @@ 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 @@ -50,44 +42,13 @@ class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterf public const DISABLE_ITEM_NORMALIZER = 'DISABLE_ITEM_NORMALIZER'; - public function __construct( - private readonly ItemNormalizer $inner, - private readonly IriConverterInterface $iriConverter, - ) { + public function __construct(private readonly ItemNormalizer $inner) + { + } 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); } @@ -126,4 +87,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 c661b0f4..1075141b 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 = min((int) floor((strlen((string) $bytes) - 1) / 3), strlen($sz) - 1); + $factor = (int) floor((strlen((string) $bytes) - 1) / 3); //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 1f25dbe6..b83501fa 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 = min((int) floor(abs($magnitude) / 3), count($prefixes_pos) - 1); + $nearest = (int) floor(abs($magnitude) / 3); $symbol = $prefixes_pos[$nearest]; } else { - $nearest = min((int) round(abs($magnitude) / 3), count($prefixes_neg) - 1); + $nearest = (int) round(abs($magnitude) / 3); $symbol = $prefixes_neg[$nearest]; } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index 08b1c301..64127341 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 e63ec7f1..cab5a49b 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(BigDecimal::fromFloatShortest($packaging_unit))); + $pricedetail->setPrice($price_per_item->multipliedBy($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 deleted file mode 100644 index 0f4fbf5f..00000000 --- a/src/Services/InfoProviderSystem/DTOs/BrowserSubmittedPage.php +++ /dev/null @@ -1,50 +0,0 @@ -. - */ - -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 f5ff144d..6c10f10e 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->dto_to_entity_converter->convertPart($search_result); + return $this->createPart($search_result->provider_key, $search_result->provider_id); } /** * Use the given details to create a part entity */ - public function createPart(string $provider_key, string $part_id, array $options): Part + public function createPart(string $provider_key, string $part_id): Part { - $details = $this->getDetails($provider_key, $part_id, $options); + $details = $this->getDetails($provider_key, $part_id); 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 6539e69b..79f07be8 100644 --- a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php +++ b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php @@ -27,11 +27,12 @@ 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; @@ -61,7 +62,6 @@ 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,17 +142,9 @@ final class AIWebProvider implements InfoProviderInterface return $cacheItem->get(); } - // 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(); - } + // Fetch HTML content + $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); @@ -184,20 +176,9 @@ final class AIWebProvider implements InfoProviderInterface */ private function extractStructuredData(string $html, string $url): string { - 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 '{}'; - } - } + //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); 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 45777f9e..06a9d4c1 100644 --- a/src/Services/InfoProviderSystem/Providers/GenericWebProvider.php +++ b/src/Services/InfoProviderSystem/Providers/GenericWebProvider.php @@ -25,7 +25,6 @@ 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; @@ -58,7 +57,6 @@ 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 @@ -296,17 +294,9 @@ class GenericWebProvider implements InfoProviderInterface } } - // 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(); - } + //Try to get the webpage content + $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 d3895795..a6e073a5 100644 --- a/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php +++ b/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php @@ -30,7 +30,6 @@ 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 deleted file mode 100644 index 5e623f57..00000000 --- a/src/Services/InfoProviderSystem/SubmittedPageStorage.php +++ /dev/null @@ -1,131 +0,0 @@ -. - */ - -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 79d9f8e2..68d962bb 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 = is_float($data) ? BigDecimal::fromFloatShortest($data) : BigDecimal::of($data); + $data = BigDecimal::of($data); } if (!$data instanceof \DateTimeInterface diff --git a/src/Services/Parts/PricedetailHelper.php b/src/Services/Parts/PricedetailHelper.php index e40fc44d..b2e1340f 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::HalfUp); + return $avg->dividedBy($count, Pricedetail::PRICE_PRECISION, RoundingMode::HALF_UP); } /** @@ -213,6 +213,6 @@ class PricedetailHelper $val_target = $val_base->multipliedBy($targetCurrency->getInverseExchangeRate()); } - return $val_target->toScale(Pricedetail::PRICE_PRECISION, RoundingMode::HalfUp); + return $val_target->toScale(Pricedetail::PRICE_PRECISION, RoundingMode::HALF_UP); } } diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index e011d980..ee5b8c68 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(BigDecimal::fromFloatShortest($entry->getQuantity()))->multipliedBy($number_of_builds)); + $total = $total->plus($unit_price->multipliedBy($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::HalfUp); + return $total->dividedBy($number_of_builds, 10, RoundingMode::HALF_UP); } /** @@ -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 927326e5..01925ff8 100644 --- a/src/Services/System/GitVersionInfoProvider.php +++ b/src/Services/System/GitVersionInfoProvider.php @@ -62,9 +62,6 @@ 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 ccc3f19f..6eb7ec13 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::fromFloatShortest($rate->getValue()); + $effective_rate = BigDecimal::of($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::fromFloatShortest($rate->getValue()); - $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HalfUp); + $rate_bd = BigDecimal::of($rate->getValue()); + $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); } $currency->setExchangeRate($effective_rate); diff --git a/src/Settings/InfoProviderSystem/BrowserPluginSettings.php b/src/Settings/InfoProviderSystem/BrowserPluginSettings.php deleted file mode 100644 index 1ad5c50b..00000000 --- a/src/Settings/InfoProviderSystem/BrowserPluginSettings.php +++ /dev/null @@ -1,40 +0,0 @@ -. - */ - -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 96de19cb..3e2a27ef 100644 --- a/src/Settings/InfoProviderSystem/InfoProviderSettings.php +++ b/src/Settings/InfoProviderSystem/InfoProviderSettings.php @@ -37,9 +37,6 @@ 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 3146c5a5..49d4b116 100644 --- a/templates/info_providers/from_url/from_url.html.twig +++ b/templates/info_providers/from_url/from_url.html.twig @@ -33,31 +33,5 @@ {{ 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 c8c087c5..8d66d362 100644 --- a/tests/API/Endpoints/PartEndpointTest.php +++ b/tests/API/Endpoints/PartEndpointTest.php @@ -69,52 +69,4 @@ final class PartEndpointTest extends CrudEndpointTestCase { $this->_testDeleteItem(1); } - - 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]]); - } -} +} \ No newline at end of file diff --git a/tests/Controller/AuthorizationTest.php b/tests/Controller/AuthorizationTest.php deleted file mode 100644 index 4e211301..00000000 --- a/tests/Controller/AuthorizationTest.php +++ /dev/null @@ -1,222 +0,0 @@ -. - */ - -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 deleted file mode 100644 index 8af82ce9..00000000 --- a/tests/Controller/BrowserPluginControllerTest.php +++ /dev/null @@ -1,247 +0,0 @@ -. - */ - -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 deleted file mode 100644 index b07053b9..00000000 --- a/tests/Controller/SelectApiControllerTest.php +++ /dev/null @@ -1,152 +0,0 @@ -. - */ - -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 deleted file mode 100644 index ce2747fa..00000000 --- a/tests/Controller/TypeaheadControllerTest.php +++ /dev/null @@ -1,162 +0,0 @@ -. - */ - -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 deleted file mode 100644 index 0d975ee0..00000000 --- a/tests/EventSubscriber/MaintenanceModeSubscriberTest.php +++ /dev/null @@ -1,103 +0,0 @@ -. - */ - -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 deleted file mode 100644 index ec782b66..00000000 --- a/tests/EventSubscriber/RedirectToHttpsSubscriberTest.php +++ /dev/null @@ -1,101 +0,0 @@ -. - */ - -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 deleted file mode 100644 index f747441f..00000000 --- a/tests/Services/Cache/ElementCacheTagGeneratorTest.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -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 deleted file mode 100644 index 23583db4..00000000 --- a/tests/Services/Cache/UserCacheKeyGeneratorTest.php +++ /dev/null @@ -1,110 +0,0 @@ -. - */ - -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 deleted file mode 100644 index f21511e0..00000000 --- a/tests/Services/EntityURLGeneratorTest.php +++ /dev/null @@ -1,113 +0,0 @@ -. - */ - -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 deleted file mode 100644 index 0b27972f..00000000 --- a/tests/Services/Formatters/MarkdownParserTest.php +++ /dev/null @@ -1,86 +0,0 @@ -. - */ - -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('