diff --git a/.docker/frankenphp/Caddyfile b/.docker/frankenphp/Caddyfile
index f26b6f22..293ab18e 100644
--- a/.docker/frankenphp/Caddyfile
+++ b/.docker/frankenphp/Caddyfile
@@ -51,6 +51,15 @@
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
header ?Permissions-Policy "browsing-topics=()"
+ # Set a strict CSP and nosniff for all static assets not handled by PHP.
+ # ? means "set only if not already present", so PHP responses carrying a Nelmio CSP are left untouched.
+ header ?Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;"
+ header ?X-Content-Type-Options "nosniff"
+
+ # SVG files get a slightly different CSP because they can embed resources and must not be framed.
+ @svg path *.svg *.svg.gz *.svg.br
+ header @svg Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;"
+
# Prevent PHP execution in the media upload directory
@php_in_media path_regexp (?i)^/media/.*\.(php[3-8]?|phar|phtml|pht|phps)$
respond @php_in_media 403
diff --git a/.env b/.env
index 8311abad..8cd39f31 100644
--- a/.env
+++ b/.env
@@ -149,6 +149,16 @@ DISABLE_YEAR2038_BUG_CHECK=0
#TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$'
+###################################################################################
+# Logging settings
+###################################################################################
+
+# The minimum level a deprecation notice must have to be written to the var/log/_deprecations.log file.
+# Deprecation notices are logged with level "info", so this disables the deprecation log by default.
+# Set to debug to log all deprecation notices
+DEPRECATION_LOG_LEVEL=emergency
+
+
###> symfony/lock ###
# Choose one of the stores below
diff --git a/.github/workflows/assets_artifact_build.yml b/.github/workflows/assets_artifact_build.yml
index a74ae7cc..5da304ec 100644
--- a/.github/workflows/assets_artifact_build.yml
+++ b/.github/workflows/assets_artifact_build.yml
@@ -27,7 +27,7 @@ jobs:
APP_ENV: prod
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml
index 210dbc18..d1f53c38 100644
--- a/.github/workflows/docker_build.yml
+++ b/.github/workflows/docker_build.yml
@@ -32,7 +32,7 @@ jobs:
steps:
-
name: Checkout
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
-
name: Docker meta
id: docker_meta
diff --git a/.github/workflows/docker_frankenphp.yml b/.github/workflows/docker_frankenphp.yml
index 36ec322d..fc69b29b 100644
--- a/.github/workflows/docker_frankenphp.yml
+++ b/.github/workflows/docker_frankenphp.yml
@@ -32,7 +32,7 @@ jobs:
steps:
-
name: Checkout
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
-
name: Docker meta
id: docker_meta
diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml
index f47ce87b..cbc42d27 100644
--- a/.github/workflows/static_analysis.yml
+++ b/.github/workflows/static_analysis.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 5b756228..66086104 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -46,7 +46,7 @@ jobs:
if: matrix.db-type == 'postgres'
- name: Checkout
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -129,7 +129,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage
- uses: codecov/codecov-action@v6
+ uses: codecov/codecov-action@v7
with:
env_vars: PHP_VERSION,DB_TYPE
token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
index e848acc1..049de283 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -193,7 +193,7 @@ RUN a2dissite 000-default.conf && \
a2enmod proxy_fcgi setenvif && \
a2enconf php${PHP_VERSION}-fpm && \
a2enconf docker-php && \
- a2enmod rewrite
+ a2enmod rewrite headers
# Install composer and yarn dependencies for Part-DB
USER www-data
diff --git a/VERSION b/VERSION
index 3cf561c0..94f15e9c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.12.1
+2.13.1
diff --git a/assets/controllers/common/dirty_form_controller.js b/assets/controllers/common/dirty_form_controller.js
index aad2e6b0..8e560a3f 100644
--- a/assets/controllers/common/dirty_form_controller.js
+++ b/assets/controllers/common/dirty_form_controller.js
@@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus";
import {visit} from "@hotwired/turbo";
-import * as bootbox from "bootbox";
-import "../../css/components/bootbox_extensions.css";
+import {ConfirmSwal} from "../../helpers/swal";
import "../../css/components/dirty_form.css";
/**
@@ -207,11 +206,10 @@ export default class extends Controller {
}
_confirmNavigation(onConfirm) {
- bootbox.confirm({
- title: this.confirmTitleValue,
- message: this.confirmMessageValue,
- callback: (result) => { if (result) onConfirm(); }
- });
+ ConfirmSwal.fire({
+ titleText: this.confirmTitleValue,
+ text: this.confirmMessageValue,
+ }).then(({isConfirmed}) => { if (isConfirmed) onConfirm(); });
}
_handleLinkClick(event) {
diff --git a/assets/controllers/common/hide_sidebar_controller.js b/assets/controllers/common/hide_sidebar_controller.js
index 4be304ff..c65cdcdf 100644
--- a/assets/controllers/common/hide_sidebar_controller.js
+++ b/assets/controllers/common/hide_sidebar_controller.js
@@ -51,7 +51,7 @@ export default class extends Controller {
//Make the state persistent over reloads
if(localStorage.getItem(STORAGE_KEY) === 'true') {
- sidebarHide();
+ this.hideSidebar();
}
}
diff --git a/assets/controllers/elements/collection_type_controller.js b/assets/controllers/elements/collection_type_controller.js
index 647ed5e5..caeb4122 100644
--- a/assets/controllers/elements/collection_type_controller.js
+++ b/assets/controllers/elements/collection_type_controller.js
@@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus";
-import * as bootbox from "bootbox";
-import "../../css/components/bootbox_extensions.css";
+import {AlertSwal, ConfirmSwal} from "../../helpers/swal";
import accept from "attr-accept";
export default class extends Controller {
@@ -62,7 +61,7 @@ export default class extends Controller {
if(!prototype) {
console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions.");
- bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)");
+ AlertSwal.fire({"text": "You do not have the permissions to create a new element. (No protoype element is set)"});
return;
}
@@ -226,8 +225,10 @@ export default class extends Controller {
}
if(this.deleteMessageValue) {
- bootbox.confirm(this.deleteMessageValue, (result) => {
- if (result) {
+ ConfirmSwal.fire({
+ text: this.deleteMessageValue,
+ }).then(({isConfirmed}) => {
+ if (isConfirmed) {
del();
}
});
diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js
index d945004b..4b84a834 100644
--- a/assets/controllers/elements/datatables/datatables_controller.js
+++ b/assets/controllers/elements/datatables/datatables_controller.js
@@ -38,9 +38,7 @@ import 'datatables.net-colreorder-bs5';
import 'datatables.net-responsive-bs5';
import '../../../js/lib/datatables';
-//import 'datatables.net-select-bs5';
-//Use the local version containing the fix for the select extension
-import '../../../js/lib/dataTables.select.mjs';
+import 'datatables.net-select-bs5';
const EVENT_DT_LOADED = 'dt:loaded';
diff --git a/assets/controllers/elements/datatables/parts_controller.js b/assets/controllers/elements/datatables/parts_controller.js
index c43fa276..cfa386cc 100644
--- a/assets/controllers/elements/datatables/parts_controller.js
+++ b/assets/controllers/elements/datatables/parts_controller.js
@@ -20,7 +20,7 @@
import DatatablesController from "./datatables_controller.js";
import TomSelect from "tom-select";
-import * as bootbox from "bootbox";
+import {ConfirmSwal} from "../../../helpers/swal";
/**
* This is the datatables controller for parts lists
@@ -146,15 +146,17 @@ export default class extends DatatablesController {
bubbles: true, //This line is important, otherwise Turbo will not receive the event
});
- const confirm = bootbox.confirm({
- message: message, title: title, callback: function (result) {
- //If the dialog was confirmed, then submit the form.
- if (result) {
- that._confirmed = true;
- form.dispatchEvent(that._our_event);
- } else {
- that._confirmed = false;
- }
+ ConfirmSwal.fire({
+ titleText: title,
+ text: message,
+ icon: "warning"
+ }).then(({isConfirmed}) => {
+ //If the dialog was confirmed, then submit the form.
+ if (isConfirmed) {
+ that._confirmed = true;
+ form.dispatchEvent(that._our_event);
+ } else {
+ that._confirmed = false;
}
});
}
diff --git a/assets/controllers/elements/delete_btn_controller.js b/assets/controllers/elements/delete_btn_controller.js
index 9ab15f7d..e1b37bcc 100644
--- a/assets/controllers/elements/delete_btn_controller.js
+++ b/assets/controllers/elements/delete_btn_controller.js
@@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus";
-import * as bootbox from "bootbox";
-import "../../css/components/bootbox_extensions.css";
+import {ConfirmSwal} from "../../helpers/swal";
export default class extends Controller
{
@@ -48,32 +47,33 @@ export default class extends Controller
const submitter = event.submitter;
const that = this;
- const confirm = bootbox.confirm({
- message: message, title: title, callback: function (result) {
- //If the dialog was confirmed, then submit the form.
- if (result) {
- //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
- that._confirmed = true;
+ ConfirmSwal.fire({
+ titleText: title,
+ html: message, //Message contains a tag and no user injectable HTML
+ }).then(({isConfirmed}) => {
+ //If the dialog was confirmed, then submit the form.
+ if (isConfirmed) {
+ //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
+ that._confirmed = true;
- //Create a submit button in the form and click it to submit the form
- //Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273
- const submit_btn = document.createElement('button');
- submit_btn.type = 'submit';
- submit_btn.style.display = 'none';
+ //Create a submit button in the form and click it to submit the form
+ //Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273
+ const submit_btn = document.createElement('button');
+ submit_btn.type = 'submit';
+ submit_btn.style.display = 'none';
- //If the clicked button has a value, set it on the submit button
- if (submitter.value) {
- submit_btn.value = submitter.value;
- }
- if (submitter.name) {
- submit_btn.name = submitter.name;
- }
- form.appendChild(submit_btn);
- submit_btn.click();
- } else {
- that._confirmed = false;
+ //If the clicked button has a value, set it on the submit button
+ if (submitter.value) {
+ submit_btn.value = submitter.value;
}
+ if (submitter.name) {
+ submit_btn.name = submitter.name;
+ }
+ form.appendChild(submit_btn);
+ submit_btn.click();
+ } else {
+ that._confirmed = false;
}
});
}
-}
\ No newline at end of file
+}
diff --git a/assets/controllers/elements/link_confirm_controller.js b/assets/controllers/elements/link_confirm_controller.js
index 3d59b492..be226517 100644
--- a/assets/controllers/elements/link_confirm_controller.js
+++ b/assets/controllers/elements/link_confirm_controller.js
@@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus";
-import * as bootbox from "bootbox";
-import "../../css/components/bootbox_extensions.css";
+import {ConfirmSwal} from "../../helpers/swal";
export default class extends Controller
{
@@ -53,20 +52,19 @@ export default class extends Controller
const that = this;
- bootbox.confirm({
- title: this.titleValue,
- message: this.messageValue,
- callback: (result) => {
- if (result) {
- //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
- that._confirmed = true;
+ ConfirmSwal.fire({
+ titleText: this.titleValue,
+ text: this.messageValue,
+ }).then(({isConfirmed}) => {
+ if (isConfirmed) {
+ //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
+ that._confirmed = true;
- //Click the link
- that.element.click();
- } else {
- that._confirmed = false;
- }
+ //Click the link
+ that.element.click();
+ } else {
+ that._confirmed = false;
}
});
}
-}
\ No newline at end of file
+}
diff --git a/assets/controllers/elements/password_strength_estimate_controller.js b/assets/controllers/elements/password_strength_estimate_controller.js
index d9cfbc87..9ad2da1c 100644
--- a/assets/controllers/elements/password_strength_estimate_controller.js
+++ b/assets/controllers/elements/password_strength_estimate_controller.js
@@ -19,12 +19,14 @@
import {Controller} from "@hotwired/stimulus";
-import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
+import { ZxcvbnFactory } from '@zxcvbn-ts/core';
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
+import * as zxcvbnItPackage from '@zxcvbn-ts/language-it';
+import * as zxcvbnPlPackage from '@zxcvbn-ts/language-pl';
import {trans} from '../../translator.js';
/* stimulusFetch: 'lazy' */
@@ -34,6 +36,8 @@ export default class extends Controller {
static targets = ["badge", "warning"]
+ _zxcvbnFactory;
+
_getTranslations() {
//Get the current locale
const locale = document.documentElement.lang;
@@ -43,6 +47,10 @@ export default class extends Controller {
return zxcvbnFrPackage.translations;
} else if (locale.includes('ja')) {
return zxcvbnJaPackage.translations;
+ } else if (locale.includes('it')) {
+ return zxcvbnItPackage.translations;
+ } else if (locale.includes('pl')) {
+ return zxcvbnPlPackage.translations;
}
//Fallback to english
@@ -56,34 +64,39 @@ export default class extends Controller {
//Configure zxcvbn
const options = {
graphs: zxcvbnCommonPackage.adjacencyGraphs,
+ useLevenshtein: true,
dictionary: {
...zxcvbnCommonPackage.dictionary,
// We could use the english dictionary here too, but it is very big. So we just use the common words
- //...zxcvbnEnPackage.dictionary,
+ ...zxcvbnEnPackage.dictionary,
+ ...zxcvbnDePackage.dictionary,
+
+ "partdb": ['part-db', 'partdb', 'part_db', 'part-db-symfony', 'partdb-symfony', 'part_db_symfony'],
},
translations: this._getTranslations(),
};
- zxcvbnOptions.setOptions(options);
+
+ this._zxcvbnFactory = new ZxcvbnFactory(options);
//Add event listener to the password input field
this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));
}
- _onPasswordInput() {
+ async _onPasswordInput() {
//Retrieve the password
const password = this._passwordInput.value;
//Estimate the password strength
- const result = zxcvbn(password);
+ const result = await this._zxcvbnFactory.checkAsync(password);
//Update the badge
this.badgeTarget.parentElement.classList.remove("d-none");
- this._setBadgeToLevel(result.score);
+ this._setBadgeToLevel(result.score, result.crackTimes.onlineNoThrottlingXPerSecond.display);
this.warningTarget.innerHTML = result.feedback.warning;
}
- _setBadgeToLevel(level) {
+ _setBadgeToLevel(level, time = null) {
let text, classes;
switch (level) {
@@ -118,5 +131,11 @@ export default class extends Controller {
//Re-add the classes
this.badgeTarget.classList.add("badge");
this.badgeTarget.classList.add(...classes.split(" "));
+
+ if (time) {
+ this.badgeTarget.setAttribute("title", trans("user.password_strength.crack_time", {"%time%": time}));
+ } else {
+ this.badgeTarget.removeAttribute("title");
+ }
}
}
diff --git a/assets/controllers/pages/reelCalculator_controller.js b/assets/controllers/pages/reelCalculator_controller.js
index a134bf9b..e0b2c4ba 100644
--- a/assets/controllers/pages/reelCalculator_controller.js
+++ b/assets/controllers/pages/reelCalculator_controller.js
@@ -18,7 +18,7 @@
*/
import {Controller} from "@hotwired/stimulus";
-import * as bootbox from "bootbox";
+import {AlertSwal} from "../../helpers/swal";
export default class extends Controller {
@@ -35,12 +35,12 @@ export default class extends Controller {
const part_distance = document.getElementById('reel_part_distance').value;
if (dia_inner == "" || dia_outer == "" || tape_thickness == "") {
- bootbox.alert(this.errorMissingValuesValue);
+ AlertSwal.fire({title: this.errorMissingValuesValue});
return;
}
if (dia_outer**dia_outer < dia_inner**dia_inner) {
- bootbox.alert(this.errorOuterGreaterInnerValue);
+ AlertSwal.fire({title: this.errorOuterGreaterInnerValue});
return;
}
@@ -61,12 +61,12 @@ export default class extends Controller {
return;
}
- var parts_per_meter = 1 / (part_distance / 1000);
+ const parts_per_meter = 1 / (part_distance / 1000);
document.getElementById('result_parts_per_meter').textContent = parts_per_meter.toFixed(2) + ' 1/m';
- var parts_amount = (length/1000) * parts_per_meter;
+ const parts_amount = (length / 1000) * parts_per_meter;
- document.getElementById('result_amount').textContent = Math.floor(parts_amount);
+ document.getElementById('result_amount').textContent = Math.floor(parts_amount).toString();
}
-}
\ No newline at end of file
+}
diff --git a/assets/css/components/swal.css b/assets/css/components/swal.css
new file mode 100644
index 00000000..4c2302a9
--- /dev/null
+++ b/assets/css/components/swal.css
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
+ *
+ * Copyright (C) 2019 - 2026 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 .
+ */
+
+/**
+ * Respect the dark mode of Bootstrap 5 set via data-bs-theme="dark" on the element. This is done by overriding the CSS variables of the bootstrap-5 theme of SweetAlert2.
+ */
+
+html[data-bs-theme="dark"] [data-swal2-theme='bootstrap-5'] {
+ /* POPUP */
+ --swal2-background: #212529;
+ --swal2-color: #fff;
+ --swal2-border: 1px solid #495057;
+
+ /* INPUT */
+ --swal2-input-background: #2b3035;
+ --swal2-input-border: 1px solid #495057;
+ --swal2-input-focus-border: 1px solid #86b7fe;
+ --swal2-input-focus-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+
+ /* VALIDATION MESSAGE */
+ --swal2-validation-message-background: #2c0b0e;
+ --swal2-validation-message-color: #ea868f;
+
+ /* FOOTER */
+ --swal2-footer-border-color: #495057;
+ --swal2-footer-background: #343a40;
+ --swal2-footer-color: #adb5bd;
+
+ /* CLOSE BUTTON */
+ --swal2-close-button-color: #fff;
+
+ /* TOASTS */
+ --swal2-toast-border: 1px solid #495057;
+}
diff --git a/assets/helpers/swal.js b/assets/helpers/swal.js
new file mode 100644
index 00000000..e370a9ed
--- /dev/null
+++ b/assets/helpers/swal.js
@@ -0,0 +1,44 @@
+/*
+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
+ *
+ * Copyright (C) 2019 - 2022 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 Swal from 'sweetalert2';
+import 'sweetalert2/themes/bootstrap-5.css';
+import '../css/components/swal.css'
+import { trans } from '../translator';
+
+const BaseSwal = Swal.mixin({
+ position: "top",
+ theme: "bootstrap-5",
+ confirmButtonText: trans('dialog.btn.ok'),
+ cancelButtonText: trans('dialog.btn.cancel'),
+ denyButtonText: trans('dialog.btn.deny'),
+});
+
+const ConfirmSwal = BaseSwal.mixin({
+ showCancelButton: true,
+ showCloseButton: true,
+ icon: "warning",
+});
+
+const AlertSwal = BaseSwal.mixin({
+ showCloseButton: true,
+ icon: "info",
+});
+
+export { ConfirmSwal, AlertSwal, BaseSwal, BaseSwal as default,};
diff --git a/assets/js/app.js b/assets/js/app.js
index 4dd39581..355fe919 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -30,21 +30,21 @@ import '../css/app/images.css';
// start the Stimulus application
import '../stimulus_bootstrap';
-// Need jQuery? Install it with "yarn add jquery", then uncomment to require it.
-const $ = require('jquery');
+import $ from 'jquery';
//Only include javascript
import '@fortawesome/fontawesome-free/css/all.css'
-require('bootstrap');
+import 'bootstrap';
import "./error_handler";
import "./tab_remember";
import "./register_events";
import "./tristate_checkboxes";
-//Define jquery globally
-global.$ = global.jQuery = require("jquery");
+// Expose jQuery globally so legacy plugins and Bootstrap's jQuery integration
+// can find it on window at runtime.
+global.$ = global.jQuery = $;
//Use the local WASM file for the ZXing library
import {
diff --git a/assets/js/error_handler.js b/assets/js/error_handler.js
index 7f047af9..67695fb9 100644
--- a/assets/js/error_handler.js
+++ b/assets/js/error_handler.js
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
-import * as bootbox from "bootbox";
+import Swal from "../helpers/swal";
/**
* If this class is imported the user is shown an error dialog if he calls an page via Turbo and an error is responded.
@@ -40,21 +40,6 @@ class ErrorHandlerHelper {
_showAlert(statusText, statusCode, location, responseHTML)
{
const httpStatusToText = {
- '200': 'OK',
- '201': 'Created',
- '202': 'Accepted',
- '203': 'Non-Authoritative Information',
- '204': 'No Content',
- '205': 'Reset Content',
- '206': 'Partial Content',
- '300': 'Multiple Choices',
- '301': 'Moved Permanently',
- '302': 'Found',
- '303': 'See Other',
- '304': 'Not Modified',
- '305': 'Use Proxy',
- '306': 'Unused',
- '307': 'Temporary Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
@@ -83,49 +68,67 @@ class ErrorHandlerHelper {
'505': 'HTTP Version Not Supported',
};
- //If the statusText is empty, we use the status code as text
- if (!statusText) {
- statusText = httpStatusToText[statusCode];
- }
-
- //Create error text
- const title = statusText + ' (Status ' + statusCode + ')';
-
- let trimString = function (string, length) {
- return string.length > length ?
- string.substring(0, length) + '...' :
- string;
+ const userFriendlyMessages = {
+ '400': 'The request was invalid or malformed.',
+ '401': 'You need to log in to access this resource.',
+ '403': 'You don\'t have permission to access this resource.',
+ '404': 'The requested page or resource could not be found.',
+ '408': 'The request timed out. Please check your connection and try again.',
+ '409': 'There was a conflict with the current state of the resource.',
+ '429': 'Too many requests sent. Please wait a moment and try again.',
+ '500': 'An internal server error occurred. This is not your fault.',
+ '502': 'The server received an invalid response from an upstream service.',
+ '503': 'The service is temporarily unavailable. Please try again later.',
+ '504': 'The server did not respond in time. Please try again later.',
};
- const short_location = trimString(location, 50);
+ if (!statusText) {
+ statusText = httpStatusToText[String(statusCode)] ?? 'Unknown Error';
+ }
- const alert = bootbox.alert(
- {
- size: 'large',
- message: function() {
- let url = location;
- let msg = `Error calling ${short_location} . `;
- msg += 'Try to reload the page or contact the administrator if this error persists. ';
+ const title = `${statusText} (HTTP ${statusCode}) `;
+ const friendlyMsg = userFriendlyMessages[String(statusCode)]
+ ?? 'An unexpected error occurred. Please try again or contact the administrator.';
- msg += '' + 'View details' + " ";
- msg += "
";
+ const short_location = location.length > 80
+ ? location.substring(0, 80) + '…'
+ : location;
- return msg;
- },
- title: title,
- callback: function () {
- //Remove blur
- $('#content').removeClass('loading-content');
- }
+ const msg = `
+ ${friendlyMsg}
+ If this error keeps happening, please contact your administrator.
+
+ Technical details
+
+
+
+
`;
- });
+ const footer = `Error while loading: ${short_location} `;
- alert.init(function (){
- var dstFrame = document.getElementById('error-iframe');
- //@ts-ignore
- var dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document;
- dstDoc.write(responseHTML)
- dstDoc.close();
+ Swal.fire({
+ icon: 'error',
+ title: title,
+ html: msg,
+ footer: footer,
+ width: '90%',
+ confirmButtonText: ' Reload page',
+ showCancelButton: true,
+ cancelButtonText: 'Close',
+ showCloseButton: true,
+ reverseButtons: true,
+ didOpen: () => {
+ const dstFrame = document.getElementById('error-iframe');
+ //@ts-ignore
+ const dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document;
+ dstDoc.write(responseHTML);
+ dstDoc.close();
+ },
+ }).then((result) => {
+ document.getElementById('content').classList.remove('loading-content');
+ if (result.isConfirmed) {
+ window.location.reload();
+ }
});
}
@@ -171,4 +174,4 @@ class ErrorHandlerHelper {
}
}
-export default new ErrorHandlerHelper();
\ No newline at end of file
+export default new ErrorHandlerHelper();
diff --git a/assets/js/lib/dataTables.select.mjs b/assets/js/lib/dataTables.select.mjs
deleted file mode 100644
index bba97692..00000000
--- a/assets/js/lib/dataTables.select.mjs
+++ /dev/null
@@ -1,1538 +0,0 @@
-/*********************
- * This is the fixed version of the select extension for DataTables with the fix for the issue with the select extension
- * (https://github.com/DataTables/Select/issues/51)
- * We use this instead of the yarn version until the PR (https://github.com/DataTables/Select/pull/52) is merged and released
- * /*******************/
-
-
-/*! Select for DataTables 2.0.0
- * © SpryMedia Ltd - datatables.net/license/mit
- */
-
-import jQuery from 'jquery';
-import DataTable from 'datatables.net';
-
-// Allow reassignment of the $ variable
-let $ = jQuery;
-
-
-// Version information for debugger
-DataTable.select = {};
-
-DataTable.select.version = '2.0.0';
-
-DataTable.select.init = function (dt) {
- var ctx = dt.settings()[0];
-
- if (!DataTable.versionCheck('2')) {
- throw 'Warning: Select requires DataTables 2 or newer';
- }
-
- if (ctx._select) {
- return;
- }
-
- var savedSelected = dt.state.loaded();
-
- var selectAndSave = function (e, settings, data) {
- if (data === null || data.select === undefined) {
- return;
- }
-
- // Clear any currently selected rows, before restoring state
- // None will be selected on first initialisation
- if (dt.rows({ selected: true }).any()) {
- dt.rows().deselect();
- }
- if (data.select.rows !== undefined) {
- dt.rows(data.select.rows).select();
- }
-
- if (dt.columns({ selected: true }).any()) {
- dt.columns().deselect();
- }
- if (data.select.columns !== undefined) {
- dt.columns(data.select.columns).select();
- }
-
- if (dt.cells({ selected: true }).any()) {
- dt.cells().deselect();
- }
- if (data.select.cells !== undefined) {
- for (var i = 0; i < data.select.cells.length; i++) {
- dt.cell(data.select.cells[i].row, data.select.cells[i].column).select();
- }
- }
-
- dt.state.save();
- };
-
- dt.on('stateSaveParams', function (e, settings, data) {
- data.select = {};
- data.select.rows = dt.rows({ selected: true }).ids(true).toArray();
- data.select.columns = dt.columns({ selected: true })[0];
- data.select.cells = dt.cells({ selected: true })[0].map(function (coords) {
- return { row: dt.row(coords.row).id(true), column: coords.column };
- });
- })
- .on('stateLoadParams', selectAndSave)
- .one('init', function () {
- selectAndSave(undefined, undefined, savedSelected);
- });
-
- var init = ctx.oInit.select;
- var defaults = DataTable.defaults.select;
- var opts = init === undefined ? defaults : init;
-
- // Set defaults
- var items = 'row';
- var style = 'api';
- var blurable = false;
- var toggleable = true;
- var info = true;
- var selector = 'td, th';
- var className = 'selected';
- var headerCheckbox = true;
- var setStyle = false;
-
- ctx._select = {
- infoEls: []
- };
-
- // Initialisation customisations
- if (opts === true) {
- style = 'os';
- setStyle = true;
- }
- else if (typeof opts === 'string') {
- style = opts;
- setStyle = true;
- }
- else if ($.isPlainObject(opts)) {
- if (opts.blurable !== undefined) {
- blurable = opts.blurable;
- }
-
- if (opts.toggleable !== undefined) {
- toggleable = opts.toggleable;
- }
-
- if (opts.info !== undefined) {
- info = opts.info;
- }
-
- if (opts.items !== undefined) {
- items = opts.items;
- }
-
- if (opts.style !== undefined) {
- style = opts.style;
- setStyle = true;
- }
- else {
- style = 'os';
- setStyle = true;
- }
-
- if (opts.selector !== undefined) {
- selector = opts.selector;
- }
-
- if (opts.className !== undefined) {
- className = opts.className;
- }
-
- if (opts.headerCheckbox !== undefined) {
- headerCheckbox = opts.headerCheckbox;
- }
- }
-
- dt.select.selector(selector);
- dt.select.items(items);
- dt.select.style(style);
- dt.select.blurable(blurable);
- dt.select.toggleable(toggleable);
- dt.select.info(info);
- ctx._select.className = className;
-
- // If the init options haven't enabled select, but there is a selectable
- // class name, then enable
- if (!setStyle && $(dt.table().node()).hasClass('selectable')) {
- dt.select.style('os');
- }
-
- // Insert a checkbox into the header if needed - might need to wait
- // for init complete, or it might already be done
- if (headerCheckbox) {
- initCheckboxHeader(dt);
-
- dt.on('init', function () {
- initCheckboxHeader(dt);
- });
- }
-};
-
-/*
-
-Select is a collection of API methods, event handlers, event emitters and
-buttons (for the `Buttons` extension) for DataTables. It provides the following
-features, with an overview of how they are implemented:
-
-## Selection of rows, columns and cells. Whether an item is selected or not is
- stored in:
-
-* rows: a `_select_selected` property which contains a boolean value of the
- DataTables' `aoData` object for each row
-* columns: a `_select_selected` property which contains a boolean value of the
- DataTables' `aoColumns` object for each column
-* cells: a `_selected_cells` property which contains an array of boolean values
- of the `aoData` object for each row. The array is the same length as the
- columns array, with each element of it representing a cell.
-
-This method of using boolean flags allows Select to operate when nodes have not
-been created for rows / cells (DataTables' defer rendering feature).
-
-## API methods
-
-A range of API methods are available for triggering selection and de-selection
-of rows. Methods are also available to configure the selection events that can
-be triggered by an end user (such as which items are to be selected). To a large
-extent, these of API methods *is* Select. It is basically a collection of helper
-functions that can be used to select items in a DataTable.
-
-Configuration of select is held in the object `_select` which is attached to the
-DataTables settings object on initialisation. Select being available on a table
-is not optional when Select is loaded, but its default is for selection only to
-be available via the API - so the end user wouldn't be able to select rows
-without additional configuration.
-
-The `_select` object contains the following properties:
-
-```
-{
- items:string - Can be `rows`, `columns` or `cells`. Defines what item
- will be selected if the user is allowed to activate row
- selection using the mouse.
- style:string - Can be `none`, `single`, `multi` or `os`. Defines the
- interaction style when selecting items
- blurable:boolean - If row selection can be cleared by clicking outside of
- the table
- toggleable:boolean - If row selection can be cancelled by repeated clicking
- on the row
- info:boolean - If the selection summary should be shown in the table
- information elements
- infoEls:element[] - List of HTML elements with info elements for a table
-}
-```
-
-In addition to the API methods, Select also extends the DataTables selector
-options for rows, columns and cells adding a `selected` option to the selector
-options object, allowing the developer to select only selected items or
-unselected items.
-
-## Mouse selection of items
-
-Clicking on items can be used to select items. This is done by a simple event
-handler that will select the items using the API methods.
-
- */
-
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Local functions
- */
-
-/**
- * Add one or more cells to the selection when shift clicking in OS selection
- * style cell selection.
- *
- * Cell range is more complicated than row and column as we want to select
- * in the visible grid rather than by index in sequence. For example, if you
- * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
- * should also be selected (and not 1-3, 1-4. etc)
- *
- * @param {DataTable.Api} dt DataTable
- * @param {object} idx Cell index to select to
- * @param {object} last Cell index to select from
- * @private
- */
-function cellRange(dt, idx, last) {
- var indexes;
- var columnIndexes;
- var rowIndexes;
- var selectColumns = function (start, end) {
- if (start > end) {
- var tmp = end;
- end = start;
- start = tmp;
- }
-
- var record = false;
- return dt
- .columns(':visible')
- .indexes()
- .filter(function (i) {
- if (i === start) {
- record = true;
- }
-
- if (i === end) {
- // not else if, as start might === end
- record = false;
- return true;
- }
-
- return record;
- });
- };
-
- var selectRows = function (start, end) {
- var indexes = dt.rows({ search: 'applied' }).indexes();
-
- // Which comes first - might need to swap
- if (indexes.indexOf(start) > indexes.indexOf(end)) {
- var tmp = end;
- end = start;
- start = tmp;
- }
-
- var record = false;
- return indexes.filter(function (i) {
- if (i === start) {
- record = true;
- }
-
- if (i === end) {
- record = false;
- return true;
- }
-
- return record;
- });
- };
-
- if (!dt.cells({ selected: true }).any() && !last) {
- // select from the top left cell to this one
- columnIndexes = selectColumns(0, idx.column);
- rowIndexes = selectRows(0, idx.row);
- }
- else {
- // Get column indexes between old and new
- columnIndexes = selectColumns(last.column, idx.column);
- rowIndexes = selectRows(last.row, idx.row);
- }
-
- indexes = dt.cells(rowIndexes, columnIndexes).flatten();
-
- if (!dt.cells(idx, { selected: true }).any()) {
- // Select range
- dt.cells(indexes).select();
- }
- else {
- // Deselect range
- dt.cells(indexes).deselect();
- }
-}
-
-/**
- * Disable mouse selection by removing the selectors
- *
- * @param {DataTable.Api} dt DataTable to remove events from
- * @private
- */
-function disableMouseSelection(dt) {
- var ctx = dt.settings()[0];
- var selector = ctx._select.selector;
-
- $(dt.table().container())
- .off('mousedown.dtSelect', selector)
- .off('mouseup.dtSelect', selector)
- .off('click.dtSelect', selector);
-
- $('body').off('click.dtSelect' + _safeId(dt.table().node()));
-}
-
-/**
- * Attach mouse listeners to the table to allow mouse selection of items
- *
- * @param {DataTable.Api} dt DataTable to remove events from
- * @private
- */
-function enableMouseSelection(dt) {
- var container = $(dt.table().container());
- var ctx = dt.settings()[0];
- var selector = ctx._select.selector;
- var matchSelection;
-
- container
- .on('mousedown.dtSelect', selector, function (e) {
- // Disallow text selection for shift clicking on the table so multi
- // element selection doesn't look terrible!
- if (e.shiftKey || e.metaKey || e.ctrlKey) {
- container
- .css('-moz-user-select', 'none')
- .one('selectstart.dtSelect', selector, function () {
- return false;
- });
- }
-
- if (window.getSelection) {
- matchSelection = window.getSelection();
- }
- })
- .on('mouseup.dtSelect', selector, function () {
- // Allow text selection to occur again, Mozilla style (tested in FF
- // 35.0.1 - still required)
- container.css('-moz-user-select', '');
- })
- .on('click.dtSelect', selector, function (e) {
- var items = dt.select.items();
- var idx;
-
- // If text was selected (click and drag), then we shouldn't change
- // the row's selected state
- if (matchSelection) {
- var selection = window.getSelection();
-
- // If the element that contains the selection is not in the table, we can ignore it
- // This can happen if the developer selects text from the click event
- if (
- !selection.anchorNode ||
- $(selection.anchorNode).closest('table')[0] === dt.table().node()
- ) {
- if (selection !== matchSelection) {
- return;
- }
- }
- }
-
- var ctx = dt.settings()[0];
- var container = dt.table().container();
-
- // Ignore clicks inside a sub-table
- if ($(e.target).closest('div.dt-container')[0] != container) {
- return;
- }
-
- var cell = dt.cell($(e.target).closest('td, th'));
-
- // Check the cell actually belongs to the host DataTable (so child
- // rows, etc, are ignored)
- if (!cell.any()) {
- return;
- }
-
- var event = $.Event('user-select.dt');
- eventTrigger(dt, event, [items, cell, e]);
-
- if (event.isDefaultPrevented()) {
- return;
- }
-
- var cellIndex = cell.index();
- if (items === 'row') {
- idx = cellIndex.row;
- typeSelect(e, dt, ctx, 'row', idx);
- }
- else if (items === 'column') {
- idx = cell.index().column;
- typeSelect(e, dt, ctx, 'column', idx);
- }
- else if (items === 'cell') {
- idx = cell.index();
- typeSelect(e, dt, ctx, 'cell', idx);
- }
-
- ctx._select_lastCell = cellIndex;
- });
-
- // Blurable
- $('body').on('click.dtSelect' + _safeId(dt.table().node()), function (e) {
- if (ctx._select.blurable) {
- // If the click was inside the DataTables container, don't blur
- if ($(e.target).parents().filter(dt.table().container()).length) {
- return;
- }
-
- // Ignore elements which have been removed from the DOM (i.e. paging
- // buttons)
- if ($(e.target).parents('html').length === 0) {
- return;
- }
-
- // Don't blur in Editor form
- if ($(e.target).parents('div.DTE').length) {
- return;
- }
-
- var event = $.Event('select-blur.dt');
- eventTrigger(dt, event, [e.target, e]);
-
- if (event.isDefaultPrevented()) {
- return;
- }
-
- clear(ctx, true);
- }
- });
-}
-
-/**
- * Trigger an event on a DataTable
- *
- * @param {DataTable.Api} api DataTable to trigger events on
- * @param {boolean} selected true if selected, false if deselected
- * @param {string} type Item type acting on
- * @param {boolean} any Require that there are values before
- * triggering
- * @private
- */
-function eventTrigger(api, type, args, any) {
- if (any && !api.flatten().length) {
- return;
- }
-
- if (typeof type === 'string') {
- type = type + '.dt';
- }
-
- args.unshift(api);
-
- $(api.table().node()).trigger(type, args);
-}
-
-/**
- * Update the information element of the DataTable showing information about the
- * items selected. This is done by adding tags to the existing text
- *
- * @param {DataTable.Api} api DataTable to update
- * @private
- */
-function info(api, node) {
- if (api.select.style() === 'api' || api.select.info() === false) {
- return;
- }
-
- var rows = api.rows({ selected: true }).flatten().length;
- var columns = api.columns({ selected: true }).flatten().length;
- var cells = api.cells({ selected: true }).flatten().length;
-
- var add = function (el, name, num) {
- el.append(
- $(' ').append(
- api.i18n(
- 'select.' + name + 's',
- { _: '%d ' + name + 's selected', 0: '', 1: '1 ' + name + ' selected' },
- num
- )
- )
- );
- };
-
- var el = $(node);
- var output = $(' ');
-
- add(output, 'row', rows);
- add(output, 'column', columns);
- add(output, 'cell', cells);
-
- var existing = el.children('span.select-info');
-
- if (existing.length) {
- existing.remove();
- }
-
- if (output.text() !== '') {
- el.append(output);
- }
-}
-
-/**
- * Add a checkbox to the header for checkbox columns, allowing all rows to
- * be selected, deselected or just to show the state.
- *
- * @param {*} dt API
- */
-function initCheckboxHeader( dt ) {
- // Find any checkbox column(s)
- dt.columns('.dt-select').every(function () {
- var header = this.header();
-
- if (! $('input', header).length) {
- // If no checkbox yet, insert one
- var input = $(' ')
- .attr({
- class: 'dt-select-checkbox',
- type: 'checkbox',
- 'aria-label': dt.i18n('select.aria.headerCheckbox') || 'Select all rows'
- })
- .appendTo(header)
- .on('change', function () {
- if (this.checked) {
- dt.rows({search: 'applied'}).select();
- }
- else {
- dt.rows({selected: true}).deselect();
- }
- })
- .on('click', function (e) {
- e.stopPropagation();
- });
-
- // Update the header checkbox's state when the selection in the
- // table changes
- dt.on('draw select deselect', function (e, pass, type) {
- if (type === 'row' || ! type) {
- var count = dt.rows({selected: true}).count();
- var search = dt.rows({search: 'applied', selected: true}).count();
- var available = dt.rows({search: 'applied'}).count();
-
- if (search && search <= count && search === available) {
- input
- .prop('checked', true)
- .prop('indeterminate', false);
- }
- else if (search === 0 && count === 0) {
- input
- .prop('checked', false)
- .prop('indeterminate', false);
- }
- else {
- input
- .prop('checked', false)
- .prop('indeterminate', true);
- }
- }
- });
- }
- });
-}
-
-/**
- * Initialisation of a new table. Attach event handlers and callbacks to allow
- * Select to operate correctly.
- *
- * This will occur _after_ the initial DataTables initialisation, although
- * before Ajax data is rendered, if there is ajax data
- *
- * @param {DataTable.settings} ctx Settings object to operate on
- * @private
- */
-function init(ctx) {
- var api = new DataTable.Api(ctx);
- ctx._select_init = true;
-
- // Row callback so that classes can be added to rows and cells if the item
- // was selected before the element was created. This will happen with the
- // `deferRender` option enabled.
- //
- // This method of attaching to `aoRowCreatedCallback` is a hack until
- // DataTables has proper events for row manipulation If you are reviewing
- // this code to create your own plug-ins, please do not do this!
- ctx.aoRowCreatedCallback.push(function (row, data, index) {
- var i, ien;
- var d = ctx.aoData[index];
-
- // Row
- if (d._select_selected) {
- $(row).addClass(ctx._select.className);
- }
-
- // Cells and columns - if separated out, we would need to do two
- // loops, so it makes sense to combine them into a single one
- for (i = 0, ien = ctx.aoColumns.length; i < ien; i++) {
- if (
- ctx.aoColumns[i]._select_selected ||
- (d._selected_cells && d._selected_cells[i])
- ) {
- $(d.anCells[i]).addClass(ctx._select.className);
- }
- }
- }
- );
-
- // On Ajax reload we want to reselect all rows which are currently selected,
- // if there is an rowId (i.e. a unique value to identify each row with)
- api.on('preXhr.dt.dtSelect', function (e, settings) {
- if (settings !== api.settings()[0]) {
- // Not triggered by our DataTable!
- return;
- }
-
- // note that column selection doesn't need to be cached and then
- // reselected, as they are already selected
- var rows = api
- .rows({ selected: true })
- .ids(true)
- .filter(function (d) {
- return d !== undefined;
- });
-
- var cells = api
- .cells({ selected: true })
- .eq(0)
- .map(function (cellIdx) {
- var id = api.row(cellIdx.row).id(true);
- return id ? { row: id, column: cellIdx.column } : undefined;
- })
- .filter(function (d) {
- return d !== undefined;
- });
-
- // On the next draw, reselect the currently selected items
- api.one('draw.dt.dtSelect', function () {
- api.rows(rows).select();
-
- // `cells` is not a cell index selector, so it needs a loop
- if (cells.any()) {
- cells.each(function (id) {
- api.cells(id.row, id.column).select();
- });
- }
- });
- });
-
- // Update the table information element with selected item summary
- api.on('info.dt', function (e, ctx, node) {
- // Store the info node for updating on select / deselect
- if (!ctx._select.infoEls.includes(node)) {
- ctx._select.infoEls.push(node);
- }
-
- info(api, node);
- });
-
- api.on('select.dtSelect.dt deselect.dtSelect.dt', function () {
- ctx._select.infoEls.forEach(function (el) {
- info(api, el);
- });
-
- api.state.save();
- });
-
- // Clean up and release
- api.on('destroy.dtSelect', function () {
- // Remove class directly rather than calling deselect - which would trigger events
- $(api.rows({ selected: true }).nodes()).removeClass(api.settings()[0]._select.className);
-
- disableMouseSelection(api);
- api.off('.dtSelect');
- $('body').off('.dtSelect' + _safeId(api.table().node()));
- });
-}
-
-/**
- * Add one or more items (rows or columns) to the selection when shift clicking
- * in OS selection style
- *
- * @param {DataTable.Api} dt DataTable
- * @param {string} type Row or column range selector
- * @param {object} idx Item index to select to
- * @param {object} last Item index to select from
- * @private
- */
-function rowColumnRange(dt, type, idx, last) {
- // Add a range of rows from the last selected row to this one
- var indexes = dt[type + 's']({ search: 'applied' }).indexes();
- var idx1 = indexes.indexOf(last);
- var idx2 = indexes.indexOf(idx);
-
- if (!dt[type + 's']({ selected: true }).any() && idx1 === -1) {
- // select from top to here - slightly odd, but both Windows and Mac OS
- // do this
- indexes.splice(indexes.indexOf(idx) + 1, indexes.length);
- }
- else {
- // reverse so we can shift click 'up' as well as down
- if (idx1 > idx2) {
- var tmp = idx2;
- idx2 = idx1;
- idx1 = tmp;
- }
-
- indexes.splice(idx2 + 1, indexes.length);
- indexes.splice(0, idx1);
- }
-
- if (!dt[type](idx, { selected: true }).any()) {
- // Select range
- dt[type + 's'](indexes).select();
- }
- else {
- // Deselect range - need to keep the clicked on row selected
- indexes.splice(indexes.indexOf(idx), 1);
- dt[type + 's'](indexes).deselect();
- }
-}
-
-/**
- * Clear all selected items
- *
- * @param {DataTable.settings} ctx Settings object of the host DataTable
- * @param {boolean} [force=false] Force the de-selection to happen, regardless
- * of selection style
- * @private
- */
-function clear(ctx, force) {
- if (force || ctx._select.style === 'single') {
- var api = new DataTable.Api(ctx);
-
- api.rows({ selected: true }).deselect();
- api.columns({ selected: true }).deselect();
- api.cells({ selected: true }).deselect();
- }
-}
-
-/**
- * Select items based on the current configuration for style and items.
- *
- * @param {object} e Mouse event object
- * @param {DataTables.Api} dt DataTable
- * @param {DataTable.settings} ctx Settings object of the host DataTable
- * @param {string} type Items to select
- * @param {int|object} idx Index of the item to select
- * @private
- */
-function typeSelect(e, dt, ctx, type, idx) {
- var style = dt.select.style();
- var toggleable = dt.select.toggleable();
- var isSelected = dt[type](idx, { selected: true }).any();
-
- if (isSelected && !toggleable) {
- return;
- }
-
- if (style === 'os') {
- if (e.ctrlKey || e.metaKey) {
- // Add or remove from the selection
- dt[type](idx).select(!isSelected);
- }
- else if (e.shiftKey) {
- if (type === 'cell') {
- cellRange(dt, idx, ctx._select_lastCell || null);
- }
- else {
- rowColumnRange(
- dt,
- type,
- idx,
- ctx._select_lastCell ? ctx._select_lastCell[type] : null
- );
- }
- }
- else {
- // No cmd or shift click - deselect if selected, or select
- // this row only
- var selected = dt[type + 's']({ selected: true });
-
- if (isSelected && selected.flatten().length === 1) {
- dt[type](idx).deselect();
- }
- else {
- selected.deselect();
- dt[type](idx).select();
- }
- }
- }
- else if (style == 'multi+shift') {
- if (e.shiftKey) {
- if (type === 'cell') {
- cellRange(dt, idx, ctx._select_lastCell || null);
- }
- else {
- rowColumnRange(
- dt,
- type,
- idx,
- ctx._select_lastCell ? ctx._select_lastCell[type] : null
- );
- }
- }
- else {
- dt[type](idx).select(!isSelected);
- }
- }
- else {
- dt[type](idx).select(!isSelected);
- }
-}
-
-function _safeId(node) {
- return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-');
-}
-
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DataTables selectors
- */
-
-// row and column are basically identical just assigned to different properties
-// and checking a different array, so we can dynamically create the functions to
-// reduce the code size
-$.each(
- [
- { type: 'row', prop: 'aoData' },
- { type: 'column', prop: 'aoColumns' }
- ],
- function (i, o) {
- DataTable.ext.selector[o.type].push(function (settings, opts, indexes) {
- var selected = opts.selected;
- var data;
- var out = [];
-
- if (selected !== true && selected !== false) {
- return indexes;
- }
-
- for (var i = 0, ien = indexes.length; i < ien; i++) {
- data = settings[o.prop][indexes[i]];
-
- if (
- data && (
- (selected === true && data._select_selected === true) ||
- (selected === false && !data._select_selected)
- )
- ) {
- out.push(indexes[i]);
- }
- }
-
- return out;
- });
- }
-);
-
-DataTable.ext.selector.cell.push(function (settings, opts, cells) {
- var selected = opts.selected;
- var rowData;
- var out = [];
-
- if (selected === undefined) {
- return cells;
- }
-
- for (var i = 0, ien = cells.length; i < ien; i++) {
- rowData = settings.aoData[cells[i].row];
-
- if (
- rowData && (
- (selected === true &&
- rowData._selected_cells &&
- rowData._selected_cells[cells[i].column] === true) ||
- (selected === false &&
- (!rowData._selected_cells || !rowData._selected_cells[cells[i].column]))
- )
- ) {
- out.push(cells[i]);
- }
- }
-
- return out;
-});
-
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DataTables API
- *
- * For complete documentation, please refer to the docs/api directory or the
- * DataTables site
- */
-
-// Local variables to improve compression
-var apiRegister = DataTable.Api.register;
-var apiRegisterPlural = DataTable.Api.registerPlural;
-
-apiRegister('select()', function () {
- return this.iterator('table', function (ctx) {
- DataTable.select.init(new DataTable.Api(ctx));
- });
-});
-
-apiRegister('select.blurable()', function (flag) {
- if (flag === undefined) {
- return this.context[0]._select.blurable;
- }
-
- return this.iterator('table', function (ctx) {
- ctx._select.blurable = flag;
- });
-});
-
-apiRegister('select.toggleable()', function (flag) {
- if (flag === undefined) {
- return this.context[0]._select.toggleable;
- }
-
- return this.iterator('table', function (ctx) {
- ctx._select.toggleable = flag;
- });
-});
-
-apiRegister('select.info()', function (flag) {
- if (flag === undefined) {
- return this.context[0]._select.info;
- }
-
- return this.iterator('table', function (ctx) {
- ctx._select.info = flag;
- });
-});
-
-apiRegister('select.items()', function (items) {
- if (items === undefined) {
- return this.context[0]._select.items;
- }
-
- return this.iterator('table', function (ctx) {
- ctx._select.items = items;
-
- eventTrigger(new DataTable.Api(ctx), 'selectItems', [items]);
- });
-});
-
-// Takes effect from the _next_ selection. None disables future selection, but
-// does not clear the current selection. Use the `deselect` methods for that
-apiRegister('select.style()', function (style) {
- if (style === undefined) {
- return this.context[0]._select.style;
- }
-
- return this.iterator('table', function (ctx) {
- if (!ctx._select) {
- DataTable.select.init(new DataTable.Api(ctx));
- }
-
- if (!ctx._select_init) {
- init(ctx);
- }
-
- ctx._select.style = style;
-
- // Add / remove mouse event handlers. They aren't required when only
- // API selection is available
- var dt = new DataTable.Api(ctx);
- disableMouseSelection(dt);
-
- if (style !== 'api') {
- enableMouseSelection(dt);
- }
-
- eventTrigger(new DataTable.Api(ctx), 'selectStyle', [style]);
- });
-});
-
-apiRegister('select.selector()', function (selector) {
- if (selector === undefined) {
- return this.context[0]._select.selector;
- }
-
- return this.iterator('table', function (ctx) {
- disableMouseSelection(new DataTable.Api(ctx));
-
- ctx._select.selector = selector;
-
- if (ctx._select.style !== 'api') {
- enableMouseSelection(new DataTable.Api(ctx));
- }
- });
-});
-
-apiRegister('select.last()', function (set) {
- let ctx = this.context[0];
-
- if (set) {
- ctx._select_lastCell = set;
- return this;
- }
-
- return ctx._select_lastCell;
-});
-
-apiRegisterPlural('rows().select()', 'row().select()', function (select) {
- var api = this;
-
- if (select === false) {
- return this.deselect();
- }
-
- this.iterator('row', function (ctx, idx) {
- clear(ctx);
-
- // There is a good amount of knowledge of DataTables internals in
- // this function. It _could_ be done without that, but it would hurt
- // performance (or DT would need new APIs for this work)
- var dtData = ctx.aoData[idx];
- var dtColumns = ctx.aoColumns;
-
- $(dtData.nTr).addClass(ctx._select.className);
- dtData._select_selected = true;
-
- for (var i=0 ; i 0);
- });
-
- this.disable();
- },
- destroy: function (dt, node, config) {
- dt.off(config._eventNamespace);
- }
- },
- showSelected: {
- text: i18n('showSelected', 'Show only selected'),
- className: 'buttons-show-selected',
- action: function (e, dt) {
- if (dt.search.fixed('dt-select')) {
- // Remove existing function
- dt.search.fixed('dt-select', null);
-
- this.active(false);
- }
- else {
- // Use a fixed filtering function to match on selected rows
- // This needs to reference the internal aoData since that is
- // where Select stores its reference for the selected state
- var dataSrc = dt.settings()[0].aoData;
-
- dt.search.fixed('dt-select', function (text, data, idx) {
- // _select_selected is set by Select on the data object for the row
- return dataSrc[idx]._select_selected;
- });
-
- this.active(true);
- }
-
- dt.draw();
- }
- }
-});
-
-$.each(['Row', 'Column', 'Cell'], function (i, item) {
- var lc = item.toLowerCase();
-
- DataTable.ext.buttons['select' + item + 's'] = {
- text: i18n('select' + item + 's', 'Select ' + lc + 's'),
- className: 'buttons-select-' + lc + 's',
- action: function () {
- this.select.items(lc);
- },
- init: function (dt) {
- var that = this;
-
- dt.on('selectItems.dt.DT', function (e, ctx, items) {
- that.active(items === lc);
- });
- }
- };
-});
-
-DataTable.type('select-checkbox', {
- className: 'dt-select',
- detect: function (data) {
- // Rendering function will tell us if it is a checkbox type
- return data === 'select-checkbox' ? data : false;
- },
- order: {
- pre: function (d) {
- return d === 'X' ? -1 : 0;
- }
- }
-});
-
-$.extend(true, DataTable.defaults.oLanguage, {
- select: {
- aria: {
- rowCheckbox: 'Select row'
- }
- }
-});
-
-DataTable.render.select = function (valueProp, nameProp) {
- var valueFn = valueProp ? DataTable.util.get(valueProp) : null;
- var nameFn = nameProp ? DataTable.util.get(nameProp) : null;
-
- return function (data, type, row, meta) {
- var dtRow = meta.settings.aoData[meta.row];
- var selected = dtRow._select_selected;
- var ariaLabel = meta.settings.oLanguage.select.aria.rowCheckbox;
-
- if (type === 'display') {
- return $(' ')
- .attr({
- 'aria-label': ariaLabel,
- class: 'dt-select-checkbox',
- name: nameFn ? nameFn(row) : null,
- type: 'checkbox',
- value: valueFn ? valueFn(row) : null,
- checked: selected
- })[0];
- }
- else if (type === 'type') {
- return 'select-checkbox';
- }
- else if (type === 'filter') {
- return '';
- }
-
- return selected ? 'X' : '';
- }
-}
-
-// Legacy checkbox ordering
-DataTable.ext.order['select-checkbox'] = function (settings, col) {
- return this.api()
- .column(col, { order: 'index' })
- .nodes()
- .map(function (td) {
- if (settings._select.items === 'row') {
- return $(td).parent().hasClass(settings._select.className);
- }
- else if (settings._select.items === 'cell') {
- return $(td).hasClass(settings._select.className);
- }
- return false;
- });
-};
-
-$.fn.DataTable.select = DataTable.select;
-
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Initialisation
- */
-
-// DataTables creation - check if select has been defined in the options. Note
-// this required that the table be in the document! If it isn't then something
-// needs to trigger this method unfortunately. The next major release of
-// DataTables will rework the events and address this.
-$(document).on('preInit.dt.dtSelect', function (e, ctx) {
- if (e.namespace !== 'dt') {
- return;
- }
-
- DataTable.select.init(new DataTable.Api(ctx));
-});
-
-
-export default DataTable;
diff --git a/assets/js/register_events.js b/assets/js/register_events.js
index 547742ea..1c6ed09b 100644
--- a/assets/js/register_events.js
+++ b/assets/js/register_events.js
@@ -19,15 +19,14 @@
'use strict';
-import {Dropdown} from "bootstrap";
+import {Dropdown, Modal, Tooltip} from "bootstrap";
import ClipboardJS from "clipboard";
-import {Modal} from "bootstrap";
class RegisterEventHelper {
constructor() {
this.registerTooltips();
this.configureDropdowns();
-
+
// Only register special character input if enabled in configuration
const keybindingsEnabled = document.body.dataset.keybindingsSpecialCharacters !== 'false';
if (keybindingsEnabled) {
@@ -40,8 +39,6 @@ class RegisterEventHelper {
});
this.registerModalDropRemovalOnFormSubmit();
-
-
}
registerModalDropRemovalOnFormSubmit() {
@@ -83,11 +80,17 @@ class RegisterEventHelper {
registerTooltips() {
const handler = () => {
- $(".tooltip").remove();
+ document.querySelectorAll('.tooltip').forEach(el => el.remove());
+
//Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.)
- $('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]')
- //@ts-ignore
- .tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
+ const tooltipSelector = 'a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]';
+ document.querySelectorAll(tooltipSelector).forEach(el => {
+ const existing = Tooltip.getInstance(el);
+ if (existing) {
+ existing.dispose();
+ }
+ new Tooltip(el, {container: 'body', placement: 'auto', boundary: 'window'});
+ });
};
this.registerLoadHandler(handler);
@@ -95,242 +98,240 @@ class RegisterEventHelper {
}
registerSpecialCharInput() {
- this.registerLoadHandler(() => {
- //@ts-ignore
- $("input[type=text], input[type=search]").unbind("keydown").keydown(function (event) {
- let use_special_char = event.altKey;
-
- let greek_char = "";
- if (use_special_char){
- //Use the key property to determine the greek letter (as it is independent of the keyboard layout)
- switch(event.key) {
- //Greek letters
- case "a": //Alpha (lowercase)
- greek_char = "\u03B1";
- break;
- case "A": //Alpha (uppercase)
- greek_char = "\u0391";
- break;
- case "b": //Beta (lowercase)
- greek_char = "\u03B2";
- break;
- case "B": //Beta (uppercase)
- greek_char = "\u0392";
- break;
- case "g": //Gamma (lowercase)
- greek_char = "\u03B3";
- break;
- case "G": //Gamma (uppercase)
- greek_char = "\u0393";
- break;
- case "d": //Delta (lowercase)
- greek_char = "\u03B4";
- break;
- case "D": //Delta (uppercase)
- greek_char = "\u0394";
- break;
- case "e": //Epsilon (lowercase)
- greek_char = "\u03B5";
- break;
- case "E": //Epsilon (uppercase)
- greek_char = "\u0395";
- break;
- case "z": //Zeta (lowercase)
- greek_char = "\u03B6";
- break;
- case "Z": //Zeta (uppercase)
- greek_char = "\u0396";
- break;
- case "h": //Eta (lowercase)
- greek_char = "\u03B7";
- break;
- case "H": //Eta (uppercase)
- greek_char = "\u0397";
- break;
- case "q": //Theta (lowercase)
- greek_char = "\u03B8";
- break;
- case "Q": //Theta (uppercase)
- greek_char = "\u0398";
- break;
- case "i": //Iota (lowercase)
- greek_char = "\u03B9";
- break;
- case "I": //Iota (uppercase)
- greek_char = "\u0399";
- break;
- case "k": //Kappa (lowercase)
- greek_char = "\u03BA";
- break;
- case "K": //Kappa (uppercase)
- greek_char = "\u039A";
- break;
- case "l": //Lambda (lowercase)
- greek_char = "\u03BB";
- break;
- case "L": //Lambda (uppercase)
- greek_char = "\u039B";
- break;
- case "m": //Mu (lowercase)
- greek_char = "\u03BC";
- break;
- case "M": //Mu (uppercase)
- greek_char = "\u039C";
- break;
- case "n": //Nu (lowercase)
- greek_char = "\u03BD";
- break;
- case "N": //Nu (uppercase)
- greek_char = "\u039D";
- break;
- case "x": //Xi (lowercase)
- greek_char = "\u03BE";
- break;
- case "X": //Xi (uppercase)
- greek_char = "\u039E";
- break;
- case "o": //Omicron (lowercase)
- greek_char = "\u03BF";
- break;
- case "O": //Omicron (uppercase)
- greek_char = "\u039F";
- break;
- case "p": //Pi (lowercase)
- greek_char = "\u03C0";
- break;
- case "P": //Pi (uppercase)
- greek_char = "\u03A0";
- break;
- case "r": //Rho (lowercase)
- greek_char = "\u03C1";
- break;
- case "R": //Rho (uppercase)
- greek_char = "\u03A1";
- break;
- case "s": //Sigma (lowercase)
- greek_char = "\u03C3";
- break;
- case "S": //Sigma (uppercase)
- greek_char = "\u03A3";
- break;
- case "t": //Tau (lowercase)
- greek_char = "\u03C4";
- break;
- case "T": //Tau (uppercase)
- greek_char = "\u03A4";
- break;
- case "u": //Upsilon (lowercase)
- greek_char = "\u03C5";
- break;
- case "U": //Upsilon (uppercase)
- greek_char = "\u03A5";
- break;
- case "f": //Phi (lowercase)
- greek_char = "\u03C6";
- break;
- case "F": //Phi (uppercase)
- greek_char = "\u03A6";
- break;
- case "c": //Chi (lowercase)
- greek_char = "\u03C7";
- break;
- case "C": //Chi (uppercase)
- greek_char = "\u03A7";
- break;
- case "y": //Psi (lowercase)
- greek_char = "\u03C8";
- break;
- case "Y": //Psi (uppercase)
- greek_char = "\u03A8";
- break;
- case "w": //Omega (lowercase)
- greek_char = "\u03C9";
- break;
- case "W": //Omega (uppercase)
- greek_char = "\u03A9";
- break;
- }
-
- //Use keycodes for special characters as the shift char on the number keys are layout dependent
- switch (event.keyCode) {
- case 49: //1 key
- //Product symbol on shift, sum on no shift
- greek_char = event.shiftKey ? "\u220F" : "\u2211";
- break;
- case 50: //2 key
- //Integral on no shift, partial derivative on shift
- greek_char = event.shiftKey ? "\u2202" : "\u222B";
- break;
- case 51: //3 key
- //Less than or equal on no shift, greater than or equal on shift
- greek_char = event.shiftKey ? "\u2265" : "\u2264";
- break;
- case 52: //4 key
- //Empty set on shift, infinity on no shift
- greek_char = event.shiftKey ? "\u2205" : "\u221E";
- break;
- case 53: //5 key
- //Not equal on shift, approx equal on no shift
- greek_char = event.shiftKey ? "\u2260" : "\u2248";
- break;
- case 54: //6 key
- //Element of on no shift, not element of on shift
- greek_char = event.shiftKey ? "\u2209" : "\u2208";
- break;
- case 55: //7 key
- //And on shift, or on no shift
- greek_char = event.shiftKey ? "\u2227" : "\u2228";
- break;
- case 56: //8 key
- //Proportional to on shift, angle on no shift
- greek_char = event.shiftKey ? "\u221D" : "\u2220";
- break;
- case 57: //9 key
- //Cube root on shift, square root on no shift
- greek_char = event.shiftKey ? "\u221B" : "\u221A";
- break;
- case 48: //0 key
- //Minus-Plus on shift, plus-minus on no shift
- greek_char = event.shiftKey ? "\u2213" : "\u00B1";
- break;
-
- //Special characters
- case 219: //hyphen (or ß on german layout)
- //Copyright on no shift, TM on shift
- greek_char = event.shiftKey ? "\u2122" : "\u00A9";
- break;
- case 191: //forward slash (or # on german layout)
- //Generic currency on no shift, paragraph on shift
- greek_char = event.shiftKey ? "\u00B6" : "\u00A4";
- break;
-
- //Currency symbols
- case 192: //: or (ö on german layout)
- //Euro on no shift, pound on shift
- greek_char = event.shiftKey ? "\u00A3" : "\u20AC";
- break;
- case 221: //; or (ä on german layout)
- //Yen on no shift, dollar on shift
- greek_char = event.shiftKey ? "\u0024" : "\u00A5";
- break;
-
-
- }
-
- if(greek_char=="") return;
-
- let $txt = $(this);
- //@ts-ignore
- let caretPos = $txt[0].selectionStart;
- let textAreaTxt = $txt.val().toString();
- $txt.val(textAreaTxt.substring(0, caretPos) + greek_char + textAreaTxt.substring(caretPos) );
+ const keydownHandler = function(event) {
+ let use_special_char = event.altKey;
+ let greek_char = "";
+ if (use_special_char){
+ //Use the key property to determine the greek letter (as it is independent of the keyboard layout)
+ switch(event.key) {
+ //Greek letters
+ case "a": //Alpha (lowercase)
+ greek_char = "α";
+ break;
+ case "A": //Alpha (uppercase)
+ greek_char = "Α";
+ break;
+ case "b": //Beta (lowercase)
+ greek_char = "β";
+ break;
+ case "B": //Beta (uppercase)
+ greek_char = "Β";
+ break;
+ case "g": //Gamma (lowercase)
+ greek_char = "γ";
+ break;
+ case "G": //Gamma (uppercase)
+ greek_char = "Γ";
+ break;
+ case "d": //Delta (lowercase)
+ greek_char = "δ";
+ break;
+ case "D": //Delta (uppercase)
+ greek_char = "Δ";
+ break;
+ case "e": //Epsilon (lowercase)
+ greek_char = "ε";
+ break;
+ case "E": //Epsilon (uppercase)
+ greek_char = "Ε";
+ break;
+ case "z": //Zeta (lowercase)
+ greek_char = "ζ";
+ break;
+ case "Z": //Zeta (uppercase)
+ greek_char = "Ζ";
+ break;
+ case "h": //Eta (lowercase)
+ greek_char = "η";
+ break;
+ case "H": //Eta (uppercase)
+ greek_char = "Η";
+ break;
+ case "q": //Theta (lowercase)
+ greek_char = "θ";
+ break;
+ case "Q": //Theta (uppercase)
+ greek_char = "Θ";
+ break;
+ case "i": //Iota (lowercase)
+ greek_char = "ι";
+ break;
+ case "I": //Iota (uppercase)
+ greek_char = "Ι";
+ break;
+ case "k": //Kappa (lowercase)
+ greek_char = "κ";
+ break;
+ case "K": //Kappa (uppercase)
+ greek_char = "Κ";
+ break;
+ case "l": //Lambda (lowercase)
+ greek_char = "λ";
+ break;
+ case "L": //Lambda (uppercase)
+ greek_char = "Λ";
+ break;
+ case "m": //Mu (lowercase)
+ greek_char = "μ";
+ break;
+ case "M": //Mu (uppercase)
+ greek_char = "Μ";
+ break;
+ case "n": //Nu (lowercase)
+ greek_char = "ν";
+ break;
+ case "N": //Nu (uppercase)
+ greek_char = "Ν";
+ break;
+ case "x": //Xi (lowercase)
+ greek_char = "ξ";
+ break;
+ case "X": //Xi (uppercase)
+ greek_char = "Ξ";
+ break;
+ case "o": //Omicron (lowercase)
+ greek_char = "ο";
+ break;
+ case "O": //Omicron (uppercase)
+ greek_char = "Ο";
+ break;
+ case "p": //Pi (lowercase)
+ greek_char = "π";
+ break;
+ case "P": //Pi (uppercase)
+ greek_char = "Π";
+ break;
+ case "r": //Rho (lowercase)
+ greek_char = "ρ";
+ break;
+ case "R": //Rho (uppercase)
+ greek_char = "Ρ";
+ break;
+ case "s": //Sigma (lowercase)
+ greek_char = "σ";
+ break;
+ case "S": //Sigma (uppercase)
+ greek_char = "Σ";
+ break;
+ case "t": //Tau (lowercase)
+ greek_char = "τ";
+ break;
+ case "T": //Tau (uppercase)
+ greek_char = "Τ";
+ break;
+ case "u": //Upsilon (lowercase)
+ greek_char = "υ";
+ break;
+ case "U": //Upsilon (uppercase)
+ greek_char = "Υ";
+ break;
+ case "f": //Phi (lowercase)
+ greek_char = "φ";
+ break;
+ case "F": //Phi (uppercase)
+ greek_char = "Φ";
+ break;
+ case "c": //Chi (lowercase)
+ greek_char = "χ";
+ break;
+ case "C": //Chi (uppercase)
+ greek_char = "Χ";
+ break;
+ case "y": //Psi (lowercase)
+ greek_char = "ψ";
+ break;
+ case "Y": //Psi (uppercase)
+ greek_char = "Ψ";
+ break;
+ case "w": //Omega (lowercase)
+ greek_char = "ω";
+ break;
+ case "W": //Omega (uppercase)
+ greek_char = "Ω";
+ break;
}
+
+ //Use keycodes for special characters as the shift char on the number keys are layout dependent
+ switch (event.keyCode) {
+ case 49: //1 key
+ //Product symbol on shift, sum on no shift
+ greek_char = event.shiftKey ? "∏" : "∑";
+ break;
+ case 50: //2 key
+ //Integral on no shift, partial derivative on shift
+ greek_char = event.shiftKey ? "∂" : "∫";
+ break;
+ case 51: //3 key
+ //Less than or equal on no shift, greater than or equal on shift
+ greek_char = event.shiftKey ? "≥" : "≤";
+ break;
+ case 52: //4 key
+ //Empty set on shift, infinity on no shift
+ greek_char = event.shiftKey ? "∅" : "∞";
+ break;
+ case 53: //5 key
+ //Not equal on shift, approx equal on no shift
+ greek_char = event.shiftKey ? "≠" : "≈";
+ break;
+ case 54: //6 key
+ //Element of on no shift, not element of on shift
+ greek_char = event.shiftKey ? "∉" : "∈";
+ break;
+ case 55: //7 key
+ //And on shift, or on no shift
+ greek_char = event.shiftKey ? "∧" : "∨";
+ break;
+ case 56: //8 key
+ //Proportional to on shift, angle on no shift
+ greek_char = event.shiftKey ? "∝" : "∠";
+ break;
+ case 57: //9 key
+ //Cube root on shift, square root on no shift
+ greek_char = event.shiftKey ? "∛" : "√";
+ break;
+ case 48: //0 key
+ //Minus-Plus on shift, plus-minus on no shift
+ greek_char = event.shiftKey ? "∓" : "±";
+ break;
+
+ //Special characters
+ case 219: //hyphen (or ß on german layout)
+ //Copyright on no shift, TM on shift
+ greek_char = event.shiftKey ? "™" : "©";
+ break;
+ case 191: //forward slash (or # on german layout)
+ //Generic currency on no shift, paragraph on shift
+ greek_char = event.shiftKey ? "¶" : "¤";
+ break;
+
+ //Currency symbols
+ case 192: //: or (ö on german layout)
+ //Euro on no shift, pound on shift
+ greek_char = event.shiftKey ? "£" : "€";
+ break;
+ case 221: //; or (ä on german layout)
+ //Yen on no shift, dollar on shift
+ greek_char = event.shiftKey ? "$" : "¥";
+ break;
+ }
+
+ if(greek_char=="") return;
+
+ const txt = event.currentTarget;
+ const caretPos = txt.selectionStart;
+ const textAreaTxt = txt.value;
+ txt.value = textAreaTxt.substring(0, caretPos) + greek_char + textAreaTxt.substring(caretPos);
+ }
+ };
+
+ this.registerLoadHandler(() => {
+ document.querySelectorAll('input[type=text], input[type=search]').forEach(input => {
+ input.removeEventListener('keydown', keydownHandler);
+ input.addEventListener('keydown', keydownHandler);
});
- //@ts-ignore
- this.greek_once = true;
- })
+ });
}
}
-export default new RegisterEventHelper();
\ No newline at end of file
+export default new RegisterEventHelper();
diff --git a/assets/themes/brite.js b/assets/themes/brite.js
new file mode 100644
index 00000000..41b82e93
--- /dev/null
+++ b/assets/themes/brite.js
@@ -0,0 +1,20 @@
+/*
+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
+ *
+ * Copyright (C) 2019 - 2026 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 "bootswatch/dist/brite/bootstrap.css";
diff --git a/composer.json b/composer.json
index a1f15834..10c9b702 100644
--- a/composer.json
+++ b/composer.json
@@ -57,9 +57,10 @@
"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.10.0",
+ "symfony/ai-lm-studio-platform": "^v0.10.0",
+ "symfony/ai-ollama-platform": "^0.10.0",
+ "symfony/ai-open-router-platform": "^0.10.0",
"symfony/apache-pack": "^1.0",
"symfony/asset": "7.4.*",
"symfony/console": "7.4.*",
@@ -70,6 +71,7 @@
"symfony/flex": "^v2.3.1",
"symfony/form": "7.4.*",
"symfony/framework-bundle": "7.4.*",
+ "symfony/html-sanitizer": "7.4.*",
"symfony/http-client": "7.4.*",
"symfony/http-kernel": "7.4.*",
"symfony/mailer": "7.4.*",
diff --git a/composer.lock b/composer.lock
index 1c2f7dda..dc8dc699 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d6bda397c505e1e6d540c814a2368fbb",
+ "content-hash": "6a79fa73091c2e15bce035c85c2d61ed",
"packages": [
{
"name": "amphp/amp",
- "version": "v3.1.1",
+ "version": "v3.1.2",
"source": {
"type": "git",
"url": "https://github.com/amphp/amp.git",
- "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f"
+ "reference": "2f3ebed5a4f663968a0590dbb7654a8b32cb63cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f",
- "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f",
+ "url": "https://api.github.com/repos/amphp/amp/zipball/2f3ebed5a4f663968a0590dbb7654a8b32cb63cb",
+ "reference": "2f3ebed5a4f663968a0590dbb7654a8b32cb63cb",
"shasum": ""
},
"require": {
@@ -27,7 +27,7 @@
"require-dev": {
"amphp/php-cs-fixer-config": "^2",
"phpunit/phpunit": "^9",
- "psalm/phar": "5.23.1"
+ "psalm/phar": "6.16.1"
},
"type": "library",
"autoload": {
@@ -77,7 +77,7 @@
],
"support": {
"issues": "https://github.com/amphp/amp/issues",
- "source": "https://github.com/amphp/amp/tree/v3.1.1"
+ "source": "https://github.com/amphp/amp/tree/v3.1.2"
},
"funding": [
{
@@ -85,7 +85,7 @@
"type": "github"
}
],
- "time": "2025-08-27T21:42:00+00:00"
+ "time": "2026-06-21T13:59:44+00:00"
},
{
"name": "amphp/byte-stream",
@@ -615,16 +615,16 @@
},
{
"name": "amphp/pipeline",
- "version": "v1.2.4",
+ "version": "v1.2.5",
"source": {
"type": "git",
"url": "https://github.com/amphp/pipeline.git",
- "reference": "a044733e080940d1483f56caff0c412ad6982776"
+ "reference": "92f121dde31cd1d89d5d0f9eba64ac40271b236e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/pipeline/zipball/a044733e080940d1483f56caff0c412ad6982776",
- "reference": "a044733e080940d1483f56caff0c412ad6982776",
+ "url": "https://api.github.com/repos/amphp/pipeline/zipball/92f121dde31cd1d89d5d0f9eba64ac40271b236e",
+ "reference": "92f121dde31cd1d89d5d0f9eba64ac40271b236e",
"shasum": ""
},
"require": {
@@ -670,7 +670,7 @@
],
"support": {
"issues": "https://github.com/amphp/pipeline/issues",
- "source": "https://github.com/amphp/pipeline/tree/v1.2.4"
+ "source": "https://github.com/amphp/pipeline/tree/v1.2.5"
},
"funding": [
{
@@ -678,7 +678,7 @@
"type": "github"
}
],
- "time": "2026-05-06T05:37:57+00:00"
+ "time": "2026-06-27T14:17:20+00:00"
},
{
"name": "amphp/process",
@@ -976,16 +976,16 @@
},
{
"name": "api-platform/doctrine-common",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/doctrine-common.git",
- "reference": "a342c7e4cd4a7545d355b8eaae6d2f46de4f8936"
+ "reference": "e4dee10c45bd701c5984321bc98adc0c3760ec48"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/a342c7e4cd4a7545d355b8eaae6d2f46de4f8936",
- "reference": "a342c7e4cd4a7545d355b8eaae6d2f46de4f8936",
+ "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/e4dee10c45bd701c5984321bc98adc0c3760ec48",
+ "reference": "e4dee10c45bd701c5984321bc98adc0c3760ec48",
"shasum": ""
},
"require": {
@@ -1060,22 +1060,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/doctrine-common/tree/v4.3.10"
+ "source": "https://github.com/api-platform/doctrine-common/tree/v4.3.15"
},
- "time": "2026-06-05T09:05:29+00:00"
+ "time": "2026-06-16T14:59:18+00:00"
},
{
"name": "api-platform/doctrine-orm",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/doctrine-orm.git",
- "reference": "db75bac977cc9fe76cee90c1e5361c744cfa2144"
+ "reference": "6af3eeefc7d483b83e56bcbdbff0dd0dde3c7fc1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/db75bac977cc9fe76cee90c1e5361c744cfa2144",
- "reference": "db75bac977cc9fe76cee90c1e5361c744cfa2144",
+ "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/6af3eeefc7d483b83e56bcbdbff0dd0dde3c7fc1",
+ "reference": "6af3eeefc7d483b83e56bcbdbff0dd0dde3c7fc1",
"shasum": ""
},
"require": {
@@ -1149,13 +1149,13 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/doctrine-orm/tree/v4.3.10"
+ "source": "https://github.com/api-platform/doctrine-orm/tree/v4.3.15"
},
- "time": "2026-06-04T13:58:17+00:00"
+ "time": "2026-06-16T13:14:05+00:00"
},
{
"name": "api-platform/documentation",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/documentation.git",
@@ -1212,22 +1212,22 @@
],
"description": "API Platform documentation controller.",
"support": {
- "source": "https://github.com/api-platform/documentation/tree/v4.3.10"
+ "source": "https://github.com/api-platform/documentation/tree/v4.4.0-alpha.1"
},
"time": "2026-04-30T12:21:24+00:00"
},
{
"name": "api-platform/http-cache",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/http-cache.git",
- "reference": "98e5ba9344abe9db7d6789429745ee4eb0a2fd8f"
+ "reference": "8e71916de766f503dd60f1a1e886b4d704f881a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/http-cache/zipball/98e5ba9344abe9db7d6789429745ee4eb0a2fd8f",
- "reference": "98e5ba9344abe9db7d6789429745ee4eb0a2fd8f",
+ "url": "https://api.github.com/repos/api-platform/http-cache/zipball/8e71916de766f503dd60f1a1e886b4d704f881a8",
+ "reference": "8e71916de766f503dd60f1a1e886b4d704f881a8",
"shasum": ""
},
"require": {
@@ -1292,22 +1292,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/http-cache/tree/v4.3.10"
+ "source": "https://github.com/api-platform/http-cache/tree/v4.4.0-alpha.1"
},
- "time": "2026-05-29T13:58:43+00:00"
+ "time": "2026-06-09T14:20:49+00:00"
},
{
"name": "api-platform/hydra",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/hydra.git",
- "reference": "a0314c8dda95f69f15054d7309ddbc1ca32f2149"
+ "reference": "2570883edf78970ad845f6eb7d69ae7894e6c480"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/hydra/zipball/a0314c8dda95f69f15054d7309ddbc1ca32f2149",
- "reference": "a0314c8dda95f69f15054d7309ddbc1ca32f2149",
+ "url": "https://api.github.com/repos/api-platform/hydra/zipball/2570883edf78970ad845f6eb7d69ae7894e6c480",
+ "reference": "2570883edf78970ad845f6eb7d69ae7894e6c480",
"shasum": ""
},
"require": {
@@ -1315,7 +1315,7 @@
"api-platform/json-schema": "^4.3",
"api-platform/jsonld": "^4.3",
"api-platform/metadata": "^4.3",
- "api-platform/serializer": "^4.3",
+ "api-platform/serializer": "^4.3.12",
"api-platform/state": "^4.3",
"php": ">=8.2",
"symfony/type-info": "^7.3 || ^8.0",
@@ -1379,29 +1379,29 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/hydra/tree/v4.3.10"
+ "source": "https://github.com/api-platform/hydra/tree/v4.3.15"
},
- "time": "2026-06-04T13:52:24+00:00"
+ "time": "2026-06-22T16:14:53+00:00"
},
{
"name": "api-platform/json-api",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/json-api.git",
- "reference": "2e1773bc4b098119531c59de14124afa0cb693a2"
+ "reference": "30f70ddc6d865e9c36d99c0255bb1f407c4d4258"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/json-api/zipball/2e1773bc4b098119531c59de14124afa0cb693a2",
- "reference": "2e1773bc4b098119531c59de14124afa0cb693a2",
+ "url": "https://api.github.com/repos/api-platform/json-api/zipball/30f70ddc6d865e9c36d99c0255bb1f407c4d4258",
+ "reference": "30f70ddc6d865e9c36d99c0255bb1f407c4d4258",
"shasum": ""
},
"require": {
"api-platform/documentation": "^4.3",
"api-platform/json-schema": "^4.3",
"api-platform/metadata": "^4.3",
- "api-platform/serializer": "^4.3.8",
+ "api-platform/serializer": "^4.3.12",
"api-platform/state": "^4.3",
"php": ">=8.2",
"symfony/error-handler": "^6.4 || ^7.0 || ^8.0",
@@ -1461,22 +1461,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/json-api/tree/v4.3.10"
+ "source": "https://github.com/api-platform/json-api/tree/v4.3.15"
},
- "time": "2026-06-05T15:17:34+00:00"
+ "time": "2026-06-17T18:14:46+00:00"
},
{
"name": "api-platform/json-schema",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/json-schema.git",
- "reference": "6a0a2ddc4914bb1fdb1b71777c49aef388f6ea31"
+ "reference": "2136965a30643056d09b10ae5e9fab1f7232a028"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/json-schema/zipball/6a0a2ddc4914bb1fdb1b71777c49aef388f6ea31",
- "reference": "6a0a2ddc4914bb1fdb1b71777c49aef388f6ea31",
+ "url": "https://api.github.com/repos/api-platform/json-schema/zipball/2136965a30643056d09b10ae5e9fab1f7232a028",
+ "reference": "2136965a30643056d09b10ae5e9fab1f7232a028",
"shasum": ""
},
"require": {
@@ -1542,27 +1542,27 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/json-schema/tree/v4.3.10"
+ "source": "https://github.com/api-platform/json-schema/tree/v4.4.0-alpha.1"
},
- "time": "2026-06-03T14:46:27+00:00"
+ "time": "2026-06-13T05:06:55+00:00"
},
{
"name": "api-platform/jsonld",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/jsonld.git",
- "reference": "20ca6d7b5c11674c3046d710aaa0c9bc1795e54b"
+ "reference": "026a380c3c85c4210028da43e0cea1b64211bbf5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/jsonld/zipball/20ca6d7b5c11674c3046d710aaa0c9bc1795e54b",
- "reference": "20ca6d7b5c11674c3046d710aaa0c9bc1795e54b",
+ "url": "https://api.github.com/repos/api-platform/jsonld/zipball/026a380c3c85c4210028da43e0cea1b64211bbf5",
+ "reference": "026a380c3c85c4210028da43e0cea1b64211bbf5",
"shasum": ""
},
"require": {
"api-platform/metadata": "^4.3",
- "api-platform/serializer": "^4.3",
+ "api-platform/serializer": "^4.3.12",
"api-platform/state": "^4.3",
"php": ">=8.2"
},
@@ -1622,22 +1622,22 @@
"rest"
],
"support": {
- "source": "https://github.com/api-platform/jsonld/tree/v4.3.10"
+ "source": "https://github.com/api-platform/jsonld/tree/v4.3.15"
},
- "time": "2026-04-30T12:21:24+00:00"
+ "time": "2026-06-13T05:11:46+00:00"
},
{
"name": "api-platform/metadata",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/metadata.git",
- "reference": "b835ce534861f9e7b2a316fa582f28afe4ad5fe6"
+ "reference": "86efa5375e994f4e46f3e4336dc4dc5ecc9456a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/metadata/zipball/b835ce534861f9e7b2a316fa582f28afe4ad5fe6",
- "reference": "b835ce534861f9e7b2a316fa582f28afe4ad5fe6",
+ "url": "https://api.github.com/repos/api-platform/metadata/zipball/86efa5375e994f4e46f3e4336dc4dc5ecc9456a8",
+ "reference": "86efa5375e994f4e46f3e4336dc4dc5ecc9456a8",
"shasum": ""
},
"require": {
@@ -1720,22 +1720,22 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/metadata/tree/v4.3.10"
+ "source": "https://github.com/api-platform/metadata/tree/v4.3.15"
},
- "time": "2026-06-05T09:05:29+00:00"
+ "time": "2026-06-13T05:03:21+00:00"
},
{
"name": "api-platform/openapi",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/openapi.git",
- "reference": "21e22f4d74fafc4b01ee5790be7a264387445dcf"
+ "reference": "c72470132f2eb35a4f8f252e60342f0f7c487704"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/openapi/zipball/21e22f4d74fafc4b01ee5790be7a264387445dcf",
- "reference": "21e22f4d74fafc4b01ee5790be7a264387445dcf",
+ "url": "https://api.github.com/repos/api-platform/openapi/zipball/c72470132f2eb35a4f8f252e60342f0f7c487704",
+ "reference": "c72470132f2eb35a4f8f252e60342f0f7c487704",
"shasum": ""
},
"require": {
@@ -1753,7 +1753,7 @@
"api-platform/doctrine-common": "^4.3",
"api-platform/doctrine-odm": "^4.3",
"api-platform/doctrine-orm": "^4.3",
- "api-platform/serializer": "^4.3",
+ "api-platform/serializer": "^4.3.12",
"phpspec/prophecy-phpunit": "^2.2",
"phpunit/phpunit": "^11.5 || ^12.2",
"symfony/type-info": "^7.3 || ^8.0"
@@ -1811,22 +1811,22 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/openapi/tree/v4.3.10"
+ "source": "https://github.com/api-platform/openapi/tree/v4.3.15"
},
- "time": "2026-06-03T14:37:29+00:00"
+ "time": "2026-06-16T10:01:53+00:00"
},
{
"name": "api-platform/serializer",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/serializer.git",
- "reference": "04d56abff2910390111613716cd75796f23e34d5"
+ "reference": "4c8f7507a13f8a9c0cace60efc245fece2259233"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/serializer/zipball/04d56abff2910390111613716cd75796f23e34d5",
- "reference": "04d56abff2910390111613716cd75796f23e34d5",
+ "url": "https://api.github.com/repos/api-platform/serializer/zipball/4c8f7507a13f8a9c0cace60efc245fece2259233",
+ "reference": "4c8f7507a13f8a9c0cace60efc245fece2259233",
"shasum": ""
},
"require": {
@@ -1905,22 +1905,22 @@
"serializer"
],
"support": {
- "source": "https://github.com/api-platform/serializer/tree/v4.3.10"
+ "source": "https://github.com/api-platform/serializer/tree/v4.3.15"
},
- "time": "2026-06-05T12:07:51+00:00"
+ "time": "2026-06-26T09:59:36+00:00"
},
{
"name": "api-platform/state",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/state.git",
- "reference": "06067bf0efae09badc618fac8add67191f12f1e3"
+ "reference": "3bb76df0857dd1a6e706dd1c5c4b574432a2fa8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/state/zipball/06067bf0efae09badc618fac8add67191f12f1e3",
- "reference": "06067bf0efae09badc618fac8add67191f12f1e3",
+ "url": "https://api.github.com/repos/api-platform/state/zipball/3bb76df0857dd1a6e706dd1c5c4b574432a2fa8d",
+ "reference": "3bb76df0857dd1a6e706dd1c5c4b574432a2fa8d",
"shasum": ""
},
"require": {
@@ -1933,7 +1933,7 @@
"symfony/translation-contracts": "^3.0"
},
"require-dev": {
- "api-platform/serializer": "^4.3",
+ "api-platform/serializer": "^4.3.12",
"api-platform/validator": "^4.3.1",
"phpunit/phpunit": "^11.5 || ^12.2",
"symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0",
@@ -2002,22 +2002,22 @@
"swagger"
],
"support": {
- "source": "https://github.com/api-platform/state/tree/v4.3.10"
+ "source": "https://github.com/api-platform/state/tree/v4.3.15"
},
- "time": "2026-06-05T15:16:47+00:00"
+ "time": "2026-06-13T05:11:46+00:00"
},
{
"name": "api-platform/symfony",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/symfony.git",
- "reference": "70a1a4468d77c9761ac1a6cba681277fa986c39b"
+ "reference": "8b20ef6262b2d557e2c286df48b78e66e7f8c000"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/api-platform/symfony/zipball/70a1a4468d77c9761ac1a6cba681277fa986c39b",
- "reference": "70a1a4468d77c9761ac1a6cba681277fa986c39b",
+ "url": "https://api.github.com/repos/api-platform/symfony/zipball/8b20ef6262b2d557e2c286df48b78e66e7f8c000",
+ "reference": "8b20ef6262b2d557e2c286df48b78e66e7f8c000",
"shasum": ""
},
"require": {
@@ -2028,7 +2028,7 @@
"api-platform/jsonld": "^4.3",
"api-platform/metadata": "^4.3",
"api-platform/openapi": "^4.3",
- "api-platform/serializer": "^4.3",
+ "api-platform/serializer": "^4.3.12",
"api-platform/state": "^4.3",
"api-platform/validator": "^4.3.1",
"php": ">=8.2",
@@ -2131,13 +2131,13 @@
"symfony"
],
"support": {
- "source": "https://github.com/api-platform/symfony/tree/v4.3.10"
+ "source": "https://github.com/api-platform/symfony/tree/v4.3.15"
},
- "time": "2026-06-05T15:17:34+00:00"
+ "time": "2026-06-17T18:14:46+00:00"
},
{
"name": "api-platform/validator",
- "version": "v4.3.10",
+ "version": "v4.3.15",
"source": {
"type": "git",
"url": "https://github.com/api-platform/validator.git",
@@ -2207,22 +2207,22 @@
"validator"
],
"support": {
- "source": "https://github.com/api-platform/validator/tree/v4.3.10"
+ "source": "https://github.com/api-platform/validator/tree/v4.3.15"
},
"time": "2026-05-07T11:45:31+00:00"
},
{
"name": "beberlei/assert",
- "version": "v3.3.3",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/beberlei/assert.git",
- "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd"
+ "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd",
- "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd",
+ "url": "https://api.github.com/repos/beberlei/assert/zipball/f193f4613c7d7fbcee2c05e4daff4061d49c040e",
+ "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e",
"shasum": ""
},
"require": {
@@ -2274,9 +2274,9 @@
],
"support": {
"issues": "https://github.com/beberlei/assert/issues",
- "source": "https://github.com/beberlei/assert/tree/v3.3.3"
+ "source": "https://github.com/beberlei/assert/tree/v3.3.4"
},
- "time": "2024-07-15T13:18:35+00:00"
+ "time": "2026-06-10T19:47:05+00:00"
},
{
"name": "beberlei/doctrineextensions",
@@ -2658,28 +2658,29 @@
},
{
"name": "composer/pcre",
- "version": "3.3.2",
+ "version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
- "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
+ "reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
- "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
+ "reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
- "phpstan/phpstan": "<1.11.10"
+ "phpstan/phpstan": "<2.2.2"
},
"require-dev": {
- "phpstan/phpstan": "^1.12 || ^2",
- "phpstan/phpstan-strict-rules": "^1 || ^2",
- "phpunit/phpunit": "^8 || ^9"
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^9"
},
"type": "library",
"extra": {
@@ -2717,7 +2718,7 @@
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
- "source": "https://github.com/composer/pcre/tree/3.3.2"
+ "source": "https://github.com/composer/pcre/tree/3.4.0"
},
"funding": [
{
@@ -2727,13 +2728,9 @@
{
"url": "https://github.com/composer",
"type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/composer/composer",
- "type": "tidelift"
}
],
- "time": "2024-11-12T16:29:46+00:00"
+ "time": "2026-06-07T11:47:49+00:00"
},
{
"name": "composer/semver",
@@ -3349,16 +3346,16 @@
},
{
"name": "doctrine/doctrine-bundle",
- "version": "2.18.2",
+ "version": "2.18.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineBundle.git",
- "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546"
+ "reference": "241d61f6bbc77275d5a9f95d514241c058bf2d0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546",
- "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546",
+ "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/241d61f6bbc77275d5a9f95d514241c058bf2d0a",
+ "reference": "241d61f6bbc77275d5a9f95d514241c058bf2d0a",
"shasum": ""
},
"require": {
@@ -3395,6 +3392,7 @@
"psr/log": "^1.1.4 || ^2.0 || ^3.0",
"symfony/doctrine-messenger": "^6.4 || ^7.0",
"symfony/expression-language": "^6.4 || ^7.0",
+ "symfony/http-kernel": "^6.4 || ^7.0",
"symfony/messenger": "^6.4 || ^7.0",
"symfony/property-info": "^6.4 || ^7.0",
"symfony/security-bundle": "^6.4 || ^7.0",
@@ -3450,7 +3448,7 @@
],
"support": {
"issues": "https://github.com/doctrine/DoctrineBundle/issues",
- "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2"
+ "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.3"
},
"funding": [
{
@@ -3466,7 +3464,7 @@
"type": "tidelift"
}
],
- "time": "2025-12-20T21:35:32+00:00"
+ "time": "2026-06-08T08:22:50+00:00"
},
{
"name": "doctrine/doctrine-migrations-bundle",
@@ -4637,26 +4635,26 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.11.0",
+ "version": "7.12.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "c987f8ce84b8434fa430795eca0f3430663da72b"
+ "reference": "9aa17bcdd777ee31df9fc83c337ca4ca2340def3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c987f8ce84b8434fa430795eca0f3430663da72b",
- "reference": "c987f8ce84b8434fa430795eca0f3430663da72b",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9aa17bcdd777ee31df9fc83c337ca4ca2340def3",
+ "reference": "9aa17bcdd777ee31df9fc83c337ca4ca2340def3",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^2.5",
- "guzzlehttp/psr7": "^2.11",
+ "guzzlehttp/psr7": "^2.12.3",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.5 || ^3.0",
- "symfony/polyfill-php80": "^1.24"
+ "symfony/polyfill-php80": "^1.25"
},
"provide": {
"psr/http-client-implementation": "1.0"
@@ -4665,7 +4663,7 @@
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
"guzzle/client-integration-tests": "3.0.2",
- "guzzlehttp/test-server": "^0.4",
+ "guzzlehttp/test-server": "^0.5.1",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.52 || ^9.6.34",
"psr/log": "^1.1 || ^2.0 || ^3.0"
@@ -4745,7 +4743,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.11.0"
+ "source": "https://github.com/guzzle/guzzle/tree/7.12.3"
},
"funding": [
{
@@ -4761,7 +4759,7 @@
"type": "tidelift"
}
],
- "time": "2026-06-02T12:40:51+00:00"
+ "time": "2026-06-23T15:29:02+00:00"
},
{
"name": "guzzlehttp/promises",
@@ -4849,16 +4847,16 @@
},
{
"name": "guzzlehttp/psr7",
- "version": "2.11.0",
+ "version": "2.12.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f"
+ "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f",
- "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/7ec62dc3f44aa218487dbed81a9bf9bc647be55d",
+ "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d",
"shasum": ""
},
"require": {
@@ -4867,7 +4865,7 @@
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0",
"symfony/deprecation-contracts": "^2.5 || ^3.0",
- "symfony/polyfill-php80": "^1.24"
+ "symfony/polyfill-php80": "^1.25"
},
"provide": {
"psr/http-factory-implementation": "1.0",
@@ -4948,7 +4946,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.11.0"
+ "source": "https://github.com/guzzle/psr7/tree/2.12.3"
},
"funding": [
{
@@ -4964,7 +4962,7 @@
"type": "tidelift"
}
],
- "time": "2026-06-02T12:30:48+00:00"
+ "time": "2026-06-23T15:21:08+00:00"
},
{
"name": "hshn/base64-encoded-file",
@@ -5364,16 +5362,16 @@
},
{
"name": "jfcherng/php-diff",
- "version": "6.16.2",
+ "version": "6.16.3",
"source": {
"type": "git",
"url": "https://github.com/jfcherng/php-diff.git",
- "reference": "7f46bcfc582e81769237d0b3f6b8a548efe8799d"
+ "reference": "6d7332b6080cdd50011a364b58f24c8d0cdeb5da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/7f46bcfc582e81769237d0b3f6b8a548efe8799d",
- "reference": "7f46bcfc582e81769237d0b3f6b8a548efe8799d",
+ "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/6d7332b6080cdd50011a364b58f24c8d0cdeb5da",
+ "reference": "6d7332b6080cdd50011a364b58f24c8d0cdeb5da",
"shasum": ""
},
"require": {
@@ -5418,7 +5416,7 @@
],
"support": {
"issues": "https://github.com/jfcherng/php-diff/issues",
- "source": "https://github.com/jfcherng/php-diff/tree/6.16.2"
+ "source": "https://github.com/jfcherng/php-diff/tree/6.16.3"
},
"funding": [
{
@@ -5426,7 +5424,7 @@
"type": "custom"
}
],
- "time": "2024-03-10T17:40:29+00:00"
+ "time": "2026-06-22T13:08:56+00:00"
},
{
"name": "jfcherng/php-mb-string",
@@ -7008,16 +7006,16 @@
},
{
"name": "masterminds/html5",
- "version": "2.10.0",
+ "version": "2.10.1",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
- "reference": "fcf91eb64359852f00d921887b219479b4f21251"
+ "reference": "fd5018f6815fff903946d0564977b44ce8010e29"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
- "reference": "fcf91eb64359852f00d921887b219479b4f21251",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29",
+ "reference": "fd5018f6815fff903946d0564977b44ce8010e29",
"shasum": ""
},
"require": {
@@ -7025,7 +7023,7 @@
"php": ">=5.3.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10"
},
"type": "library",
"extra": {
@@ -7069,9 +7067,9 @@
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
- "source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
+ "source": "https://github.com/Masterminds/html5-php/tree/2.10.1"
},
- "time": "2025-07-25T09:04:22+00:00"
+ "time": "2026-06-23T18:43:15+00:00"
},
{
"name": "mf2/mf2",
@@ -9999,16 +9997,16 @@
},
{
"name": "sabberworm/php-css-parser",
- "version": "v9.3.0",
+ "version": "v9.4.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
- "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949"
+ "reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
- "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
+ "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
+ "reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
"shasum": ""
},
"require": {
@@ -10019,15 +10017,15 @@
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
- "phpstan/phpstan": "1.12.32 || 2.1.32",
- "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
- "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
+ "phpstan/phpstan": "1.12.33 || 2.2.2",
+ "phpstan/phpstan-phpunit": "1.4.2 || 2.0.16",
+ "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.11",
"phpunit/phpunit": "8.5.52",
"rawr/phpunit-data-provider": "3.3.1",
- "rector/rector": "1.2.10 || 2.2.8",
- "rector/type-perfect": "1.0.0 || 2.1.0",
+ "rector/rector": "1.2.10 || 2.4.6",
+ "rector/type-perfect": "1.0.0 || 2.1.3",
"squizlabs/php_codesniffer": "4.0.1",
- "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
+ "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.3"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
@@ -10035,7 +10033,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "9.4.x-dev"
+ "dev-main": "9.5.x-dev"
}
},
"autoload": {
@@ -10073,9 +10071,9 @@
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
- "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0"
+ "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.4.0"
},
- "time": "2026-03-03T17:31:43+00:00"
+ "time": "2026-06-18T15:10:53+00:00"
},
{
"name": "sabre/uri",
@@ -10140,16 +10138,16 @@
},
{
"name": "scheb/2fa-backup-code",
- "version": "v7.13.1",
+ "version": "v7.14.0",
"source": {
"type": "git",
"url": "https://github.com/scheb/2fa-backup-code.git",
- "reference": "35f1ace4be7be2c10158d2bb8284208499111db8"
+ "reference": "73946182322f43cf82c5ee8c5481481a570ade40"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/35f1ace4be7be2c10158d2bb8284208499111db8",
- "reference": "35f1ace4be7be2c10158d2bb8284208499111db8",
+ "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/73946182322f43cf82c5ee8c5481481a570ade40",
+ "reference": "73946182322f43cf82c5ee8c5481481a570ade40",
"shasum": ""
},
"require": {
@@ -10183,22 +10181,22 @@
"two-step"
],
"support": {
- "source": "https://github.com/scheb/2fa-backup-code/tree/v7.13.1"
+ "source": "https://github.com/scheb/2fa-backup-code/tree/v7.14.0"
},
- "time": "2025-11-20T13:35:24+00:00"
+ "time": "2026-01-24T13:47:32+00:00"
},
{
"name": "scheb/2fa-bundle",
- "version": "v7.13.1",
+ "version": "v7.14.0",
"source": {
"type": "git",
"url": "https://github.com/scheb/2fa-bundle.git",
- "reference": "edcc14456b508aab37ec792cfc36793d04226784"
+ "reference": "65fa9eb61d1205f2024f14c8c38b53e7bb20927c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/edcc14456b508aab37ec792cfc36793d04226784",
- "reference": "edcc14456b508aab37ec792cfc36793d04226784",
+ "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/65fa9eb61d1205f2024f14c8c38b53e7bb20927c",
+ "reference": "65fa9eb61d1205f2024f14c8c38b53e7bb20927c",
"shasum": ""
},
"require": {
@@ -10251,22 +10249,22 @@
"two-step"
],
"support": {
- "source": "https://github.com/scheb/2fa-bundle/tree/v7.13.1"
+ "source": "https://github.com/scheb/2fa-bundle/tree/v7.14.0"
},
- "time": "2025-12-18T15:29:07+00:00"
+ "time": "2026-06-12T18:31:07+00:00"
},
{
"name": "scheb/2fa-google-authenticator",
- "version": "v7.13.1",
+ "version": "v7.14.0",
"source": {
"type": "git",
"url": "https://github.com/scheb/2fa-google-authenticator.git",
- "reference": "7ad34bbde343a0770571464127ee072aacb70a58"
+ "reference": "23b0bda5b924b8752047c4994f10e1c9e1532179"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/7ad34bbde343a0770571464127ee072aacb70a58",
- "reference": "7ad34bbde343a0770571464127ee072aacb70a58",
+ "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/23b0bda5b924b8752047c4994f10e1c9e1532179",
+ "reference": "23b0bda5b924b8752047c4994f10e1c9e1532179",
"shasum": ""
},
"require": {
@@ -10304,22 +10302,22 @@
"two-step"
],
"support": {
- "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.13.1"
+ "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.14.0"
},
- "time": "2025-12-04T15:55:14+00:00"
+ "time": "2026-01-24T13:53:55+00:00"
},
{
"name": "scheb/2fa-trusted-device",
- "version": "v7.13.1",
+ "version": "v7.14.0",
"source": {
"type": "git",
"url": "https://github.com/scheb/2fa-trusted-device.git",
- "reference": "ae3a5819faccbf151af078f432e4e6c97bb44ebf"
+ "reference": "590f400bac58bff70af24adbc702e57a30e493a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/ae3a5819faccbf151af078f432e4e6c97bb44ebf",
- "reference": "ae3a5819faccbf151af078f432e4e6c97bb44ebf",
+ "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/590f400bac58bff70af24adbc702e57a30e493a3",
+ "reference": "590f400bac58bff70af24adbc702e57a30e493a3",
"shasum": ""
},
"require": {
@@ -10355,9 +10353,9 @@
"two-step"
],
"support": {
- "source": "https://github.com/scheb/2fa-trusted-device/tree/v7.13.1"
+ "source": "https://github.com/scheb/2fa-trusted-device/tree/v7.14.0"
},
- "time": "2025-12-01T15:40:59+00:00"
+ "time": "2026-01-24T13:47:32+00:00"
},
{
"name": "shivas/versioning-bundle",
@@ -10735,21 +10733,21 @@
},
{
"name": "symfony/ai-bundle",
- "version": "v0.9.0",
+ "version": "v0.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ai-bundle.git",
- "reference": "77fd1b513174770acf49abd68effa995fa518f7c"
+ "reference": "5f6d218ca26a4ac3c2b743e4bfae769c41c556c0"
},
"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/5f6d218ca26a4ac3c2b743e4bfae769c41c556c0",
+ "reference": "5f6d218ca26a4ac3c2b743e4bfae769c41c556c0",
"shasum": ""
},
"require": {
"php": ">=8.2",
- "symfony/ai-platform": "^0.9",
+ "symfony/ai-platform": "^0.10",
"symfony/clock": "^7.3|^8.0",
"symfony/config": "^7.3|^8.0",
"symfony/console": "^7.3|^8.0",
@@ -10764,74 +10762,74 @@
"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.10",
+ "symfony/ai-ai-ml-api-platform": "^0.10",
+ "symfony/ai-albert-platform": "^0.10",
+ "symfony/ai-amazee-ai-platform": "^0.10",
+ "symfony/ai-anthropic-platform": "^0.10",
+ "symfony/ai-azure-platform": "^0.10",
+ "symfony/ai-azure-search-store": "^0.10",
+ "symfony/ai-bedrock-platform": "^0.10",
+ "symfony/ai-cache-message-store": "^0.10",
+ "symfony/ai-cache-platform": "^0.10",
+ "symfony/ai-cache-store": "^0.10",
+ "symfony/ai-cartesia-platform": "^0.10",
+ "symfony/ai-cerebras-platform": "^0.10",
+ "symfony/ai-chat": "^0.10",
+ "symfony/ai-chroma-db-store": "^0.10",
+ "symfony/ai-click-house-store": "^0.10",
+ "symfony/ai-cloudflare-message-store": "^0.10",
+ "symfony/ai-cloudflare-store": "^0.10",
+ "symfony/ai-cohere-platform": "^0.10",
+ "symfony/ai-decart-platform": "^0.10",
+ "symfony/ai-deep-seek-platform": "^0.10",
+ "symfony/ai-docker-model-runner-platform": "^0.10",
+ "symfony/ai-doctrine-message-store": "^0.10",
+ "symfony/ai-elasticsearch-store": "^0.10",
+ "symfony/ai-eleven-labs-platform": "^0.10",
+ "symfony/ai-failover-platform": "^0.10",
+ "symfony/ai-gemini-platform": "^0.10",
+ "symfony/ai-generic-platform": "^0.10",
+ "symfony/ai-hugging-face-platform": "^0.10",
+ "symfony/ai-lm-studio-platform": "^0.10",
+ "symfony/ai-manticore-search-store": "^0.10",
+ "symfony/ai-maria-db-store": "^0.10",
+ "symfony/ai-meilisearch-message-store": "^0.10",
+ "symfony/ai-meilisearch-store": "^0.10",
+ "symfony/ai-meta-platform": "^0.10",
+ "symfony/ai-milvus-store": "^0.10",
+ "symfony/ai-mistral-platform": "^0.10",
+ "symfony/ai-mongo-db-message-store": "^0.10",
+ "symfony/ai-mongo-db-store": "^0.10",
+ "symfony/ai-neo4j-store": "^0.10",
+ "symfony/ai-ollama-platform": "^0.10",
+ "symfony/ai-open-ai-platform": "^0.10",
+ "symfony/ai-open-responses-platform": "^0.10",
+ "symfony/ai-open-router-platform": "^0.10",
+ "symfony/ai-open-search-store": "^0.10",
+ "symfony/ai-ovh-platform": "^0.10",
+ "symfony/ai-perplexity-platform": "^0.10",
+ "symfony/ai-pinecone-store": "^0.10",
+ "symfony/ai-pogocache-message-store": "^0.10",
+ "symfony/ai-postgres-store": "^0.10",
+ "symfony/ai-qdrant-store": "^0.10",
+ "symfony/ai-redis-message-store": "^0.10",
+ "symfony/ai-redis-store": "^0.10",
+ "symfony/ai-replicate-platform": "^0.10",
+ "symfony/ai-s3vectors-store": "^0.10",
+ "symfony/ai-scaleway-platform": "^0.10",
+ "symfony/ai-session-message-store": "^0.10",
+ "symfony/ai-sqlite-store": "^0.10",
+ "symfony/ai-store": "^0.10",
+ "symfony/ai-supabase-store": "^0.10",
+ "symfony/ai-surreal-db-message-store": "^0.10",
+ "symfony/ai-surreal-db-store": "^0.10",
+ "symfony/ai-transformers-php-platform": "^0.10",
+ "symfony/ai-typesense-store": "^0.10",
+ "symfony/ai-vektor-store": "^0.10",
+ "symfony/ai-vertex-ai-platform": "^0.10",
+ "symfony/ai-voyage-platform": "^0.10",
+ "symfony/ai-weaviate-store": "^0.10",
"symfony/expression-language": "^7.3|^8.0",
"symfony/security-core": "^7.3|^8.0",
"symfony/translation": "^7.3|^8.0",
@@ -10869,7 +10867,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.10.0"
},
"funding": [
{
@@ -10889,25 +10887,25 @@
"type": "tidelift"
}
],
- "time": "2026-05-16T08:40:45+00:00"
+ "time": "2026-06-16T07:10:08+00:00"
},
{
"name": "symfony/ai-generic-platform",
- "version": "v0.9.0",
+ "version": "v0.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ai-generic-platform.git",
- "reference": "8887d12b8ea97d079c5c97de4aebb19f42c58dc5"
+ "reference": "a099d8a35ea9f7254a159b9c17bc7a4927f9ebb2"
},
"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/a099d8a35ea9f7254a159b9c17bc7a4927f9ebb2",
+ "reference": "a099d8a35ea9f7254a159b9c17bc7a4927f9ebb2",
"shasum": ""
},
"require": {
"php": ">=8.2",
- "symfony/ai-platform": "^0.9",
+ "symfony/ai-platform": "^0.10",
"symfony/http-client": "^7.3|^8.0"
},
"require-dev": {
@@ -10954,7 +10952,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.10.0"
},
"funding": [
{
@@ -10974,26 +10972,26 @@
"type": "tidelift"
}
],
- "time": "2026-05-16T01:01:33+00:00"
+ "time": "2026-06-16T07:10:08+00:00"
},
{
"name": "symfony/ai-lm-studio-platform",
- "version": "v0.9.0",
+ "version": "v0.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ai-lm-studio-platform.git",
- "reference": "9e53e56c8c3a04dddb955088b40904e747ec3981"
+ "reference": "b6c4a3a5bedf2fd613953447602945c104e75a30"
},
"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/b6c4a3a5bedf2fd613953447602945c104e75a30",
+ "reference": "b6c4a3a5bedf2fd613953447602945c104e75a30",
"shasum": ""
},
"require": {
"php": ">=8.2",
- "symfony/ai-generic-platform": "^0.9",
- "symfony/ai-platform": "^0.9",
+ "symfony/ai-generic-platform": "^0.10",
+ "symfony/ai-platform": "^0.10",
"symfony/http-client": "^7.3|^8.0"
},
"require-dev": {
@@ -11041,7 +11039,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.10.0"
},
"funding": [
{
@@ -11061,26 +11059,112 @@
"type": "tidelift"
}
],
- "time": "2026-05-16T01:01:33+00:00"
+ "time": "2026-06-15T22:48:31+00:00"
},
{
- "name": "symfony/ai-open-router-platform",
- "version": "v0.9.0",
+ "name": "symfony/ai-ollama-platform",
+ "version": "v0.10.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/ai-open-router-platform.git",
- "reference": "7e2b560c86f618cd5d33f9f0c581d83bebc9802f"
+ "url": "https://github.com/symfony/ai-ollama-platform.git",
+ "reference": "1542f19b78362cafc034c219f5bc9a5a239a0ffb"
},
"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-ollama-platform/zipball/1542f19b78362cafc034c219f5bc9a5a239a0ffb",
+ "reference": "1542f19b78362cafc034c219f5bc9a5a239a0ffb",
"shasum": ""
},
"require": {
"php": ">=8.2",
- "symfony/ai-generic-platform": "^0.9",
- "symfony/ai-platform": "^0.9",
+ "symfony/ai-platform": "^0.10",
+ "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"
+ },
+ "type": "symfony-ai-platform",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/ai",
+ "name": "symfony/ai"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\AI\\Platform\\Bridge\\Ollama\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christopher Hertel",
+ "email": "mail@christopher-hertel.de"
+ },
+ {
+ "name": "Oskar Stark",
+ "email": "oskarstark@googlemail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Ollama platform bridge for Symfony AI",
+ "keywords": [
+ "Bridge",
+ "ai",
+ "local",
+ "ollama",
+ "platform"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/ai-ollama-platform/tree/v0.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-06-15T22:48:31+00:00"
+ },
+ {
+ "name": "symfony/ai-open-router-platform",
+ "version": "v0.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/ai-open-router-platform.git",
+ "reference": "cfadb7858fca98b28b968b032b1bbeb5c9cc985b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/ai-open-router-platform/zipball/cfadb7858fca98b28b968b032b1bbeb5c9cc985b",
+ "reference": "cfadb7858fca98b28b968b032b1bbeb5c9cc985b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/ai-generic-platform": "^0.10",
+ "symfony/ai-platform": "^0.10",
"symfony/http-client": "^7.3|^8.0"
},
"require-dev": {
@@ -11128,7 +11212,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.10.0"
},
"funding": [
{
@@ -11148,20 +11232,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-16T01:01:33+00:00"
+ "time": "2026-06-16T08:09:36+00:00"
},
{
"name": "symfony/ai-platform",
- "version": "v0.9.0",
+ "version": "v0.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ai-platform.git",
- "reference": "fb55ebdf20bbe30af6752a0ce6a25abc56b2b625"
+ "reference": "8100507aa9c46f3ad56d0272e8e46b558f451052"
},
"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/8100507aa9c46f3ad56d0272e8e46b558f451052",
+ "reference": "8100507aa9c46f3ad56d0272e8e46b558f451052",
"shasum": ""
},
"require": {
@@ -11260,7 +11344,7 @@
"voyage"
],
"support": {
- "source": "https://github.com/symfony/ai-platform/tree/v0.9.0"
+ "source": "https://github.com/symfony/ai-platform/tree/v0.10.0"
},
"funding": [
{
@@ -11280,7 +11364,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-15T19:15:50+00:00"
+ "time": "2026-06-16T06:39:24+00:00"
},
{
"name": "symfony/apache-pack",
@@ -11383,16 +11467,16 @@
},
{
"name": "symfony/cache",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
- "reference": "4c09e18a92cce126cc0d1155825279fca8cd0673"
+ "reference": "9adfcb2a7fc3924473b09f5a0b058dcc2cc7be9a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/4c09e18a92cce126cc0d1155825279fca8cd0673",
- "reference": "4c09e18a92cce126cc0d1155825279fca8cd0673",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/9adfcb2a7fc3924473b09f5a0b058dcc2cc7be9a",
+ "reference": "9adfcb2a7fc3924473b09f5a0b058dcc2cc7be9a",
"shasum": ""
},
"require": {
@@ -11463,7 +11547,7 @@
"psr6"
],
"support": {
- "source": "https://github.com/symfony/cache/tree/v7.4.13"
+ "source": "https://github.com/symfony/cache/tree/v7.4.14"
},
"funding": [
{
@@ -11483,20 +11567,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-24T08:43:14+00:00"
+ "time": "2026-06-17T14:44:48+00:00"
},
{
"name": "symfony/cache-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache-contracts.git",
- "reference": "225e8a254166bd3442e370c6f50145465db63831"
+ "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831",
- "reference": "225e8a254166bd3442e370c6f50145465db63831",
+ "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2",
+ "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2",
"shasum": ""
},
"require": {
@@ -11543,7 +11627,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/cache-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -11563,7 +11647,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-05T15:33:14+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/clock",
@@ -11645,16 +11729,16 @@
},
{
"name": "symfony/config",
- "version": "v7.4.10",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57"
+ "reference": "7b665e443381ea7c4db03eb03b4bf79ea2b020eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
- "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
+ "url": "https://api.github.com/repos/symfony/config/zipball/7b665e443381ea7c4db03eb03b4bf79ea2b020eb",
+ "reference": "7b665e443381ea7c4db03eb03b4bf79ea2b020eb",
"shasum": ""
},
"require": {
@@ -11700,7 +11784,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v7.4.10"
+ "source": "https://github.com/symfony/config/tree/v7.4.14"
},
"funding": [
{
@@ -11720,20 +11804,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-03T14:20:49+00:00"
+ "time": "2026-06-09T07:51:57+00:00"
},
{
"name": "symfony/console",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217"
+ "reference": "92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217",
- "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217",
+ "url": "https://api.github.com/repos/symfony/console/zipball/92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87",
+ "reference": "92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87",
"shasum": ""
},
"require": {
@@ -11798,7 +11882,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.4.13"
+ "source": "https://github.com/symfony/console/tree/v7.4.14"
},
"funding": [
{
@@ -11818,7 +11902,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-24T08:56:14+00:00"
+ "time": "2026-06-16T11:50:14+00:00"
},
{
"name": "symfony/css-selector",
@@ -11891,16 +11975,16 @@
},
{
"name": "symfony/dependency-injection",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "f299e20ce983be6c0744952533c6dfeaaa1448e2"
+ "reference": "2c8c64a33e2e6911579e1ff79a8e06c27d48d402"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f299e20ce983be6c0744952533c6dfeaaa1448e2",
- "reference": "f299e20ce983be6c0744952533c6dfeaaa1448e2",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2c8c64a33e2e6911579e1ff79a8e06c27d48d402",
+ "reference": "2c8c64a33e2e6911579e1ff79a8e06c27d48d402",
"shasum": ""
},
"require": {
@@ -11951,7 +12035,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v7.4.13"
+ "source": "https://github.com/symfony/dependency-injection/tree/v7.4.14"
},
"funding": [
{
@@ -11971,20 +12055,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-20T14:07:29+00:00"
+ "time": "2026-06-24T07:41:05+00:00"
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
- "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
@@ -12022,7 +12106,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -12042,20 +12126,20 @@
"type": "tidelift"
}
],
- "time": "2026-04-13T15:52:40+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/doctrine-bridge",
- "version": "v7.4.9",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/doctrine-bridge.git",
- "reference": "7a87c85853f3069e3657a823c62b02952de46b0a"
+ "reference": "f65262a834d1117617d6e072cb180a0b79428789"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/7a87c85853f3069e3657a823c62b02952de46b0a",
- "reference": "7a87c85853f3069e3657a823c62b02952de46b0a",
+ "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/f65262a834d1117617d6e072cb180a0b79428789",
+ "reference": "f65262a834d1117617d6e072cb180a0b79428789",
"shasum": ""
},
"require": {
@@ -12135,7 +12219,7 @@
"description": "Provides integration for Doctrine with various Symfony components",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/doctrine-bridge/tree/v7.4.9"
+ "source": "https://github.com/symfony/doctrine-bridge/tree/v7.4.14"
},
"funding": [
{
@@ -12155,7 +12239,7 @@
"type": "tidelift"
}
],
- "time": "2026-04-29T14:19:39+00:00"
+ "time": "2026-06-11T07:31:44+00:00"
},
{
"name": "symfony/dom-crawler",
@@ -12231,16 +12315,16 @@
},
{
"name": "symfony/dotenv",
- "version": "v7.4.11",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/dotenv.git",
- "reference": "82e9b1355c68ef7b96397dbd34cc75a92eebae7c"
+ "reference": "9b9c7a00e565238857eea0040dc9745e3576402e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dotenv/zipball/82e9b1355c68ef7b96397dbd34cc75a92eebae7c",
- "reference": "82e9b1355c68ef7b96397dbd34cc75a92eebae7c",
+ "url": "https://api.github.com/repos/symfony/dotenv/zipball/9b9c7a00e565238857eea0040dc9745e3576402e",
+ "reference": "9b9c7a00e565238857eea0040dc9745e3576402e",
"shasum": ""
},
"require": {
@@ -12285,7 +12369,7 @@
"environment"
],
"support": {
- "source": "https://github.com/symfony/dotenv/tree/v7.4.11"
+ "source": "https://github.com/symfony/dotenv/tree/v7.4.14"
},
"funding": [
{
@@ -12305,20 +12389,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-11T13:02:51+00:00"
+ "time": "2026-06-05T06:22:21+00:00"
},
{
"name": "symfony/error-handler",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa"
+ "reference": "4e1a093b481f323e6e326451f9760c3868430673"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa",
- "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/4e1a093b481f323e6e326451f9760c3868430673",
+ "reference": "4e1a093b481f323e6e326451f9760c3868430673",
"shasum": ""
},
"require": {
@@ -12367,7 +12451,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v7.4.8"
+ "source": "https://github.com/symfony/error-handler/tree/v7.4.14"
},
"funding": [
{
@@ -12387,20 +12471,20 @@
"type": "tidelift"
}
],
- "time": "2026-03-24T13:12:05+00:00"
+ "time": "2026-06-05T06:22:21+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v7.4.9",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101"
+ "reference": "51fe3d170227be8d1772214b82ae506e15ed78ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101",
- "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/51fe3d170227be8d1772214b82ae506e15ed78ff",
+ "reference": "51fe3d170227be8d1772214b82ae506e15ed78ff",
"shasum": ""
},
"require": {
@@ -12452,7 +12536,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.14"
},
"funding": [
{
@@ -12472,20 +12556,20 @@
"type": "tidelift"
}
],
- "time": "2026-04-18T13:18:21+00:00"
+ "time": "2026-06-06T11:10:32+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
- "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32"
+ "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32",
- "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e",
+ "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e",
"shasum": ""
},
"require": {
@@ -12532,7 +12616,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -12552,20 +12636,20 @@
"type": "tidelift"
}
],
- "time": "2026-01-05T13:30:16+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/expression-language",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/expression-language.git",
- "reference": "87ff95687748f4af65e4d5a6e917d448ec52aa83"
+ "reference": "bd5763f92959201816ecc31defdf352d2ea473be"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/expression-language/zipball/87ff95687748f4af65e4d5a6e917d448ec52aa83",
- "reference": "87ff95687748f4af65e4d5a6e917d448ec52aa83",
+ "url": "https://api.github.com/repos/symfony/expression-language/zipball/bd5763f92959201816ecc31defdf352d2ea473be",
+ "reference": "bd5763f92959201816ecc31defdf352d2ea473be",
"shasum": ""
},
"require": {
@@ -12600,7 +12684,7 @@
"description": "Provides an engine that can compile and evaluate expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/expression-language/tree/v7.4.8"
+ "source": "https://github.com/symfony/expression-language/tree/v7.4.14"
},
"funding": [
{
@@ -12620,7 +12704,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-24T13:12:05+00:00"
+ "time": "2026-06-08T20:24:16+00:00"
},
{
"name": "symfony/filesystem",
@@ -12694,16 +12778,16 @@
},
{
"name": "symfony/finder",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "e0be088d22278583a82da281886e8c3592fbf149"
+ "reference": "13b38720174286f55d1761152b575a8d1436fc25"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149",
- "reference": "e0be088d22278583a82da281886e8c3592fbf149",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/13b38720174286f55d1761152b575a8d1436fc25",
+ "reference": "13b38720174286f55d1761152b575a8d1436fc25",
"shasum": ""
},
"require": {
@@ -12738,7 +12822,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v7.4.8"
+ "source": "https://github.com/symfony/finder/tree/v7.4.14"
},
"funding": [
{
@@ -12758,7 +12842,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-24T13:12:05+00:00"
+ "time": "2026-06-27T08:31:18+00:00"
},
{
"name": "symfony/flex",
@@ -12835,16 +12919,16 @@
},
{
"name": "symfony/form",
- "version": "v7.4.9",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
- "reference": "b6c107af659106abec1771d9d7d22da528644d3a"
+ "reference": "355e9567b60254deef62392829a1784e66322041"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/form/zipball/b6c107af659106abec1771d9d7d22da528644d3a",
- "reference": "b6c107af659106abec1771d9d7d22da528644d3a",
+ "url": "https://api.github.com/repos/symfony/form/zipball/355e9567b60254deef62392829a1784e66322041",
+ "reference": "355e9567b60254deef62392829a1784e66322041",
"shasum": ""
},
"require": {
@@ -12914,7 +12998,7 @@
"description": "Allows to easily create, process and reuse HTML forms",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/form/tree/v7.4.9"
+ "source": "https://github.com/symfony/form/tree/v7.4.14"
},
"funding": [
{
@@ -12934,20 +13018,20 @@
"type": "tidelift"
}
],
- "time": "2026-04-29T14:39:19+00:00"
+ "time": "2026-06-16T15:54:05+00:00"
},
{
"name": "symfony/framework-bundle",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/framework-bundle.git",
- "reference": "8be39c7bf9e6f58fe49c07927572a9df7c961c95"
+ "reference": "4d11fd50d0a3d2c43b400154ad7ec35b84ea3e5b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/8be39c7bf9e6f58fe49c07927572a9df7c961c95",
- "reference": "8be39c7bf9e6f58fe49c07927572a9df7c961c95",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/4d11fd50d0a3d2c43b400154ad7ec35b84ea3e5b",
+ "reference": "4d11fd50d0a3d2c43b400154ad7ec35b84ea3e5b",
"shasum": ""
},
"require": {
@@ -12997,7 +13081,7 @@
"symfony/twig-bundle": "<6.4",
"symfony/validator": "<6.4",
"symfony/web-profiler-bundle": "<6.4",
- "symfony/webhook": "<7.2",
+ "symfony/webhook": "<7.4",
"symfony/workflow": "<7.4"
},
"require-dev": {
@@ -13041,7 +13125,7 @@
"symfony/uid": "^6.4|^7.0|^8.0",
"symfony/validator": "^7.4|^8.0",
"symfony/web-link": "^6.4|^7.0|^8.0",
- "symfony/webhook": "^7.2|^8.0",
+ "symfony/webhook": "^7.4|^8.0",
"symfony/workflow": "^7.4|^8.0",
"symfony/yaml": "^7.3|^8.0",
"twig/twig": "^3.12"
@@ -13072,7 +13156,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.13"
+ "source": "https://github.com/symfony/framework-bundle/tree/v7.4.14"
},
"funding": [
{
@@ -13092,20 +13176,94 @@
"type": "tidelift"
}
],
- "time": "2026-05-23T18:04:28+00:00"
+ "time": "2026-06-27T08:31:38+00:00"
},
{
- "name": "symfony/http-client",
- "version": "v7.4.13",
+ "name": "symfony/html-sanitizer",
+ "version": "v7.4.14",
"source": {
"type": "git",
- "url": "https://github.com/symfony/http-client.git",
- "reference": "e8a112b8415707265a7e614278136a9d92989a6a"
+ "url": "https://github.com/symfony/html-sanitizer.git",
+ "reference": "c328df69f5b6f44a0d031d757903d955bebb23b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/e8a112b8415707265a7e614278136a9d92989a6a",
- "reference": "e8a112b8415707265a7e614278136a9d92989a6a",
+ "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/c328df69f5b6f44a0d031d757903d955bebb23b3",
+ "reference": "c328df69f5b6f44a0d031d757903d955bebb23b3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "league/uri": "^6.5|^7.0",
+ "masterminds/html5": "^2.7.2",
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HtmlSanitizer\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Titouan Galopin",
+ "email": "galopintitouan@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to sanitize untrusted HTML input for safe insertion into a document's DOM.",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "Purifier",
+ "html",
+ "sanitizer"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/html-sanitizer/tree/v7.4.14"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-06-06T11:10:32+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v7.4.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "f6bc6b5a54ff5afac4725cacec9bf2f52eb15920"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/f6bc6b5a54ff5afac4725cacec9bf2f52eb15920",
+ "reference": "f6bc6b5a54ff5afac4725cacec9bf2f52eb15920",
"shasum": ""
},
"require": {
@@ -13173,7 +13331,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v7.4.13"
+ "source": "https://github.com/symfony/http-client/tree/v7.4.14"
},
"funding": [
{
@@ -13193,20 +13351,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-24T09:57:54+00:00"
+ "time": "2026-06-16T11:50:14+00:00"
},
{
"name": "symfony/http-client-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client-contracts.git",
- "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d"
+ "reference": "41fc42d276aeff21192465331ebbab7d83a743c0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d",
- "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0",
+ "reference": "41fc42d276aeff21192465331ebbab7d83a743c0",
"shasum": ""
},
"require": {
@@ -13255,7 +13413,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -13275,20 +13433,20 @@
"type": "tidelift"
}
],
- "time": "2026-03-06T13:17:50+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "bc354f47c62301e990b7874fa662326368508e2c"
+ "reference": "06db5ae1552177bf8572f8908839f12e3c06aed3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bc354f47c62301e990b7874fa662326368508e2c",
- "reference": "bc354f47c62301e990b7874fa662326368508e2c",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/06db5ae1552177bf8572f8908839f12e3c06aed3",
+ "reference": "06db5ae1552177bf8572f8908839f12e3c06aed3",
"shasum": ""
},
"require": {
@@ -13337,7 +13495,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.4.13"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.4.14"
},
"funding": [
{
@@ -13357,20 +13515,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-24T11:20:33+00:00"
+ "time": "2026-06-11T07:31:44+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "9df847980c436451f4f51d1284491bb4356dd989"
+ "reference": "e99af79b1e776646eda0e1c23b7b45c184ff99be"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9df847980c436451f4f51d1284491bb4356dd989",
- "reference": "9df847980c436451f4f51d1284491bb4356dd989",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e99af79b1e776646eda0e1c23b7b45c184ff99be",
+ "reference": "e99af79b1e776646eda0e1c23b7b45c184ff99be",
"shasum": ""
},
"require": {
@@ -13456,7 +13614,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.13"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.4.14"
},
"funding": [
{
@@ -13476,20 +13634,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-27T08:31:43+00:00"
+ "time": "2026-06-27T09:14:35+00:00"
},
{
"name": "symfony/intl",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
- "reference": "7cfb7792d580dea833647420afd5f2f98df8457b"
+ "reference": "02d77a81a198788444a8371658ef98e2e20c8c2b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/intl/zipball/7cfb7792d580dea833647420afd5f2f98df8457b",
- "reference": "7cfb7792d580dea833647420afd5f2f98df8457b",
+ "url": "https://api.github.com/repos/symfony/intl/zipball/02d77a81a198788444a8371658ef98e2e20c8c2b",
+ "reference": "02d77a81a198788444a8371658ef98e2e20c8c2b",
"shasum": ""
},
"require": {
@@ -13546,7 +13704,7 @@
"localization"
],
"support": {
- "source": "https://github.com/symfony/intl/tree/v7.4.8"
+ "source": "https://github.com/symfony/intl/tree/v7.4.14"
},
"funding": [
{
@@ -13566,20 +13724,20 @@
"type": "tidelift"
}
],
- "time": "2026-03-30T12:55:43+00:00"
+ "time": "2026-06-05T06:22:21+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.4.12",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "5cefb712a25f320579615ba9e1942abaeade7dff"
+ "reference": "f88ce03ae73e3edb5c176ce1f337709996e88495"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/5cefb712a25f320579615ba9e1942abaeade7dff",
- "reference": "5cefb712a25f320579615ba9e1942abaeade7dff",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/f88ce03ae73e3edb5c176ce1f337709996e88495",
+ "reference": "f88ce03ae73e3edb5c176ce1f337709996e88495",
"shasum": ""
},
"require": {
@@ -13630,7 +13788,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.14"
},
"funding": [
{
@@ -13650,7 +13808,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-20T07:20:23+00:00"
+ "time": "2026-06-13T08:51:35+00:00"
},
{
"name": "symfony/mime",
@@ -14477,16 +14635,16 @@
},
{
"name": "symfony/polyfill-php83",
- "version": "v1.38.1",
+ "version": "v1.38.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
- "reference": "8339098cae28673c15cce00d80734af0453054e2"
+ "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8"
},
"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/796a26abb75ce49f3a84433cd81bf1009d73d5f8",
+ "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8",
"shasum": ""
},
"require": {
@@ -14533,7 +14691,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1"
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2"
},
"funding": [
{
@@ -14553,7 +14711,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-26T12:51:13+00:00"
+ "time": "2026-05-27T06:51:48+00:00"
},
{
"name": "symfony/polyfill-php84",
@@ -15124,16 +15282,16 @@
},
{
"name": "symfony/rate-limiter",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/rate-limiter.git",
- "reference": "8b162768544e5a8895c52161d63c999aca91f4a9"
+ "reference": "5c0af3fdc93e6115d9b806a6ea73e7de040e711d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/8b162768544e5a8895c52161d63c999aca91f4a9",
- "reference": "8b162768544e5a8895c52161d63c999aca91f4a9",
+ "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/5c0af3fdc93e6115d9b806a6ea73e7de040e711d",
+ "reference": "5c0af3fdc93e6115d9b806a6ea73e7de040e711d",
"shasum": ""
},
"require": {
@@ -15174,7 +15332,7 @@
"rate-limiter"
],
"support": {
- "source": "https://github.com/symfony/rate-limiter/tree/v7.4.13"
+ "source": "https://github.com/symfony/rate-limiter/tree/v7.4.14"
},
"funding": [
{
@@ -15194,7 +15352,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-23T16:05:06+00:00"
+ "time": "2026-06-08T20:24:16+00:00"
},
{
"name": "symfony/routing",
@@ -15283,16 +15441,16 @@
},
{
"name": "symfony/runtime",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/runtime.git",
- "reference": "1a24cf8aab3a9378117718b35525c4126ad3adec"
+ "reference": "15497f743dd714f3167cb6a56509b9d42e6417b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/runtime/zipball/1a24cf8aab3a9378117718b35525c4126ad3adec",
- "reference": "1a24cf8aab3a9378117718b35525c4126ad3adec",
+ "url": "https://api.github.com/repos/symfony/runtime/zipball/15497f743dd714f3167cb6a56509b9d42e6417b3",
+ "reference": "15497f743dd714f3167cb6a56509b9d42e6417b3",
"shasum": ""
},
"require": {
@@ -15300,7 +15458,8 @@
"php": ">=8.2"
},
"conflict": {
- "symfony/dotenv": "<6.4"
+ "symfony/dotenv": "<6.4",
+ "symfony/http-foundation": "<6.4"
},
"require-dev": {
"composer/composer": "^2.6",
@@ -15343,7 +15502,7 @@
"runtime"
],
"support": {
- "source": "https://github.com/symfony/runtime/tree/v7.4.13"
+ "source": "https://github.com/symfony/runtime/tree/v7.4.14"
},
"funding": [
{
@@ -15363,20 +15522,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-23T18:04:28+00:00"
+ "time": "2026-06-05T06:22:21+00:00"
},
{
"name": "symfony/security-bundle",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-bundle.git",
- "reference": "0cbc6528aa583795ab44e43b4e92a09acf927c6f"
+ "reference": "6ec147e67262c6f5ac5dbb8b2ccbb34af14c4d79"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-bundle/zipball/0cbc6528aa583795ab44e43b4e92a09acf927c6f",
- "reference": "0cbc6528aa583795ab44e43b4e92a09acf927c6f",
+ "url": "https://api.github.com/repos/symfony/security-bundle/zipball/6ec147e67262c6f5ac5dbb8b2ccbb34af14c4d79",
+ "reference": "6ec147e67262c6f5ac5dbb8b2ccbb34af14c4d79",
"shasum": ""
},
"require": {
@@ -15455,7 +15614,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.13"
+ "source": "https://github.com/symfony/security-bundle/tree/v7.4.14"
},
"funding": [
{
@@ -15475,20 +15634,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-23T16:05:06+00:00"
+ "time": "2026-06-16T15:54:05+00:00"
},
{
"name": "symfony/security-core",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-core.git",
- "reference": "25db686fcf2a3fe00e1cf6dcab1fcb7aac71ba9b"
+ "reference": "880bb18eff6188d55115e795f06e4185373c35fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-core/zipball/25db686fcf2a3fe00e1cf6dcab1fcb7aac71ba9b",
- "reference": "25db686fcf2a3fe00e1cf6dcab1fcb7aac71ba9b",
+ "url": "https://api.github.com/repos/symfony/security-core/zipball/880bb18eff6188d55115e795f06e4185373c35fd",
+ "reference": "880bb18eff6188d55115e795f06e4185373c35fd",
"shasum": ""
},
"require": {
@@ -15546,7 +15705,7 @@
"description": "Symfony Security Component - Core Library",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/security-core/tree/v7.4.13"
+ "source": "https://github.com/symfony/security-core/tree/v7.4.14"
},
"funding": [
{
@@ -15566,7 +15725,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-23T16:05:06+00:00"
+ "time": "2026-06-09T07:51:57+00:00"
},
{
"name": "symfony/security-csrf",
@@ -15644,16 +15803,16 @@
},
{
"name": "symfony/security-http",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-http.git",
- "reference": "da3c28025a664e6a88e1af104a74457d99301161"
+ "reference": "148e038b91c8cc3e42aee177f8b0117437077c9b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-http/zipball/da3c28025a664e6a88e1af104a74457d99301161",
- "reference": "da3c28025a664e6a88e1af104a74457d99301161",
+ "url": "https://api.github.com/repos/symfony/security-http/zipball/148e038b91c8cc3e42aee177f8b0117437077c9b",
+ "reference": "148e038b91c8cc3e42aee177f8b0117437077c9b",
"shasum": ""
},
"require": {
@@ -15712,7 +15871,7 @@
"description": "Symfony Security Component - HTTP Integration",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/security-http/tree/v7.4.13"
+ "source": "https://github.com/symfony/security-http/tree/v7.4.14"
},
"funding": [
{
@@ -15732,20 +15891,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-25T06:06:12+00:00"
+ "time": "2026-06-19T08:40:54+00:00"
},
{
"name": "symfony/serializer",
- "version": "v7.4.10",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
- "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd"
+ "reference": "55acb01b9c8a5211dfbaf68c314d90d0ed2cc3d1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/serializer/zipball/268c5aa6c4bd675eddd89348e7ecac292a843ddd",
- "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/55acb01b9c8a5211dfbaf68c314d90d0ed2cc3d1",
+ "reference": "55acb01b9c8a5211dfbaf68c314d90d0ed2cc3d1",
"shasum": ""
},
"require": {
@@ -15816,7 +15975,7 @@
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/serializer/tree/v7.4.10"
+ "source": "https://github.com/symfony/serializer/tree/v7.4.14"
},
"funding": [
{
@@ -15836,20 +15995,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-03T13:03:28+00:00"
+ "time": "2026-06-27T08:31:18+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a"
+ "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a",
- "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0",
+ "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0",
"shasum": ""
},
"require": {
@@ -15903,7 +16062,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -15923,7 +16082,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-28T09:44:51+00:00"
+ "time": "2026-06-16T09:55:08+00:00"
},
{
"name": "symfony/stimulus-bundle",
@@ -16157,16 +16316,16 @@
},
{
"name": "symfony/translation",
- "version": "v7.4.10",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde"
+ "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/ada7578c30dd5feaa8259cff3e885069ea81ddde",
- "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/a1af4dacb24eb7ef4f1ca71b94da8ddbce572281",
+ "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281",
"shasum": ""
},
"require": {
@@ -16233,7 +16392,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.4.10"
+ "source": "https://github.com/symfony/translation/tree/v7.4.14"
},
"funding": [
{
@@ -16253,20 +16412,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-06T11:19:24+00:00"
+ "time": "2026-06-06T09:33:19+00:00"
},
{
"name": "symfony/translation-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
- "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d"
+ "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d",
- "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621",
+ "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621",
"shasum": ""
},
"require": {
@@ -16315,7 +16474,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -16335,20 +16494,20 @@
"type": "tidelift"
}
],
- "time": "2026-01-05T13:30:16+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/twig-bridge",
- "version": "v7.4.12",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
- "reference": "81663873d946531129c76c65e80b681ce99c0e89"
+ "reference": "e4574ab4d5411a7c495d4189b15a8ecfbc720332"
},
"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/e4574ab4d5411a7c495d4189b15a8ecfbc720332",
+ "reference": "e4574ab4d5411a7c495d4189b15a8ecfbc720332",
"shasum": ""
},
"require": {
@@ -16430,7 +16589,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.14"
},
"funding": [
{
@@ -16450,20 +16609,20 @@
"type": "tidelift"
}
],
- "time": "2026-04-29T17:13:54+00:00"
+ "time": "2026-06-17T13:16:29+00:00"
},
{
"name": "symfony/twig-bundle",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bundle.git",
- "reference": "ba1e06d7ff1ebb1d1799b6608d925f4eaba88d95"
+ "reference": "11b69c64efdd0c3465403bb2747bf4585add1ec7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/ba1e06d7ff1ebb1d1799b6608d925f4eaba88d95",
- "reference": "ba1e06d7ff1ebb1d1799b6608d925f4eaba88d95",
+ "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/11b69c64efdd0c3465403bb2747bf4585add1ec7",
+ "reference": "11b69c64efdd0c3465403bb2747bf4585add1ec7",
"shasum": ""
},
"require": {
@@ -16520,7 +16679,7 @@
"description": "Provides a tight integration of Twig into the Symfony full-stack framework",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/twig-bundle/tree/v7.4.8"
+ "source": "https://github.com/symfony/twig-bundle/tree/v7.4.14"
},
"funding": [
{
@@ -16540,7 +16699,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-24T13:12:05+00:00"
+ "time": "2026-06-05T06:22:21+00:00"
},
{
"name": "symfony/type-info",
@@ -16889,16 +17048,16 @@
},
{
"name": "symfony/validator",
- "version": "v7.4.10",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
- "reference": "c76458623af9a3fe3b2e5b09b36453f334c2a361"
+ "reference": "306d904336166d001751759351d40d5e82312596"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/validator/zipball/c76458623af9a3fe3b2e5b09b36453f334c2a361",
- "reference": "c76458623af9a3fe3b2e5b09b36453f334c2a361",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/306d904336166d001751759351d40d5e82312596",
+ "reference": "306d904336166d001751759351d40d5e82312596",
"shasum": ""
},
"require": {
@@ -16969,7 +17128,7 @@
"description": "Provides tools to validate values",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/validator/tree/v7.4.10"
+ "source": "https://github.com/symfony/validator/tree/v7.4.14"
},
"funding": [
{
@@ -16989,20 +17148,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-05T15:30:56+00:00"
+ "time": "2026-06-27T06:16:12+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd"
+ "reference": "9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd",
- "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358",
+ "reference": "9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358",
"shasum": ""
},
"require": {
@@ -17056,7 +17215,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.4.8"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.4.14"
},
"funding": [
{
@@ -17076,20 +17235,20 @@
"type": "tidelift"
}
],
- "time": "2026-03-30T13:44:50+00:00"
+ "time": "2026-06-08T20:24:16+00:00"
},
{
"name": "symfony/var-exporter",
- "version": "v7.4.9",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
- "reference": "22e03a49c95ef054a43601cd159b222bfab1c701"
+ "reference": "0118811b1d59f323bf131250b3fb919febfece28"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/22e03a49c95ef054a43601cd159b222bfab1c701",
- "reference": "22e03a49c95ef054a43601cd159b222bfab1c701",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0118811b1d59f323bf131250b3fb919febfece28",
+ "reference": "0118811b1d59f323bf131250b3fb919febfece28",
"shasum": ""
},
"require": {
@@ -17137,7 +17296,7 @@
"serialize"
],
"support": {
- "source": "https://github.com/symfony/var-exporter/tree/v7.4.9"
+ "source": "https://github.com/symfony/var-exporter/tree/v7.4.14"
},
"funding": [
{
@@ -17157,7 +17316,7 @@
"type": "tidelift"
}
],
- "time": "2026-04-18T13:18:21+00:00"
+ "time": "2026-06-27T08:41:53+00:00"
},
{
"name": "symfony/web-link",
@@ -17248,16 +17407,16 @@
},
{
"name": "symfony/webpack-encore-bundle",
- "version": "v2.4.0",
+ "version": "v2.4.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/webpack-encore-bundle.git",
- "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e"
+ "reference": "cac8d6c722999c8add9272f9de6e8079628df4f5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e",
- "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e",
+ "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/cac8d6c722999c8add9272f9de6e8079628df4f5",
+ "reference": "cac8d6c722999c8add9272f9de6e8079628df4f5",
"shasum": ""
},
"require": {
@@ -17300,7 +17459,7 @@
"description": "Integration of your Symfony app with Webpack Encore",
"support": {
"issues": "https://github.com/symfony/webpack-encore-bundle/issues",
- "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.0"
+ "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.1"
},
"funding": [
{
@@ -17320,20 +17479,20 @@
"type": "tidelift"
}
],
- "time": "2025-11-27T13:41:46+00:00"
+ "time": "2026-06-24T07:21:58+00:00"
},
{
"name": "symfony/yaml",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c"
+ "reference": "f8f328665ace2370d1e10645b807ba1646dc7dcc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/a7ec3b1156faf8815db7683ec7c1e7338e6f977c",
- "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/f8f328665ace2370d1e10645b807ba1646dc7dcc",
+ "reference": "f8f328665ace2370d1e10645b807ba1646dc7dcc",
"shasum": ""
},
"require": {
@@ -17376,7 +17535,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v7.4.13"
+ "source": "https://github.com/symfony/yaml/tree/v7.4.14"
},
"funding": [
{
@@ -17396,20 +17555,20 @@
"type": "tidelift"
}
],
- "time": "2026-05-25T06:06:12+00:00"
+ "time": "2026-06-08T20:24:16+00:00"
},
{
"name": "symplify/easy-coding-standard",
- "version": "13.1.5",
+ "version": "13.2.3",
"source": {
"type": "git",
- "url": "https://github.com/easy-coding-standard/ecs.git",
- "reference": "96294d652c17ccffabb7d36f9d116dd5f0e6b84e"
+ "url": "https://github.com/ecsphp/ecs.git",
+ "reference": "94f56bce0420d4e837a85c4b2c6501293a5974eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/easy-coding-standard/ecs/zipball/96294d652c17ccffabb7d36f9d116dd5f0e6b84e",
- "reference": "96294d652c17ccffabb7d36f9d116dd5f0e6b84e",
+ "url": "https://api.github.com/repos/ecsphp/ecs/zipball/94f56bce0420d4e837a85c4b2c6501293a5974eb",
+ "reference": "94f56bce0420d4e837a85c4b2c6501293a5974eb",
"shasum": ""
},
"require": {
@@ -17444,22 +17603,22 @@
"static analysis"
],
"support": {
- "source": "https://github.com/easy-coding-standard/ecs/tree/13.1.5"
+ "source": "https://github.com/ecsphp/ecs/tree/13.2.3"
},
- "time": "2026-05-30T09:12:26+00:00"
+ "time": "2026-06-15T22:08:41+00:00"
},
{
"name": "tecnickcom/tc-lib-barcode",
- "version": "2.7.0",
+ "version": "2.11.1",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/tc-lib-barcode.git",
- "reference": "4e53047a4ba4ed592ae677b3729ce9bfeae1cfbb"
+ "reference": "69238f94a1e46332ebc057fddf5bba2f776d56f6"
},
"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/69238f94a1e46332ebc057fddf5bba2f776d56f6",
+ "reference": "69238f94a1e46332ebc057fddf5bba2f776d56f6",
"shasum": ""
},
"require": {
@@ -17468,12 +17627,11 @@
"ext-gd": "*",
"ext-pcre": "*",
"php": ">=8.2",
- "tecnickcom/tc-lib-color": "^2.7"
+ "tecnickcom/tc-lib-color": "^2.12"
},
"require-dev": {
"pdepend/pdepend": "^2.16",
- "phpcompatibility/php-compatibility": "^10.0.0@dev",
- "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5"
+ "phpunit/phpunit": "^11.5 || ^12.5 || ^13.2"
},
"type": "library",
"autoload": {
@@ -17545,20 +17703,20 @@
"type": "github"
}
],
- "time": "2026-05-22T07:09:18+00:00"
+ "time": "2026-06-26T10:37:56+00:00"
},
{
"name": "tecnickcom/tc-lib-color",
- "version": "2.8.0",
+ "version": "2.12.2",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/tc-lib-color.git",
- "reference": "6947cc9fffe23a21642279b8ab73a43f3311c5f9"
+ "reference": "ba7d43d9bb06946c9717a4e3c6aa73346266cdbf"
},
"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/ba7d43d9bb06946c9717a4e3c6aa73346266cdbf",
+ "reference": "ba7d43d9bb06946c9717a4e3c6aa73346266cdbf",
"shasum": ""
},
"require": {
@@ -17567,8 +17725,7 @@
},
"require-dev": {
"pdepend/pdepend": "^2.16",
- "phpcompatibility/php-compatibility": "^10.0.0@dev",
- "phpunit/phpunit": "^13.1 || ^12.5 || ^11.5"
+ "phpunit/phpunit": "^11.5 || ^12.5 || ^13.2"
},
"type": "library",
"autoload": {
@@ -17613,7 +17770,7 @@
"type": "github"
}
],
- "time": "2026-05-22T06:55:57+00:00"
+ "time": "2026-06-26T10:26:00+00:00"
},
{
"name": "thecodingmachine/safe",
@@ -18730,16 +18887,16 @@
},
{
"name": "webmozart/assert",
- "version": "2.4.0",
+ "version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
- "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155"
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155",
- "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
"shasum": ""
},
"require": {
@@ -18790,9 +18947,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.4.1"
},
- "time": "2026-05-20T13:07:01+00:00"
+ "time": "2026-06-15T15:31:57+00:00"
},
{
"name": "willdurand/negotiation",
@@ -19493,21 +19650,21 @@
},
{
"name": "phpstan/phpstan-doctrine",
- "version": "2.0.25",
+ "version": "2.0.27",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git",
- "reference": "e20e8bf3223ae6eba9c4b5987c391d922e094b3c"
+ "reference": "39b4ca45a07cdd6366eeefa2f7a993cddf3b9f9f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e20e8bf3223ae6eba9c4b5987c391d922e094b3c",
- "reference": "e20e8bf3223ae6eba9c4b5987c391d922e094b3c",
+ "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/39b4ca45a07cdd6366eeefa2f7a993cddf3b9f9f",
+ "reference": "39b4ca45a07cdd6366eeefa2f7a993cddf3b9f9f",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
- "phpstan/phpstan": "^2.1.34"
+ "phpstan/phpstan": "^2.2.2"
},
"conflict": {
"doctrine/collections": "<1.0",
@@ -19564,9 +19721,9 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
- "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.25"
+ "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.27"
},
- "time": "2026-06-02T20:27:36+00:00"
+ "time": "2026-06-10T10:39:35+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
@@ -19621,16 +19778,16 @@
},
{
"name": "phpstan/phpstan-symfony",
- "version": "2.0.19",
+ "version": "2.0.20",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git",
- "reference": "546071ed7f80a89ec30909346eb7cc741800740a"
+ "reference": "53f1a6462dbe71fad36ce054caf5e1b725b740fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/546071ed7f80a89ec30909346eb7cc741800740a",
- "reference": "546071ed7f80a89ec30909346eb7cc741800740a",
+ "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/53f1a6462dbe71fad36ce054caf5e1b725b740fd",
+ "reference": "53f1a6462dbe71fad36ce054caf5e1b725b740fd",
"shasum": ""
},
"require": {
@@ -19689,9 +19846,9 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
- "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.19"
+ "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.20"
},
- "time": "2026-05-29T12:52:44+00:00"
+ "time": "2026-06-16T09:17:35+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -20152,21 +20309,21 @@
},
{
"name": "rector/rector",
- "version": "2.4.5",
+ "version": "2.5.2",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "cbd86024be5014d3c14d9f0b3f7aae8ecbffd62c"
+ "reference": "49ff6339174bdbdf50b0b35ecbcff14a05ac9e24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/cbd86024be5014d3c14d9f0b3f7aae8ecbffd62c",
- "reference": "cbd86024be5014d3c14d9f0b3f7aae8ecbffd62c",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/49ff6339174bdbdf50b0b35ecbcff14a05ac9e24",
+ "reference": "49ff6339174bdbdf50b0b35ecbcff14a05ac9e24",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
- "phpstan/phpstan": "^2.1.56"
+ "phpstan/phpstan": "^2.2.2"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -20200,7 +20357,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.4.5"
+ "source": "https://github.com/rectorphp/rector/tree/2.5.2"
},
"funding": [
{
@@ -20208,7 +20365,7 @@
"type": "github"
}
],
- "time": "2026-05-26T21:03:22+00:00"
+ "time": "2026-06-22T11:39:33+00:00"
},
{
"name": "roave/security-advisories",
@@ -20216,12 +20373,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
- "reference": "b000295a5fd02609ee7ad31d084cfa904af8a69a"
+ "reference": "36ba91e82e1b493faef2c13277d6bd2669ea9f31"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/b000295a5fd02609ee7ad31d084cfa904af8a69a",
- "reference": "b000295a5fd02609ee7ad31d084cfa904af8a69a",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/36ba91e82e1b493faef2c13277d6bd2669ea9f31",
+ "reference": "36ba91e82e1b493faef2c13277d6bd2669ea9f31",
"shasum": ""
},
"conflict": {
@@ -20238,6 +20395,7 @@
"aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7",
"aimeos/aimeos-laravel": "==2021.10",
"aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5",
+ "aimeos/pagible": "<0.10.4",
"airesvsg/acf-to-rest-api": "<=3.1",
"akaunting/akaunting": "<2.1.13",
"akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53",
@@ -20322,13 +20480,14 @@
"bytefury/crater": "<6.0.2",
"cachethq/cachet": "<2.5.1",
"cadmium-org/cadmium-cms": "<=0.4.9",
- "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|>=5.2.10,<5.2.12|==5.3",
+ "cakephp/authentication": "<3.3.6|>=4,<4.1.1",
+ "cakephp/cakephp": "<4.5.11|>=4.6,<4.6.4|>=5,<5.1.7|>=5.2,<5.2.13|>=5.3,<5.3.6",
"cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10",
"cardgate/magento2": "<2.0.33",
"cardgate/woocommerce": "<=3.1.15",
- "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
+ "cart2quote/module-quotation": ">=4.1.6,<4.4.6|>=5,<5.4.4",
"cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
- "cartalyst/sentry": "<=2.1.6",
+ "cartalyst/sentry": "<2.1.7",
"catfan/medoo": "<1.7.5",
"causal/oidc": "<4",
"cecil/cecil": "<7.47.1",
@@ -20346,7 +20505,7 @@
"code16/sharp": "<9.22",
"codeception/codeception": "<3.1.3|>=4,<4.1.22",
"codeigniter/framework": "<3.1.10",
- "codeigniter4/framework": "<4.6.2",
+ "codeigniter4/framework": "<4.7.2",
"codeigniter4/shield": "<1.0.0.0-beta8",
"codiad/codiad": "<=2.8.4",
"codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9",
@@ -20354,7 +20513,7 @@
"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",
- "concrete5/concrete5": "<9.4.8",
+ "concrete5/concrete5": "<9.5.1",
"concrete5/core": "<8.5.8|>=9,<9.1",
"contao-components/mediaelement": ">=2.14.2,<2.21.1",
"contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4",
@@ -20366,12 +20525,13 @@
"coreshop/core-shop": "<4.1.9|==5",
"corveda/phpsandbox": "<1.3.5",
"cosenary/instagram": "<=2.3",
+ "cotonti/cotonti": "<=1",
"couleurcitron/tarteaucitron-wp": "<0.3",
"cpsit/typo3-mailqueue": "<0.4.5|>=0.5,<0.5.2",
"craftcms/aws-s3": ">=2.0.2,<=2.2.4",
"craftcms/azure-blob": ">=2.0.0.0-beta1,<=2.1",
- "craftcms/cms": "<4.17.12|>=5,<5.9.18",
- "craftcms/commerce": ">=4,<4.11|>=5,<5.6",
+ "craftcms/cms": "<4.18|>=5,<5.10",
+ "craftcms/commerce": ">=4,<=4.11.1|>=5,<=5.6.4",
"craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1",
"craftcms/craft": ">=3.5,<=4.16.17|>=5.0.0.0-RC1-dev,<=5.8.21",
"craftcms/google-cloud": ">=2.0.0.0-beta1,<=2.2",
@@ -20426,7 +20586,7 @@
"drupal/commerce_alphabank_redirect": "<1.0.3",
"drupal/commerce_eurobank_redirect": "<2.1.1",
"drupal/config_split": "<1.10|>=2,<2.0.2",
- "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.5.9|>=10.6,<10.6.7|>=11,<11.2.11|>=11.3,<11.3.7",
+ "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.5.10|>=10.6,<10.6.9|>=11,<11.2.12|>=11.3,<11.3.10",
"drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8",
"drupal/currency": "<3.5",
"drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8",
@@ -20497,10 +20657,11 @@
"feehi/cms": "<=2.1.1",
"feehi/feehicms": "<=2.1.1",
"fenom/fenom": "<=2.12.1",
- "filament/actions": ">=3.2,<3.2.123",
- "filament/filament": ">=4,<4.3.1",
- "filament/infolists": ">=3,<3.2.115",
- "filament/tables": ">=3,<3.2.115|>=4,<4.8.5|>=5,<5.3.5",
+ "filament/actions": ">=3.2,<3.2.123|>=4,<=4.11.3|>=5,<=5.6.3",
+ "filament/filament": ">=3,<=3.3.51|>=4,<4.11.5|>=5,<5.6.5",
+ "filament/forms": ">=3,<=3.3.52",
+ "filament/infolists": ">=3,<3.2.115|>=4,<=4.11.4|>=5,<=5.6.4",
+ "filament/tables": ">=3,<=3.3.50|>=4,<=4.11.4|>=5,<=5.6.4",
"filegator/filegator": "<7.8",
"filp/whoops": "<2.1.13",
"fineuploader/php-traditional-server": "<=1.2.2",
@@ -20546,10 +20707,10 @@
"georgringer/news": "<10.0.4|>=11,<11.4.4|>=12,<12.3.2|>=13,<13.0.2|>=14,<14.0.3",
"geshi/geshi": "<=1.0.9.1",
"getformwork/formwork": "<=2.3.3",
- "getgrav/grav": "<=2.0.0.0-RC1",
+ "getgrav/grav": "<=2.0.0.0-RC8",
"getgrav/grav-plugin-api": "<1.0.0.0-beta15",
"getgrav/grav-plugin-form": "<9.1",
- "getkirby/cms": "<=4.9|>=5,<=5.4",
+ "getkirby/cms": "<=4.9.3|>=5,<=5.4.3",
"getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1",
"getkirby/panel": "<2.5.14",
"getkirby/starterkit": "<=3.7.0.2",
@@ -20564,11 +20725,12 @@
"gp247/core": "<1.1.24",
"gree/jose": "<2.2.1",
"gregwar/rst": "<1.0.3",
- "grumpydictator/firefly-iii": "<6.1.17|>=6.4.23,<=6.5",
+ "grumpydictator/firefly-iii": "<=6.6.2",
"gugoan/economizzer": "<=0.9.0.0-beta1",
- "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5",
+ "guzzlehttp/guzzle": "<7.12.1",
+ "guzzlehttp/guzzle-services": "<1.5.4",
"guzzlehttp/oauth-subscriber": "<0.8.1",
- "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5",
+ "guzzlehttp/psr7": "<2.12.1",
"haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2",
"handcraftedinthealps/goodby-csv": "<1.4.3",
"harvesthq/chosen": "<1.8.7",
@@ -20611,7 +20773,7 @@
"inter-mediator/inter-mediator": "==5.5",
"intercom/intercom-php": "==5.0.2",
"invoiceninja/invoiceninja": "<5.13.4",
- "ipl/web": "<=0.13",
+ "ipl/web": "<=0.10.2|>=0.11,<=0.13",
"islandora/crayfish": "<4.1",
"islandora/islandora": ">=2,<2.4.1",
"ivankristianto/phpwhois": "<=4.3",
@@ -20623,6 +20785,7 @@
"jasig/phpcas": "<1.3.3",
"jbartels/wec-map": "<3.0.3",
"jcbrand/converse.js": "<3.3.3",
+ "jleehr/canto-saas-api": "<=2",
"joedolson/my-calendar": "<3.7.7",
"joelbutcher/socialstream": "<5.6|>=6,<6.2",
"johnbillion/query-monitor": "<3.20.4",
@@ -20666,7 +20829,7 @@
"lara-zeus/artemis": ">=1,<=1.0.6",
"lara-zeus/dynamic-dashboard": ">=3,<=3.0.1",
"laravel/fortify": "<1.11.1",
- "laravel/framework": "<12.60|>=13,<13.10",
+ "laravel/framework": "<12.61.1|>=13,<13.12",
"laravel/laravel": ">=5.4,<5.4.22",
"laravel/passport": ">=13,<13.7.1",
"laravel/pulse": "<1.3.1",
@@ -20757,6 +20920,7 @@
"movim/moxl": ">=0.8,<=0.10",
"movingbytes/social-network": "<=1.2.1",
"mpdf/mpdf": "<=7.1.7",
+ "mtdowling/jmespath.php": "<2.9.1",
"munkireport/comment": "<4",
"munkireport/managedinstalls": "<2.6",
"munkireport/munki_facts": "<1.5",
@@ -20826,6 +20990,7 @@
"paragonie/random_compat": "<2",
"paragonie/sodium_compat": "<1.24|>=2,<2.5",
"passbolt/passbolt_api": "<4.6.2",
+ "paymenter/paymenter": "<1.5",
"paypal/adaptivepayments-sdk-php": "<=3.9.2",
"paypal/invoice-sdk-php": "<=3.9",
"paypal/merchant-sdk-php": "<3.12",
@@ -20839,22 +21004,25 @@
"personnummer/personnummer": "<3.0.2",
"ph7software/ph7builder": "<=17.9.1",
"phanan/koel": "<=9.3.4",
+ "pheditor/pheditor": ">=2.0.1,<=2.0.3",
"phenx/php-svg-lib": "<0.5.2",
"php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5",
"php-mod/curl": "<2.3.2",
- "phpbb/phpbb": "<3.3.11",
+ "php-standard-library/h2": ">=6.1,<6.1.2|>=6.2,<6.2.1",
+ "php-standard-library/php-standard-library": ">=6.1,<6.1.2|>=6.2,<6.2.1",
+ "phpbb/phpbb": "<3.3.16|==4.0.0.0-alpha1",
"phpems/phpems": ">=6,<=6.1.3",
"phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7",
"phpmailer/phpmailer": "<6.5",
"phpmussel/phpmussel": ">=1,<1.6",
"phpmyadmin/phpmyadmin": "<5.2.2",
- "phpmyfaq/phpmyfaq": "<4.1.3",
+ "phpmyfaq/phpmyfaq": "<4.1.4",
"phpoffice/common": "<0.2.9",
"phpoffice/math": "<=0.2",
"phpoffice/phpexcel": "<=1.8.2",
- "phpoffice/phpspreadsheet": "<=1.30.3|>=2,<=2.1.15|>=2.2,<=2.4.4|>=3,<=3.10.4|>=4,<=5.6",
+ "phpoffice/phpspreadsheet": "<=1.30.4|>=2,<=2.1.15|>=2.2,<=2.4.4|>=3,<=3.10.4|>=4,<=5.6",
"phppgadmin/phppgadmin": "<=7.13",
- "phpseclib/phpseclib": "<=2.0.53|>=3,<=3.0.51",
+ "phpseclib/phpseclib": "<=2.0.54|>=3,<=3.0.53",
"phpservermon/phpservermon": "<3.6",
"phpsysinfo/phpsysinfo": "<3.4.3",
"phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8|>=12.5.21,<12.5.22|>=13.1.5,<13.1.6",
@@ -20870,7 +21038,7 @@
"pimcore/demo": "<10.3",
"pimcore/ecommerce-framework-bundle": "<1.0.10",
"pimcore/perspective-editor": "<1.5.1",
- "pimcore/pimcore": "<=12.3.6",
+ "pimcore/pimcore": "<=12.3.8",
"pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1",
"piwik/piwik": "<1.11",
"pixelfed/pixelfed": "<0.12.5",
@@ -20878,6 +21046,8 @@
"pocketmine/bedrock-protocol": "<8.0.2",
"pocketmine/pocketmine-mp": "<5.42.1",
"pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1",
+ "pontedilana/php-weasyprint": "<=2.5.1",
+ "poweradmin/poweradmin": "<4.2.4|>=4.3,<4.3.3",
"pressbooks/pressbooks": "<5.18",
"prestashop/autoupgrade": ">=4,<4.10.1",
"prestashop/blockreassurance": "<=5.1.3",
@@ -20893,8 +21063,8 @@
"prestashop/ps_linklist": "<3.1",
"privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3",
"processwire/processwire": "<=3.0.255",
- "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7",
- "propel/propel1": ">=1,<=1.7.1",
+ "propel/propel": ">=2.0.0.0-alpha1,<2.0.0.0-alpha8",
+ "propel/propel1": ">=1,<1.7.2",
"psy/psysh": "<=0.11.22|>=0.12,<=0.12.18",
"pterodactyl/panel": "<1.12.3",
"ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2",
@@ -20947,7 +21117,7 @@
"shopware/core": "<6.6.10.18-dev|>=6.7,<6.7.10.1-dev",
"shopware/platform": "<6.6.10.18-dev|>=6.7,<6.7.10.1-dev",
"shopware/production": "<=6.3.5.2",
- "shopware/shopware": "<=5.7.17|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev",
+ "shopware/shopware": "<=6.3.5.2|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev",
"shopware/storefront": "<6.6.10.10-dev|>=6.7,<6.7.5.1-dev",
"shopxo/shopxo": "<=6.4",
"showdoc/showdoc": "<3.8.1",
@@ -20955,10 +21125,10 @@
"silverstripe-australia/advancedreports": ">=1,<=2",
"silverstripe/admin": "<1.13.19|>=2,<2.1.8",
"silverstripe/assets": "<2.4.5|>=3,<3.1.3",
- "silverstripe/cms": "<4.11.3",
+ "silverstripe/cms": "<6.2.1",
"silverstripe/comments": ">=1.3,<3.1.1",
- "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
- "silverstripe/framework": "<5.3.23",
+ "silverstripe/forum": "<0.6.2|>=0.7,<0.7.4",
+ "silverstripe/framework": "<6.2.2",
"silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3",
"silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1",
"silverstripe/recipe-cms": ">=4.5,<4.5.3",
@@ -20968,7 +21138,8 @@
"silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1",
"silverstripe/subsites": ">=2,<2.6.1",
"silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1",
- "silverstripe/userforms": "<3|>=5,<5.4.2",
+ "silverstripe/userforms": "<6.4.9|>=7,<7.0.7|>=7.1,<7.1.1",
+ "silverstripe/versioned": "<3.2.1",
"silverstripe/versioned-admin": ">=1,<1.11.1",
"simogeo/filemanager": "<=2.5",
"simple-updates/phpwhois": "<=1",
@@ -20987,12 +21158,13 @@
"sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3",
"sjbr/static-info-tables": "<2.3.1",
"slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1",
- "slim/slim": "<2.6",
+ "slim/slim": "<2.6|>=4.4,<=4.15.1",
"slub/slub-events": "<3.0.3",
"smarty/smarty": "<4.5.3|>=5,<5.1.1",
- "snipe/snipe-it": "<8.4.1",
+ "snipe/snipe-it": "<=8.6.1",
"socalnick/scn-social-auth": "<1.15.2",
"socialiteproviders/steam": "<1.1",
+ "solidinvoice/solidinvoice": "<=2.3.15",
"solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6",
"soosyze/soosyze": "<=2",
"spatie/browsershot": "<5.0.5",
@@ -21001,6 +21173,7 @@
"spencer14420/sp-php-email-handler": "<1",
"spipu/html2pdf": "<5.2.8",
"spiral/roadrunner": "<2025.1",
+ "spomky-labs/otphp": "<11.4.3",
"spoon/library": "<1.4.1",
"spoonity/tcpdf": "<6.2.22",
"squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
@@ -21009,7 +21182,7 @@
"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.74|>=6,<6.20.3",
"stormpath/sdk": "<9.9.99",
"studio-42/elfinder": "<=2.1.67",
"studiomitte/friendlycaptcha": "<0.1.4",
@@ -21029,6 +21202,7 @@
"sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2",
"sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4",
"sylius/sylius": "<1.9.12|>=1.10,<1.10.16|>=1.11,<1.11.17|>=1.12,<=1.12.22|>=1.13,<=1.13.14|>=1.14,<=1.14.17|>=2,<=2.0.15|>=2.1,<=2.1.11|>=2.2,<=2.2.2",
+ "symbiote/silverstripe-advancedworkflow": "<6.4.5|>=7,<7.1.3|>=7.2,<7.2.1",
"symbiote/silverstripe-multivaluefield": ">=3,<3.1",
"symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4",
"symbiote/silverstripe-seed": "<6.0.3",
@@ -21074,7 +21248,9 @@
"symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8|>=6.4.24,<6.4.40",
"symfony/twilio-notifier": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12",
"symfony/ux-autocomplete": "<2.36|>=3,<3.1",
+ "symfony/ux-icons": ">=2.17,<2.36.1|>=3,<3.2",
"symfony/ux-live-component": "<2.36|>=3,<3.1",
+ "symfony/ux-toolkit": ">=2.32,<2.36.1|>=3,<3.2",
"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",
@@ -21094,7 +21270,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.4",
"tikiwiki/tiki-manager": "<=17.1",
"timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1",
"tinymce/tinymce": "<7.9.3|>=8,<8.5.1",
@@ -21118,22 +21294,23 @@
"twig/twig": "<3.27",
"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",
+ "typo3/cms-backend": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
"typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18",
- "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1",
+ "typo3/cms-core": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18",
"typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1",
"typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
"typo3/cms-felogin": ">=4.2,<4.2.3",
+ "typo3/cms-filelist": ">=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1",
- "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-form": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5",
- "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2",
"typo3/cms-lowlevel": ">=11,<=11.5.41",
"typo3/cms-recordlist": ">=11,<11.5.48",
- "typo3/cms-recycler": ">=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1",
+ "typo3/cms-recycler": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-redirects": ">=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1",
"typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30",
"typo3/cms-scheduler": ">=11,<=11.5.41",
@@ -21141,7 +21318,7 @@
"typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11",
"typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18",
"typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
- "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3",
+ "typo3/html-sanitizer": "<2.3.2",
"typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3",
"typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1",
"typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5",
@@ -21173,8 +21350,11 @@
"wapplersystems/a21glossary": "<=0.4.10",
"web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4|>=5.3,<5.3.1",
"web-auth/webauthn-lib": ">=4.5,<4.9|>=5.2,<5.2.4",
- "web-auth/webauthn-symfony-bundle": ">=5.2,<5.2.4",
+ "web-auth/webauthn-symfony-bundle": "<5.3.4",
"web-feet/coastercms": "==5.5",
+ "web-token/jwt-experimental": "<=4.1.6",
+ "web-token/jwt-framework": "<=4.2.99",
+ "web-token/jwt-library": "<3.4.10|>=4,<4.0.7|>=4.1,<4.1.7",
"web-tp3/wec_map": "<3.0.3",
"webbuilders-group/silverstripe-kapost-bridge": "<0.4",
"webcoast/deferred-image-processing": "<1.0.2",
@@ -21300,7 +21480,7 @@
"type": "tidelift"
}
],
- "time": "2026-06-05T20:39:10+00:00"
+ "time": "2026-06-26T23:29:05+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -22342,16 +22522,16 @@
},
{
"name": "symfony/browser-kit",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "41850d8f8ddef9a9cd7314fa9f4902cf48885521"
+ "reference": "bb28e8761a6c33975972948010f00d4a10f0a634"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/41850d8f8ddef9a9cd7314fa9f4902cf48885521",
- "reference": "41850d8f8ddef9a9cd7314fa9f4902cf48885521",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/bb28e8761a6c33975972948010f00d4a10f0a634",
+ "reference": "bb28e8761a6c33975972948010f00d4a10f0a634",
"shasum": ""
},
"require": {
@@ -22391,7 +22571,7 @@
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/browser-kit/tree/v7.4.8"
+ "source": "https://github.com/symfony/browser-kit/tree/v7.4.14"
},
"funding": [
{
@@ -22411,7 +22591,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-24T13:12:05+00:00"
+ "time": "2026-06-08T20:24:16+00:00"
},
{
"name": "symfony/debug-bundle",
@@ -22589,16 +22769,16 @@
},
{
"name": "symfony/phpunit-bridge",
- "version": "v7.4.8",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
- "reference": "140bbbe1cd1c21a084494ccddeee33f3c3381d7d"
+ "reference": "11eeee9d109963145e66f5b1919e5cf5411da58b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/140bbbe1cd1c21a084494ccddeee33f3c3381d7d",
- "reference": "140bbbe1cd1c21a084494ccddeee33f3c3381d7d",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/11eeee9d109963145e66f5b1919e5cf5411da58b",
+ "reference": "11eeee9d109963145e66f5b1919e5cf5411da58b",
"shasum": ""
},
"require": {
@@ -22650,7 +22830,7 @@
"testing"
],
"support": {
- "source": "https://github.com/symfony/phpunit-bridge/tree/v7.4.8"
+ "source": "https://github.com/symfony/phpunit-bridge/tree/v7.4.14"
},
"funding": [
{
@@ -22670,20 +22850,20 @@
"type": "tidelift"
}
],
- "time": "2026-03-24T13:12:05+00:00"
+ "time": "2026-06-08T20:24:16+00:00"
},
{
"name": "symfony/web-profiler-bundle",
- "version": "v7.4.13",
+ "version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-profiler-bundle.git",
- "reference": "153076bb3f0690fff0e95e55cc06358b22f236a5"
+ "reference": "5dead36a9202a6008b54b95308bce7ab97a41fe0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/153076bb3f0690fff0e95e55cc06358b22f236a5",
- "reference": "153076bb3f0690fff0e95e55cc06358b22f236a5",
+ "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/5dead36a9202a6008b54b95308bce7ab97a41fe0",
+ "reference": "5dead36a9202a6008b54b95308bce7ab97a41fe0",
"shasum": ""
},
"require": {
@@ -22740,7 +22920,7 @@
"dev"
],
"support": {
- "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.13"
+ "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.14"
},
"funding": [
{
@@ -22760,7 +22940,7 @@
"type": "tidelift"
}
],
- "time": "2026-05-23T16:05:06+00:00"
+ "time": "2026-06-05T06:22:21+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/config/packages/ai_lm_studio_platform.yaml b/config/packages/ai_lm_studio_platform.yaml
index 0e4287e0..1d832763 100644
--- a/config/packages/ai_lm_studio_platform.yaml
+++ b/config/packages/ai_lm_studio_platform.yaml
@@ -2,3 +2,4 @@ ai:
platform:
lmstudio:
host_url: '%env(string:settings:ai_lmstudio:hostURL)%'
+ http_client: 'app.http_client.ai_lmstudio'
diff --git a/config/packages/ai_ollama_platform.yaml b/config/packages/ai_ollama_platform.yaml
new file mode 100644
index 00000000..67ebe190
--- /dev/null
+++ b/config/packages/ai_ollama_platform.yaml
@@ -0,0 +1,6 @@
+ai:
+ platform:
+ ollama:
+ endpoint: '%env(string:settings:ai_ollama:endpoint)%'
+ api_key: '%env(string:settings:ai_ollama:apiKey)%'
+ http_client: 'app.http_client.ai_ollama'
diff --git a/config/packages/ai_open_router_platform.yaml b/config/packages/ai_open_router_platform.yaml
index d34de592..53eb20b9 100644
--- a/config/packages/ai_open_router_platform.yaml
+++ b/config/packages/ai_open_router_platform.yaml
@@ -2,3 +2,4 @@ ai:
platform:
openrouter:
api_key: '%env(string:settings:ai_openrouter:apiKey)%'
+ http_client: 'app.http_client.ai_openrouter'
diff --git a/config/packages/dev/easy_log_handler.yaml b/config/packages/dev/easy_log_handler.yaml
deleted file mode 100644
index 27bfc608..00000000
--- a/config/packages/dev/easy_log_handler.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-services:
- EasyCorp\EasyLog\EasyLogHandler:
- public: false
- arguments: ['%kernel.logs_dir%/%kernel.environment%.log']
-
-#// FIXME: How to add this configuration automatically without messing up with the monolog configuration?
-#monolog:
-# handlers:
-# buffered:
-# type: buffer
-# handler: easylog
-# channels: ['!event']
-# level: debug
-# easylog:
-# type: service
-# id: EasyCorp\EasyLog\EasyLogHandler
diff --git a/config/packages/doctrine.php b/config/packages/doctrine.php
index e5be011f..f62cdcfb 100644
--- a/config/packages/doctrine.php
+++ b/config/packages/doctrine.php
@@ -20,16 +20,16 @@
declare(strict_types=1);
-use Symfony\Config\DoctrineConfig;
-
/**
- * This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+.
+ * This file enables native lazy objects on PHP 8.4+.
* We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version.
+ *
+ * TODO: Remove this file when we drop support for PHP < 8.4
*/
-return static function(DoctrineConfig $doctrine) {
- //On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
- if (PHP_VERSION_ID >= 80400) {
- $doctrine->orm()->enableNativeLazyObjects(true);
- }
-};
+// On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
+if (PHP_VERSION_ID >= 80400) {
+ return ['doctrine' => ['orm' => ['enable_native_lazy_objects' => true]]];
+}
+
+return [];
diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml
index 387d71ad..17f8f4c2 100644
--- a/config/packages/monolog.yaml
+++ b/config/packages/monolog.yaml
@@ -51,6 +51,7 @@ when@prod:
type: stream
channels: [deprecation]
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
+ level: "%env(DEPRECATION_LOG_LEVEL)%"
when@docker:
monolog:
@@ -75,3 +76,4 @@ when@docker:
type: stream
channels: [deprecation]
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
+ level: "%env(DEPRECATION_LOG_LEVEL)%"
diff --git a/config/parameters.yaml b/config/parameters.yaml
index b1aa5314..e654a9b5 100644
--- a/config/parameters.yaml
+++ b/config/parameters.yaml
@@ -53,6 +53,7 @@ parameters:
# Themes commented here by default, are not really usable, because of display problems. Enable them at your own risk!
partdb.available_themes:
- bootstrap
+ - brite
- cerulean
- cosmo
- cyborg
diff --git a/config/reference.php b/config/reference.php
index 49c16f65..461a3578 100644
--- a/config/reference.php
+++ b/config/reference.php
@@ -121,7 +121,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* }
* @psalm-type ServicesConfig = array{
* _defaults?: DefaultsType,
- * _instanceof?: InstanceofType,
+ * _instanceof?: array,
* ...
* }
* @psalm-type ExtensionType = array
@@ -653,7 +653,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* time_based_uuid_node?: scalar|Param|null,
* },
* html_sanitizer?: bool|array{ // HtmlSanitizer configuration
- * enabled?: bool|Param, // Default: false
+ * enabled?: bool|Param, // Default: true
* sanitizers?: array_deprecations.log` file. This option sets the minimum log
+ level a deprecation notice must have to be written there. Since deprecation notices are logged with level `info`,
+ the default value of `emergency` effectively disables this dedicated deprecation log. Set it to `debug` to enable it.
## Banner
diff --git a/docs/installation/installation_guide-debian.md b/docs/installation/installation_guide-debian.md
index e9f500b8..2915adbb 100644
--- a/docs/installation/installation_guide-debian.md
+++ b/docs/installation/installation_guide-debian.md
@@ -232,7 +232,7 @@ sudo ln -s /etc/apache2/sites-available/partdb.conf /etc/apache2/sites-enabled/p
Configure apache to show pretty URL paths for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`):
```bash
-sudo a2enmod rewrite
+sudo a2enmod rewrite headers
```
If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the
diff --git a/docs/installation/nginx.md b/docs/installation/nginx.md
index 981c18d5..1ae1d32c 100644
--- a/docs/installation/nginx.md
+++ b/docs/installation/nginx.md
@@ -36,6 +36,10 @@ server {
root /var/www/partdb/public;
location / {
+ # Headers are set here for static assets. PHP responses are served via the index.php location
+ # below and inherit neither of these headers, so Nelmio's PHP-side CSP is unaffected.
+ add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;" always;
+ add_header X-Content-Type-Options "nosniff" always;
try_files $uri /index.php$is_args$args;
}
@@ -57,10 +61,12 @@ server {
location ~* ^/media/.*\.(php[3-8]?|phar|phtml|pht|phps)$ {
return 403;
}
-
- # Set Content-Security-Policy for svg files, to block embedded javascript in there
+
+ # SVG files get a slightly different CSP because they can embed resources and must not be framed.
+ # This regex location takes precedence over location /, so headers must be repeated here.
location ~* \.svg$ {
- add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';";
+ add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;" always;
+ add_header X-Content-Type-Options "nosniff" always;
}
error_log /var/log/nginx/parts.error.log;
diff --git a/docs/usage/ai.md b/docs/usage/ai.md
index 3a1fb419..30f6c628 100644
--- a/docs/usage/ai.md
+++ b/docs/usage/ai.md
@@ -25,3 +25,10 @@ You need to supply an API key for OpenRouter to use it as an AI platform in Part
[LMStudio](https://lmstudio.ai/) is a local LLM hosting solution that allows you to run LLMs on your own hardware. You can use LMStudio to host your own LLM and connect it to Part-DB for AI features.
Currently only LMStudio without any authentication is supported. Supply your LMStudio instance URL (including the port) to use it as an AI platform in Part-DB.
+You have to set a model by hand, as suggestions currently do not work yet. Ensure the context length is suitable for your application.
+
+### Ollama
+
+[Ollama](https://ollama.com/) is another local LLM hosting solution that allows you to run LLMs on your own hardware. You can use Ollama to host your own LLM and connect it to Part-DB for AI features.
+Supply your Ollama instance URL (including the port) and an optional API key for authentication to use it as an AI platform in Part-DB. The model selector should give you suggestions about available models.
+Ensure the context length is suitable for your application.
diff --git a/package.json b/package.json
index f846f1d1..f906d814 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"bootstrap": "^5.1.3",
"core-js": "^3.38.0",
"intl-messageformat": "^10.5.11",
- "jquery": "^3.5.1",
+ "jquery": "^4.0.0",
"popper.js": "^1.14.7",
"regenerator-runtime": "^0.14.1",
"webpack": "^5.74.0",
@@ -38,20 +38,21 @@
"@algolia/autocomplete-theme-classic": "^1.17.0",
"@jbtronics/bs-treeview": "^1.0.1",
"@part-db/html5-qrcode": "^4.0.0",
- "@zxcvbn-ts/core": "^3.0.2",
- "@zxcvbn-ts/language-common": "^3.0.3",
- "@zxcvbn-ts/language-de": "^3.0.1",
- "@zxcvbn-ts/language-en": "^3.0.1",
- "@zxcvbn-ts/language-fr": "^3.0.1",
- "@zxcvbn-ts/language-ja": "^3.0.1",
+ "@zxcvbn-ts/core": "^4.1.2",
+ "@zxcvbn-ts/language-common": "^4.1.2",
+ "@zxcvbn-ts/language-de": "^4.1.1",
+ "@zxcvbn-ts/language-en": "^4.1.1",
+ "@zxcvbn-ts/language-fr": "^4.1.1",
+ "@zxcvbn-ts/language-it": "^4.1.1",
+ "@zxcvbn-ts/language-ja": "^4.1.1",
+ "@zxcvbn-ts/language-pl": "^4.1.1",
"attr-accept": "^2.2.5",
"barcode-detector": "^3.0.5",
- "bootbox": "^6.0.0",
"bootswatch": "^5.1.3",
"bs-custom-file-input": "^1.3.4",
"ckeditor5": "^48.0.0",
"clipboard": "^2.0.4",
- "compression-webpack-plugin": "^11.1.0",
+ "compression-webpack-plugin": "^12.0.0",
"datatables.net": "^2.0.0",
"datatables.net-bs5": "^2.0.0",
"datatables.net-buttons-bs5": "^3.0.0",
@@ -69,11 +70,12 @@
"marked-mangle": "^1.0.1",
"pdfmake": "^0.3.7",
"stimulus-use": "^0.52.0",
+ "sweetalert2": "^11.26.25",
"tom-select": "^2.1.0",
"ts-loader": "^9.2.6",
"typescript": "^6.0.2"
},
"resolutions": {
- "jquery": "^3.5.1"
+ "jquery": "^4.0.0"
}
}
diff --git a/public/.htaccess b/public/.htaccess
index a13baeee..0493298f 100644
--- a/public/.htaccess
+++ b/public/.htaccess
@@ -119,9 +119,14 @@ DirectoryIndex index.php
-# Set Content-Security-Policy for svg files (and compressed variants), to block embedded javascript in there
+ # Set a strict CSP for all static assets not handled by PHP.
+ # PHP responses already carry their own CSP via NelmioSecurityBundle, so setifempty leaves those untouched.
+ Header always setifempty Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;"
+ Header always setifempty X-Content-Type-Options "nosniff"
+
+ # SVG files get a slightly different CSP because they can embed resources and must not be framed.
- Header set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';"
+ Header always set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;"
-
\ No newline at end of file
+
diff --git a/public/kicad/footprints.txt b/public/kicad/footprints.txt
index 5f691a41..08380de1 100644
--- a/public/kicad/footprints.txt
+++ b/public/kicad/footprints.txt
@@ -1,4 +1,4 @@
-# Generated on Mon Jun 1 07:07:44 UTC 2026
+# Generated on Mon Jun 22 07:31:48 UTC 2026
# This file contains all footprints available in the offical KiCAD library
Audio_Module:Reverb_BTDR-1H
Audio_Module:Reverb_BTDR-1V
@@ -8293,6 +8293,7 @@ Converter_DCDC:Converter_DCDC_Hamamatsu_C11204-1_THT
Converter_DCDC:Converter_DCDC_MeanWell_NID30_THT
Converter_DCDC:Converter_DCDC_MeanWell_NID60_THT
Converter_DCDC:Converter_DCDC_MeanWell_NSD10_THT
+Converter_DCDC:Converter_DCDC_MeanWell_SMU02x-xxN_THT
Converter_DCDC:Converter_DCDC_Murata_CRE1xxxxxx3C_THT
Converter_DCDC:Converter_DCDC_Murata_CRE1xxxxxxDC_THT
Converter_DCDC:Converter_DCDC_Murata_CRE1xxxxxxSC_THT
@@ -12012,7 +12013,6 @@ Package_DFN_QFN:WDFN-8-1EP_4x3mm_P0.65mm_EP2.4x1.8mm_ThermalVias
Package_DFN_QFN:WDFN-8-1EP_6x5mm_P1.27mm_EP3.4x4mm
Package_DFN_QFN:WDFN-8-1EP_8x6mm_P1.27mm_EP6x4.8mm
Package_DFN_QFN:WDFN-8-1EP_8x6mm_P1.27mm_EP6x4.8mm_ThermalVias
-Package_DFN_QFN:WDFN-8_2x2mm_P0.5mm
Package_DFN_QFN:WFDFPN-8-1EP_3x2mm_P0.5mm_EP1.25x1.35mm
Package_DFN_QFN:WQFN-14-1EP_2.5x2.5mm_P0.5mm_EP1.45x1.45mm
Package_DFN_QFN:WQFN-14-1EP_2.5x2.5mm_P0.5mm_EP1.45x1.45mm_ThermalVias
@@ -12358,7 +12358,6 @@ Package_DirectFET:DirectFET_SQ
Package_DirectFET:DirectFET_ST
Package_LCC:Analog_LCC-8_5x5mm_P1.27mm
Package_LCC:MO047AD_PLCC-52_19.1x19.1mm_P1.27mm
-Package_LCC:PLCC-20
Package_LCC:PLCC-20_9.0x9.0mm_P1.27mm
Package_LCC:PLCC-20_SMD-Socket
Package_LCC:PLCC-20_THT-Socket
@@ -13032,8 +13031,6 @@ Package_SON:WSON-6-1EP_2x2mm_P0.65mm_EP1x1.6mm
Package_SON:WSON-6-1EP_2x2mm_P0.65mm_EP1x1.6mm_ThermalVias
Package_SON:WSON-6-1EP_3x3mm_P0.95mm
Package_SON:WSON-6_1.5x1.5mm_P0.5mm
-Package_SON:WSON-8-1EP_2x2mm_P0.5mm_EP0.9x1.6mm
-Package_SON:WSON-8-1EP_2x2mm_P0.5mm_EP0.9x1.6mm_ThermalVias
Package_SON:WSON-8-1EP_3x2.5mm_P0.5mm_EP1.2x1.5mm_PullBack
Package_SON:WSON-8-1EP_3x2.5mm_P0.5mm_EP1.2x1.5mm_PullBack_ThermalVias
Package_SON:WSON-8-1EP_3x3mm_P0.5mm_EP1.2x2mm
diff --git a/public/kicad/symbols.txt b/public/kicad/symbols.txt
index 7b4fde9c..a24a1785 100644
--- a/public/kicad/symbols.txt
+++ b/public/kicad/symbols.txt
@@ -1,4 +1,4 @@
-# Generated on Mon Jun 1 07:08:24 UTC 2026
+# Generated on Mon Jun 22 07:32:26 UTC 2026
# This file contains all symbols available in the offical KiCAD library
4xxx:14528
4xxx:14529
@@ -4954,6 +4954,7 @@ Converter_DCDC:RPMH15-1.5
Converter_DCDC:RPMH24-1.5
Converter_DCDC:RPMH3.3-1.5
Converter_DCDC:RPMH5.0-1.5
+Converter_DCDC:SMU02L-24N
Converter_DCDC:TBA1-0310
Converter_DCDC:TBA1-0311
Converter_DCDC:TBA1-0510
@@ -5300,21 +5301,39 @@ Converter_DCDC:TMR-4812
Converter_DCDC:TMR-4821
Converter_DCDC:TMR-4822
Converter_DCDC:TMR-4823
+Converter_DCDC:TMR10-1211WI
+Converter_DCDC:TMR10-1212WI
+Converter_DCDC:TMR10-1213WI
+Converter_DCDC:TMR10-1215WI
+Converter_DCDC:TMR10-1222WI
+Converter_DCDC:TMR10-1223WI
Converter_DCDC:TMR10-2410WIR
+Converter_DCDC:TMR10-2411WI
Converter_DCDC:TMR10-2411WIR
+Converter_DCDC:TMR10-2412WI
Converter_DCDC:TMR10-2412WIR
+Converter_DCDC:TMR10-2413WI
Converter_DCDC:TMR10-2413WIR
+Converter_DCDC:TMR10-2415WI
Converter_DCDC:TMR10-2415WIR
Converter_DCDC:TMR10-2421WIR
+Converter_DCDC:TMR10-2422WI
Converter_DCDC:TMR10-2422WIR
+Converter_DCDC:TMR10-2423WI
Converter_DCDC:TMR10-2423WIR
Converter_DCDC:TMR10-4810WIR
+Converter_DCDC:TMR10-4811WI
Converter_DCDC:TMR10-4811WIR
+Converter_DCDC:TMR10-4812WI
Converter_DCDC:TMR10-4812WIR
+Converter_DCDC:TMR10-4813WI
Converter_DCDC:TMR10-4813WIR
+Converter_DCDC:TMR10-4815WI
Converter_DCDC:TMR10-4815WIR
Converter_DCDC:TMR10-4821WIR
+Converter_DCDC:TMR10-4822WI
Converter_DCDC:TMR10-4822WIR
+Converter_DCDC:TMR10-4823WI
Converter_DCDC:TMR10-4823WIR
Converter_DCDC:TMR10-7210WIR
Converter_DCDC:TMR10-7211WIR
@@ -6124,6 +6143,7 @@ Device:SparkGap
Device:Speaker
Device:Speaker_Crystal
Device:Speaker_Ultrasound
+Device:Thermal_Jumper
Device:Thermistor
Device:Thermistor_NTC
Device:Thermistor_NTC_3Wire
@@ -14593,6 +14613,8 @@ MCU_Texas:LM4F111C4QR
MCU_Texas:LM4F111E5QR
MCU_Texas:LM4F111H5QR
MCU_Texas:MSP432E401Y
+MCU_Texas:MSPM0C110xSDDF
+MCU_Texas:MSPM0C110xSDSG
MCU_Texas:TM4C1230C3PM
MCU_Texas:TM4C1230D5PM
MCU_Texas:TM4C1230E6PM
@@ -15115,6 +15137,7 @@ Memory_Flash:AM29F400Bx-xxEx
Memory_Flash:AM29F400Bx-xxSx
Memory_Flash:AM29PDL128G
Memory_Flash:AT25DF041x-UxN-x
+Memory_Flash:AT25SF041B-SSHD-X
Memory_Flash:AT25SF081-SSHD-X
Memory_Flash:AT25SF081-SSHF-X
Memory_Flash:AT25SF081-XMHD-X
@@ -15670,6 +15693,7 @@ Power_Management:LM5069MM-1
Power_Management:LM5069MM-2
Power_Management:LM66100DCK
Power_Management:LM74700
+Power_Management:LM74701-Q1
Power_Management:LMG3410
Power_Management:LMG5200
Power_Management:LT1641-1
@@ -15771,6 +15795,7 @@ Power_Management:TPS22810DBV
Power_Management:TPS22810DRV
Power_Management:TPS22917DBV
Power_Management:TPS22917LDBV
+Power_Management:TPS22919DCK
Power_Management:TPS22929D
Power_Management:TPS22993
Power_Management:TPS2412D
@@ -16480,6 +16505,7 @@ RF_Module:DWM3000
RF_Module:E18-MS1-PCB
RF_Module:E73-2G4M04S-52810
RF_Module:E73-2G4M04S-52832
+RF_Module:ESP-01
RF_Module:ESP-07
RF_Module:ESP-12E
RF_Module:ESP-12F
@@ -16681,6 +16707,19 @@ Reference_Voltage:LM4040LP-4.1
Reference_Voltage:LM4040LP-5
Reference_Voltage:LM4040LP-8.2
Reference_Voltage:LM4041LP-ADJ
+Reference_Voltage:LM4050xEM3-2.1
+Reference_Voltage:LM4050xEM3-2.5
+Reference_Voltage:LM4050xEM3-3.0
+Reference_Voltage:LM4050xEM3-3.3
+Reference_Voltage:LM4050xEM3-4.1
+Reference_Voltage:LM4050xEM3-5.0
+Reference_Voltage:LM4050xEX3-2.1
+Reference_Voltage:LM4050xEX3-2.5
+Reference_Voltage:LM4050xEX3-3.3
+Reference_Voltage:LM4050xEX3-4.1
+Reference_Voltage:LM4050xEX3-5.0
+Reference_Voltage:LM4051xEM3-1.2
+Reference_Voltage:LM4051xEX3-1.2
Reference_Voltage:LM4125AIM5-2.5
Reference_Voltage:LM4125IM5-2.0
Reference_Voltage:LM4125IM5-2.5
@@ -16828,7 +16867,6 @@ Reference_Voltage:MCP1501-25xCH
Reference_Voltage:MCP1501-25xRW
Reference_Voltage:MCP1501-25xSN
Reference_Voltage:MCP1501-30xCH
-Reference_Voltage:MCP1501-30xRW
Reference_Voltage:MCP1501-30xSN
Reference_Voltage:MCP1501-33xCH
Reference_Voltage:MCP1501-33xRW
@@ -19261,6 +19299,7 @@ Regulator_Switching:LT1373HVCN8
Regulator_Switching:LT1373HVCS8
Regulator_Switching:LT1377CN8
Regulator_Switching:LT1377CS8
+Regulator_Switching:LT1931
Regulator_Switching:LT1945
Regulator_Switching:LT3430
Regulator_Switching:LT3430-1
@@ -20989,6 +21028,7 @@ Sensor_Temperature:MCP9501
Sensor_Temperature:MCP9502
Sensor_Temperature:MCP9503
Sensor_Temperature:MCP9504
+Sensor_Temperature:MCP96xx01x-x-MX
Sensor_Temperature:MCP9700Ax-ELT
Sensor_Temperature:MCP9700Ax-ETT
Sensor_Temperature:MCP9700Ax-HLT
@@ -22479,6 +22519,7 @@ Transistor_FET_Other:Q_NMOS_Depletion_GDS
Transistor_FET_Other:Q_NMOS_Depletion_GSD
Transistor_FET_Other:Q_NMOS_Depletion_SDG
Transistor_FET_Other:Q_NMOS_Depletion_SGD
+Transistor_FET_Other:SP010N70T8
Transistor_FET_Other:VNB35N07xx-E
Transistor_FET_Other:VNP10N07
Transistor_FET_Other:VNP35N07xx-E
diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php
index 01aeab11..7f48e661 100644
--- a/src/Controller/AttachmentFileController.php
+++ b/src/Controller/AttachmentFileController.php
@@ -93,6 +93,8 @@ class AttachmentFileController extends AbstractController
//Set header content disposition, so that the file will be downloaded
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $attachment->getFilename());
+ $this->setAttachmentCSPHeaders($response);
+
return $response;
}
@@ -112,6 +114,16 @@ class AttachmentFileController extends AbstractController
//Set header content disposition, so that the file will be downloaded
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $attachment->getFilename());
+ $this->setAttachmentCSPHeaders($response);
+
+ return $response;
+ }
+
+ private function setAttachmentCSPHeaders(Response $response): Response
+ {
+ //Set an CSP that disallow to run any scripts, styles or images from the attachment render page, as it is not used anywhere else for now and can be a security risk if used without proper precautions, so it should be opt-in
+ $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;");
+
return $response;
}
diff --git a/src/Controller/TreeController.php b/src/Controller/TreeController.php
index 71f8ba5c..b8c50d9b 100644
--- a/src/Controller/TreeController.php
+++ b/src/Controller/TreeController.php
@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Controller;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category;
@@ -55,7 +56,7 @@ class TreeController extends AbstractController
#[Route(path: '/category/{id}', name: 'tree_category')]
#[Route(path: '/categories', name: 'tree_category_root')]
- public function categoryTree(?Category $category = null): JsonResponse
+ public function categoryTree(#[MapEntity(id: 'id')] ?Category $category = null): JsonResponse
{
if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) {
$tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root');
@@ -68,7 +69,7 @@ class TreeController extends AbstractController
#[Route(path: '/footprint/{id}', name: 'tree_footprint')]
#[Route(path: '/footprints', name: 'tree_footprint_root')]
- public function footprintTree(?Footprint $footprint = null): JsonResponse
+ public function footprintTree(#[MapEntity(id: 'id')] ?Footprint $footprint = null): JsonResponse
{
if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) {
$tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root');
@@ -80,7 +81,7 @@ class TreeController extends AbstractController
#[Route(path: '/location/{id}', name: 'tree_location')]
#[Route(path: '/locations', name: 'tree_location_root')]
- public function locationTree(?StorageLocation $location = null): JsonResponse
+ public function locationTree(#[MapEntity(id: 'id')] ?StorageLocation $location = null): JsonResponse
{
if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) {
$tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root');
@@ -93,7 +94,7 @@ class TreeController extends AbstractController
#[Route(path: '/manufacturer/{id}', name: 'tree_manufacturer')]
#[Route(path: '/manufacturers', name: 'tree_manufacturer_root')]
- public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
+ public function manufacturerTree(#[MapEntity(id: 'id')] ?Manufacturer $manufacturer = null): JsonResponse
{
if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) {
$tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root');
@@ -106,7 +107,7 @@ class TreeController extends AbstractController
#[Route(path: '/supplier/{id}', name: 'tree_supplier')]
#[Route(path: '/suppliers', name: 'tree_supplier_root')]
- public function supplierTree(?Supplier $supplier = null): JsonResponse
+ public function supplierTree(#[MapEntity(id: 'id')] ?Supplier $supplier = null): JsonResponse
{
if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) {
$tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root');
@@ -119,7 +120,7 @@ class TreeController extends AbstractController
#[Route(path: '/device/{id}', name: 'tree_device')]
#[Route(path: '/devices', name: 'tree_device_root')]
- public function deviceTree(?Project $device = null): JsonResponse
+ public function deviceTree(#[MapEntity(id: 'id')] ?Project $device = null): JsonResponse
{
if ($this->isGranted('@projects.read')) {
$tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices');
diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php
index 16e6a7a7..6c4c905a 100644
--- a/src/DataTables/AttachmentDataTable.php
+++ b/src/DataTables/AttachmentDataTable.php
@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\DataTables;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\PrettyBoolColumn;
use App\DataTables\Column\RowClassColumn;
@@ -40,14 +41,19 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
-final class AttachmentDataTable implements DataTableTypeInterface
+final readonly class AttachmentDataTable implements DataTableTypeInterface
{
- public function __construct(private readonly TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator, private readonly AttachmentManager $attachmentHelper, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly ElementTypeNameGenerator $elementTypeNameGenerator)
+ public function __construct(private TranslatorInterface $translator, private EntityURLGenerator $entityURLGenerator, private AttachmentManager $attachmentHelper, private AttachmentURLGenerator $attachmentURLGenerator, private ElementTypeNameGenerator $elementTypeNameGenerator)
{
}
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$dataTable->add('dont_matter', RowClassColumn::class, [
'render' => function ($value, Attachment $context): string {
//Mark attachments yellow which have an internal file linked that doesn't exist
@@ -59,10 +65,10 @@ final class AttachmentDataTable implements DataTableTypeInterface
},
]);
- $dataTable->add('picture', TextColumn::class, [
+ $dataTable->add('picture', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => function ($value, Attachment $context): string {
+ 'data' => function (Attachment $context): string {
if ($context->isPicture()
&& $this->attachmentHelper->isInternalFileExisting($context)) {
@@ -95,65 +101,65 @@ final class AttachmentDataTable implements DataTableTypeInterface
'orderField' => 'NATSORT(attachment.name)',
]);
- $dataTable->add('attachment_type', TextColumn::class, [
+ $dataTable->add('attachment_type', HTMLColumn::class, [
'label' => 'attachment.table.type',
'field' => 'attachment_type.name',
'orderField' => 'NATSORT(attachment_type.name)',
- 'render' => fn($value, Attachment $context): string => sprintf(
+ 'data' => fn(Attachment $context, $value): string => sprintf(
'%s ',
$this->entityURLGenerator->editURL($context->getAttachmentType()),
htmlspecialchars((string) $value)
),
]);
- $dataTable->add('element', TextColumn::class, [
+ $dataTable->add('element', HTMLColumn::class, [
'label' => 'attachment.table.element',
//'propertyPath' => 'element.name',
- 'render' => fn($value, Attachment $context): string => sprintf(
+ 'data' => fn(Attachment $context): string => sprintf(
'%s ',
$this->entityURLGenerator->infoURL($context->getElement()),
$this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
),
]);
- $dataTable->add('internal_link', TextColumn::class, [
+ $dataTable->add('internal_link', HTMLColumn::class, [
'label' => 'attachment.table.internal_file',
'propertyPath' => 'filename',
'orderField' => 'NATSORT(attachment.original_filename)',
- 'render' => function ($value, Attachment $context) {
+ 'data' => function (Attachment $context, $value) {
if ($this->attachmentHelper->isInternalFileExisting($context)) {
return sprintf(
'%s ',
$this->entityURLGenerator->viewURL($context),
- htmlspecialchars($value)
+ htmlspecialchars((string) $value)
);
}
- return $value;
- }
+ return htmlspecialchars((string) $value);
+ },
]);
- $dataTable->add('external_link', TextColumn::class, [
+ $dataTable->add('external_link', HTMLColumn::class, [
'label' => 'attachment.table.external_link',
'propertyPath' => 'host',
'orderField' => 'attachment.external_path',
- 'render' => function ($value, Attachment $context) {
+ 'data' => function (Attachment $context, $value) {
if ($context->hasExternal()) {
return sprintf(
'%s ',
htmlspecialchars((string) $context->getExternalPath()),
htmlspecialchars((string) $context->getExternalPath()),
- htmlspecialchars($value),
+ htmlspecialchars((string) $value),
);
}
- return $value;
- }
+ return htmlspecialchars((string) $value);
+ },
]);
- $dataTable->add('filesize', TextColumn::class, [
+ $dataTable->add('filesize', HTMLColumn::class, [
'label' => $this->translator->trans('attachment.table.filesize'),
- 'render' => function ($value, Attachment $context) {
+ 'data' => function (Attachment $context) {
if (!$context->hasInternal()) {
return sprintf(
'
@@ -168,7 +174,7 @@ final class AttachmentDataTable implements DataTableTypeInterface
'
%s
',
- $this->attachmentHelper->getHumanFileSize($context)
+ htmlspecialchars($this->attachmentHelper->getHumanFileSize($context))
);
}
diff --git a/src/DataTables/Column/EntityColumn.php b/src/DataTables/Column/EntityColumn.php
index 54ae3fb3..b5d71a08 100644
--- a/src/DataTables/Column/EntityColumn.php
+++ b/src/DataTables/Column/EntityColumn.php
@@ -78,7 +78,7 @@ class EntityColumn extends AbstractColumn
);
}
- return sprintf('%s ', $value);
+ return sprintf('%s ', htmlspecialchars($value));
}
return '';
diff --git a/assets/css/components/bootbox_extensions.css b/src/DataTables/Column/HTMLColumn.php
similarity index 58%
rename from assets/css/components/bootbox_extensions.css
rename to src/DataTables/Column/HTMLColumn.php
index 42bbd78d..a1220dd3 100644
--- a/assets/css/components/bootbox_extensions.css
+++ b/src/DataTables/Column/HTMLColumn.php
@@ -1,7 +1,11 @@
+.
*/
+namespace App\DataTables\Column;
-.modal-body > .bootbox-close-button {
- position: absolute;
- top: 0;
- right: 0;
- padding: 0.5rem 0.75rem;
- z-index: 1;
-}
-.modal .bootbox-close-button {
- font-weight: 100;
-}
+use Omines\DataTablesBundle\Column\TextColumn;
-button.bootbox-close-button {
- padding: 0;
- background-color: transparent;
- border: 0;
- -webkit-appearance: none;
+/**
+ * A TextColumn whose value is always treated as raw HTML and therefore never passed through htmlspecialchars().
+ * The value returned by the 'data' option must already contain properly escaped/sanitized HTML, as it is output as-is.
+ */
+class HTMLColumn extends TextColumn
+{
+ public function isRaw(): bool
+ {
+ return true;
+ }
}
-
-.bootbox-close-button {
- /* float: right; */
- font-size: 1.40625rem;
- font-weight: 600;
- line-height: 1;
- color: #000;
- text-shadow: none;
- opacity: .5;
-}
\ No newline at end of file
diff --git a/src/DataTables/Column/IconLinkColumn.php b/src/DataTables/Column/IconLinkColumn.php
index 6704cb4a..47b35d82 100644
--- a/src/DataTables/Column/IconLinkColumn.php
+++ b/src/DataTables/Column/IconLinkColumn.php
@@ -87,9 +87,9 @@ class IconLinkColumn extends AbstractColumn
return sprintf(
' ',
$disabled ? 'disabled' : '',
- $href,
- $title,
- $icon
+ htmlspecialchars($href),
+ htmlspecialchars($title ?? ''),
+ htmlspecialchars($icon ?? '')
);
}
diff --git a/src/DataTables/ErrorDataTable.php b/src/DataTables/ErrorDataTable.php
index 833ea934..a16b453e 100644
--- a/src/DataTables/ErrorDataTable.php
+++ b/src/DataTables/ErrorDataTable.php
@@ -22,9 +22,9 @@ declare(strict_types=1);
*/
namespace App\DataTables;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\RowClassColumn;
use Omines\DataTablesBundle\Adapter\ArrayAdapter;
-use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableFactory;
use Omines\DataTablesBundle\DataTableTypeInterface;
@@ -32,7 +32,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
-class ErrorDataTable implements DataTableTypeInterface
+final readonly class ErrorDataTable implements DataTableTypeInterface
{
public function configureOptions(OptionsResolver $optionsResolver): void
{
@@ -49,6 +49,11 @@ class ErrorDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$optionsResolver = new OptionsResolver();
$this->configureOptions($optionsResolver);
$options = $optionsResolver->resolve($options);
@@ -58,9 +63,9 @@ class ErrorDataTable implements DataTableTypeInterface
'render' => fn($value, $context): string => 'table-warning',
])
- ->add('error', TextColumn::class, [
+ ->add('error', HTMLColumn::class, [
'label' => 'error_table.error',
- 'render' => fn($value, $context): string => ' ' . $value,
+ 'data' => fn($context, $value): string => ' ' . htmlspecialchars((string) $value),
])
;
diff --git a/src/DataTables/Helpers/PartDataTableHelper.php b/src/DataTables/Helpers/PartDataTableHelper.php
index 54094ff1..2f40dbd2 100644
--- a/src/DataTables/Helpers/PartDataTableHelper.php
+++ b/src/DataTables/Helpers/PartDataTableHelper.php
@@ -62,7 +62,7 @@ class PartDataTableHelper
}
if ($context->getBuiltProject() instanceof Project) {
$icon = sprintf(' ',
- $this->translator->trans('part.info.projectBuildPart.hint').': '.$context->getBuiltProject()->getName());
+ $this->translator->trans('part.info.projectBuildPart.hint').': '.htmlspecialchars($context->getBuiltProject()->getName()));
}
diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php
index 2c37767b..5c4ca88b 100644
--- a/src/DataTables/LogDataTable.php
+++ b/src/DataTables/LogDataTable.php
@@ -25,6 +25,7 @@ namespace App\DataTables;
use App\DataTables\Column\EnumColumn;
use App\Entity\LogSystem\LogTargetType;
use Symfony\Bundle\SecurityBundle\Security;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\LogEntryExtraColumn;
@@ -59,7 +60,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
-class LogDataTable implements DataTableTypeInterface
+final readonly class LogDataTable implements DataTableTypeInterface
{
protected LogEntryRepository $logRepo;
@@ -95,6 +96,11 @@ class LogDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$options = $resolver->resolve($options);
@@ -104,10 +110,10 @@ class LogDataTable implements DataTableTypeInterface
'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()),
]);
- $dataTable->add('symbol', TextColumn::class, [
+ $dataTable->add('symbol', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => fn($value, AbstractLogEntry $context): string => sprintf(
+ 'data' => fn(AbstractLogEntry $context): string => sprintf(
' ',
$this->logLevelHelper->logLevelToIconClass($context->getLevelString()),
$context->getLevelString()
@@ -128,10 +134,10 @@ class LogDataTable implements DataTableTypeInterface
)
]);
- $dataTable->add('type', TextColumn::class, [
+ $dataTable->add('type', HTMLColumn::class, [
'label' => 'log.type',
'propertyPath' => 'type',
- 'render' => function (string $value, AbstractLogEntry $context) {
+ 'data' => function (AbstractLogEntry $context, string $value) {
$text = $this->translator->trans('log.type.'.$value);
if ($context instanceof PartStockChangedLogEntry) {
@@ -149,20 +155,20 @@ class LogDataTable implements DataTableTypeInterface
'label' => 'log.level',
'visible' => 'system_log' === $options['mode'],
'propertyPath' => 'levelString',
- 'render' => fn(string $value, AbstractLogEntry $context) => $this->translator->trans('log.level.'.$value),
+ 'data' => fn(AbstractLogEntry $context, string $value) => $this->translator->trans('log.level.'.$value),
]);
- $dataTable->add('user', TextColumn::class, [
+ $dataTable->add('user', HTMLColumn::class, [
'label' => 'log.user',
'orderField' => 'NATSORT(user.name)',
- 'render' => function ($value, AbstractLogEntry $context): string {
+ 'data' => function (AbstractLogEntry $context): string {
$user = $context->getUser();
//If user was deleted, show the info from the username field
if (!$user instanceof User) {
if ($context->isCLIEntry()) {
return sprintf('%s [%s]',
- htmlentities((string) $context->getCLIUsername()),
+ htmlspecialchars((string) $context->getCLIUsername()),
$this->translator->trans('log.cli_user')
);
}
@@ -170,7 +176,7 @@ class LogDataTable implements DataTableTypeInterface
//Else we just deal with a deleted user
return sprintf(
'@%s [%s]',
- htmlentities($context->getUsername()),
+ htmlspecialchars($context->getUsername()),
$this->translator->trans('log.target_deleted'),
);
}
@@ -182,7 +188,7 @@ class LogDataTable implements DataTableTypeInterface
$img_url,
$this->userAvatarHelper->getAvatarMdURL($user),
$this->urlGenerator->generate('user_info', ['id' => $user->getID()]),
- htmlentities($user->getFullName(true))
+ htmlspecialchars($user->getFullName(true))
);
},
]);
@@ -194,7 +200,7 @@ class LogDataTable implements DataTableTypeInterface
'render' => function (LogTargetType $value, AbstractLogEntry $context) {
$class = $value->toClass();
if (null !== $class) {
- return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
+ return $this->elementTypeNameGenerator->typeLabel($class);
}
return '';
@@ -216,9 +222,9 @@ class LogDataTable implements DataTableTypeInterface
'icon' => 'fas fa-fw fa-eye',
'href' => function ($value, AbstractLogEntry $context) {
if (
+ $context instanceof CollectionElementDeleted ||
($context instanceof TimeTravelInterface
&& $context->hasOldDataInformation())
- || $context instanceof CollectionElementDeleted
) {
try {
$target = $this->logRepo->getTargetElement($context);
diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php
index bcf64056..5912a20e 100644
--- a/src/DataTables/PartsDataTable.php
+++ b/src/DataTables/PartsDataTable.php
@@ -25,6 +25,7 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
@@ -58,7 +59,7 @@ use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
-final class PartsDataTable implements DataTableTypeInterface
+final readonly class PartsDataTable implements DataTableTypeInterface
{
public const LENGTH_MENU = [[10, 25, 50, 100, 250, 500, -1], [10, 25, 50, 100, 250, 500, "All"]];
@@ -94,6 +95,11 @@ final class PartsDataTable implements DataTableTypeInterface
* When adding columns here, add them also to PartTableColumns enum, to make them configurable in the settings!
*************************************************************************************************************/
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$this->csh
//Color the table rows depending on the review and favorite status
->add('row_color', RowClassColumn::class, [
@@ -109,23 +115,23 @@ final class PartsDataTable implements DataTableTypeInterface
},
], visibility_configurable: false)
->add('select', SelectColumn::class, visibility_configurable: false)
- ->add('picture', TextColumn::class, [
+ ->add('picture', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderPicture($context),
], visibility_configurable: false)
- ->add('name', TextColumn::class, [
+ ->add('name', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.name'),
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderName($context),
'orderField' => 'NATSORT(part.name)'
])
->add('si_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.si_value'),
- 'render' => function ($value, Part $context): string {
+ 'data' => function (Part $context): string {
$siValue = SiValueSort::sqliteSiValue($context->getName());
if ($siValue !== null) {
//Output it as scientific number with a big E
- return htmlspecialchars(sprintf('%G', $siValue));
+ return sprintf('%G', $siValue);
}
return '';
},
@@ -156,38 +162,38 @@ final class PartsDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('part.table.manufacturer'),
'orderField' => 'NATSORT(_manufacturer.name)'
])
- ->add('storelocation', TextColumn::class, [
+ ->add('storelocation', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'),
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation
'orderField' => 'NATSORT(MIN(_storelocations.name))',
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
], alias: 'storage_location')
- ->add('amount', TextColumn::class, [
+ ->add('amount', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.amount'),
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderAmount($context),
'orderField' => 'amountSum'
])
->add('minamount', TextColumn::class, [
'label' => $this->translator->trans('part.table.minamount'),
- 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format(
+ 'data' => fn(Part $context, $value): string => $this->amountFormatter->format(
$value,
$context->getPartUnit()
- )),
+ ),
])
->add('partUnit', TextColumn::class, [
'label' => $this->translator->trans('part.table.partUnit'),
'orderField' => 'NATSORT(_partUnit.name)',
- 'render' => function ($value, Part $context): string {
+ 'data' => function (Part $context): string {
$partUnit = $context->getPartUnit();
if ($partUnit === null) {
return '';
}
- $tmp = htmlspecialchars($partUnit->getName());
+ $tmp = $partUnit->getName();
if ($partUnit->getUnit()) {
- $tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')';
+ $tmp .= ' (' . $partUnit->getUnit() . ')';
}
return $tmp;
}
@@ -195,14 +201,14 @@ final class PartsDataTable implements DataTableTypeInterface
->add('partCustomState', TextColumn::class, [
'label' => $this->translator->trans('part.table.partCustomState'),
'orderField' => 'NATSORT(_partCustomState.name)',
- 'render' => function($value, Part $context): string {
+ 'data' => function(Part $context): string {
$partCustomState = $context->getPartCustomState();
if ($partCustomState === null) {
return '';
}
- return htmlspecialchars($partCustomState->getName());
+ return $partCustomState->getName();
}
])
->add('addedDate', LocaleDateTimeColumn::class, [
@@ -248,25 +254,25 @@ final class PartsDataTable implements DataTableTypeInterface
])
->add('eda_reference', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_reference'),
- 'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getReferencePrefix() ?? ''),
+ 'data' => static fn(Part $context) => $context->getEdaInfo()->getReferencePrefix() ?? '',
'orderField' => 'NATSORT(part.eda_info.reference_prefix)'
])
->add('eda_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_value'),
- 'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getValue() ?? ''),
+ 'data' => static fn(Part $context) => $context->getEdaInfo()->getValue() ?? '',
'orderField' => 'NATSORT(part.eda_info.value)'
])
- ->add('eda_status', TextColumn::class, [
+ ->add('eda_status', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.eda_status'),
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderEdaStatus($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderEdaStatus($context),
'className' => 'text-center',
]);
//Add a column to list the projects where the part is used, when the user has the permission to see the projects
if ($this->security->isGranted('read', Project::class)) {
- $this->csh->add('projects', TextColumn::class, [
+ $this->csh->add('projects', HTMLColumn::class, [
'label' => $this->translator->trans('project.labelp'),
- 'render' => function ($value, Part $context): string {
+ 'data' => function (Part $context): string {
//Only show the first 5 projects names
$projects = $context->getProjects();
$tmp = "";
@@ -286,7 +292,7 @@ final class PartsDataTable implements DataTableTypeInterface
}
return $tmp;
- }
+ },
]);
}
diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php
index b5beeca0..f65f0df7 100644
--- a/src/DataTables/ProjectBomEntriesDataTable.php
+++ b/src/DataTables/ProjectBomEntriesDataTable.php
@@ -25,6 +25,7 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
use App\DataTables\Helpers\PartDataTableHelper;
@@ -48,7 +49,7 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
-class ProjectBomEntriesDataTable implements DataTableTypeInterface
+final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterface
{
public function __construct(
protected EntityURLGenerator $entityURLGenerator,
@@ -63,17 +64,22 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$dataTable
//->add('select', SelectColumn::class)
- ->add('picture', TextColumn::class, [
+ ->add('picture', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) {
return '';
}
return $this->partDataTableHelper->renderPicture($context->getPart());
- }
+ },
])
->add('id', TextColumn::class, [
@@ -85,27 +91,27 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('project.bom.quantity'),
'className' => 'text-center',
'orderField' => 'bom_entry.quantity',
- 'render' => function ($value, ProjectBOMEntry $context): float|string {
+ 'data' => function (ProjectBOMEntry $context): float|string {
//If we have a non-part entry, only show the rounded quantity
if (!$context->getPart() instanceof Part) {
return round($context->getQuantity());
}
//Otherwise use the unit of the part to format the quantity
- return htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit()));
+ return $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) {
+ 'data' => function (ProjectBOMEntry $context) {
return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : '';
},
])
- ->add('name', TextColumn::class, [
+ ->add('name', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.name'),
'orderField' => 'NATSORT(part.name)',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) {
return htmlspecialchars((string) $context->getName());
}
@@ -123,11 +129,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('part.table.ipn'),
'orderField' => 'NATSORT(part.ipn)',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
- if($context->getPart() instanceof Part) {
- return $context->getPart()->getIpn();
- }
- }
+ 'data' => fn (ProjectBOMEntry $context) => $context->getPart()?->getIpn()
])
->add('description', MarkdownColumn::class, [
'label' => $this->translator->trans('part.table.description'),
@@ -172,9 +174,9 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
},
])
- ->add('mountnames', TextColumn::class, [
+ ->add('mountnames', HTMLColumn::class, [
'label' => 'project.bom.mountnames',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
$html = '';
foreach (explode(',', $context->getMountnames()) as $mountname) {
@@ -184,34 +186,34 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
},
])
- ->add('instockAmount', TextColumn::class, [
+ ->add('instockAmount', HTMLColumn::class, [
'label' => 'project.bom.instockAmount',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderAmount($context->getPart());
}
return '';
- }
+ },
])
- ->add('storelocation', TextColumn::class, [
+ ->add('storelocation', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'),
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation
'orderField' => 'NATSORT(MIN(_storelocations.name))',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderStorageLocations($context->getPart());
}
return '';
- }
+ },
])
->add('price', TextColumn::class, [
'label' => 'project.bom.price',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true);
},
@@ -219,7 +221,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
->add('ext_price', TextColumn::class, [
'label' => 'project.bom.ext_price',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format(
$price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity()))
diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php
index aa6108c9..f721f986 100644
--- a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php
+++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php
@@ -27,6 +27,7 @@ use App\Doctrine\Functions\SiValueSort;
use App\Exceptions\InvalidRegexException;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
+use Pdo\Sqlite;
/**
* This middleware is used to add the regexp operator to the SQLite platform.
@@ -44,17 +45,30 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
if ($params['driver'] === 'pdo_sqlite') {
$native_connection = $connection->getNativeConnection();
- //Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
if($native_connection instanceof \PDO) {
- $native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC);
- $native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
- $native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
- //Create a new collation for natural sorting
- $native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
+ //Use the new PDO::createFunction and PDO::createCollation methods if available (PHP 8.4+)
+ if (is_a($native_connection, Sqlite::class)) { #TODO: Remove this check when PHP 8.4 is the minimum requirement
+ $native_connection->createFunction('REGEXP', self::regexp(...), 2, Sqlite::DETERMINISTIC);
+ $native_connection->createFunction('FIELD', self::field(...), -1, Sqlite::DETERMINISTIC);
+ $native_connection->createFunction('FIELD2', self::field2(...), 2, Sqlite::DETERMINISTIC);
- //Create a function for SI prefix value sorting
- $native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC);
+ //Create a new collation for natural sorting
+ $native_connection->createCollation('NATURAL_CMP', strnatcmp(...));
+
+ //Create a function for SI prefix value sorting
+ $native_connection->createFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, Sqlite::DETERMINISTIC);
+ } else {
+ $native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC);
+ $native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
+ $native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
+
+ //Create a new collation for natural sorting
+ $native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
+
+ //Create a function for SI prefix value sorting
+ $native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC);
+ }
}
}
@@ -118,4 +132,4 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
return $index + 1;
}
-}
\ No newline at end of file
+}
diff --git a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php
index d05b6b9c..b8e70ff2 100644
--- a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php
+++ b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php
@@ -35,8 +35,10 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware
{
//Only set this on MySQL connections, as other databases don't support this parameter
if($params['driver'] === 'pdo_mysql') {
- //1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value
- $params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
+ //PDO::MYSQL_ATTR_INIT_COMMAND is deprecated since PHP 8.5 in favor of Pdo\Mysql::ATTR_INIT_COMMAND,
+ //but the Pdo\Mysql class only exists since PHP 8.4. Both constants have the same value (1002).
+ $initCommandAttr = class_exists(\Pdo\Mysql::class) ? \Pdo\Mysql::ATTR_INIT_COMMAND : \PDO::MYSQL_ATTR_INIT_COMMAND;
+ $params['driverOptions'][$initCommandAttr] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
}
return parent::connect($params);
diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php
index 7a314ffe..03bb8031 100644
--- a/src/Entity/Attachments/AttachmentType.php
+++ b/src/Entity/Attachments/AttachmentType.php
@@ -65,21 +65,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/attachment_types/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: AttachmentType::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of an attachment type.'),
+ security: 'is_granted("@attachment_types.read")'
+ ),
],
normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['attachment_type:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/attachment_types/{id}/children.{_format}',
- operations: [
- new GetCollection(openapi: new Operation(summary: 'Retrieves the children elements of an attachment type.'),
- security: 'is_granted("@attachment_types.read")')
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: AttachmentType::class)
- ],
- normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php
index 22f8a3e4..80fcc43c 100644
--- a/src/Entity/Parts/Category.php
+++ b/src/Entity/Parts/Category.php
@@ -68,23 +68,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/categories/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: Category::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a category.'),
+ security: 'is_granted("@categories.read")'
+ ),
],
normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['category:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/categories/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a category.'),
- security: 'is_granted("@categories.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Category::class)
- ],
- normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php
index 3d8be686..2027a310 100644
--- a/src/Entity/Parts/Footprint.php
+++ b/src/Entity/Parts/Footprint.php
@@ -67,23 +67,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/footprints/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: Footprint::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a footprint.'),
+ security: 'is_granted("@footprints.read")'
+ ),
],
normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['footprint:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/footprints/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a footprint.'),
- security: 'is_granted("@footprints.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Footprint::class)
- ],
- normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php
index 810aef0c..8f6874b9 100644
--- a/src/Entity/Parts/InfoProviderReference.php
+++ b/src/Entity/Parts/InfoProviderReference.php
@@ -28,9 +28,11 @@ use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Embeddable;
use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
- * This class represents a reference to a info provider inside a part.
+ * This class represents a reference to an info provider inside a part.
* @see \App\Tests\Entity\Parts\InfoProviderReferenceTest
*/
#[Embeddable]
@@ -157,4 +159,44 @@ class InfoProviderReference
$ref->last_updated = new \DateTimeImmutable();
return $ref;
}
+
+ /**
+ * Creates a reference to an info provider based on the given parameters.
+ * @param string|null $provider_key
+ * @param string|null $provider_id
+ * @param string|null $provider_url
+ * @param \DateTimeImmutable|null $last_updated
+ * @return self
+ */
+ public static function create(?string $provider_key, ?string $provider_id, ?string $provider_url, ?\DateTimeImmutable $last_updated): self
+ {
+ $ref = new InfoProviderReference();
+ $ref->provider_key = $provider_key;
+ $ref->provider_id = $provider_id;
+ $ref->provider_url = $provider_url;
+ $ref->last_updated = $last_updated;
+ return $ref;
+ }
+
+ #[Assert\Callback()]
+ public function validate(ExecutionContextInterface $context, mixed $payload): void
+ {
+ if ($this->provider_key === null && $this->provider_id !== null) {
+ $context->buildViolation('info_providers.validation.provider_id_without_key')
+ ->atPath('provider_key')
+ ->addViolation();
+ }
+
+ if ($this->provider_key === null && $this->provider_url !== null) {
+ $context->buildViolation('info_providers.validation.provider_url_without_key')
+ ->atPath('provider_url')
+ ->addViolation();
+ }
+
+ if ($this->provider_key !== null && $this->provider_id === null) {
+ $context->buildViolation('info_providers.validation.provider_key_without_id')
+ ->atPath('provider_id')
+ ->addViolation();
+ }
+ }
}
diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php
index 0edf8232..76526c31 100644
--- a/src/Entity/Parts/Manufacturer.php
+++ b/src/Entity/Parts/Manufacturer.php
@@ -66,23 +66,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/manufacturers/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a manufacturer.'),
+ security: 'is_granted("@manufacturers.read")'
+ ),
],
normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/manufacturers/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a manufacturer.'),
- security: 'is_granted("@manufacturers.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class)
- ],
- normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php
index 6dd0b9f2..e775b65b 100644
--- a/src/Entity/Parts/MeasurementUnit.php
+++ b/src/Entity/Parts/MeasurementUnit.php
@@ -71,23 +71,16 @@ use Symfony\Component\Validator\Constraints\Length;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/measurement_units/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: MeasurementUnit::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a MeasurementUnit.'),
+ security: 'is_granted("@measurement_units.read")'
+ ),
],
normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['measurement_unit:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/measurement_units/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a MeasurementUnit.'),
- security: 'is_granted("@measurement_units.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: MeasurementUnit::class)
- ],
- normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "unit"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php
index 065469b5..9fa41f93 100644
--- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php
+++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php
@@ -75,6 +75,7 @@ trait AdvancedPropertyTrait
*/
#[ORM\Embedded(class: InfoProviderReference::class, columnPrefix: 'provider_reference_')]
#[Groups(['full', 'part:read'])]
+ #[Assert\Valid()]
protected InfoProviderReference $providerReference;
/**
diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php
index 6c455ae5..9571da6e 100644
--- a/src/Entity/Parts/StorageLocation.php
+++ b/src/Entity/Parts/StorageLocation.php
@@ -67,23 +67,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/storage_locations/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: StorageLocation::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a storage location.'),
+ security: 'is_granted("@storelocations.read")'
+ ),
],
normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['location:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/storage_locations/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a storage location.'),
- security: 'is_granted("@storelocations.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class)
- ],
- normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php
index 2c004e9e..75cf62d1 100644
--- a/src/Entity/Parts/Supplier.php
+++ b/src/Entity/Parts/Supplier.php
@@ -71,21 +71,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/suppliers/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: Supplier::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a supplier.'),
+ security: 'is_granted("@manufacturers.read")'
+ ),
],
normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/suppliers/{id}/children.{_format}',
- operations: [new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a supplier.'),
- security: 'is_granted("@manufacturers.read")'
- )],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Supplier::class)
- ],
- normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php
index 4a811aa0..507ec810 100644
--- a/src/Entity/PriceInformations/Currency.php
+++ b/src/Entity/PriceInformations/Currency.php
@@ -71,23 +71,16 @@ use Symfony\Component\Validator\Constraints as Assert;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/currencies/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: Currency::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a currency.'),
+ security: 'is_granted("@currencies.read")'
+ ),
],
normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['currency:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/currencies/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a currency.'),
- security: 'is_granted("@currencies.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Currency::class)
- ],
- normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "iso_code"])]
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php
index 56428e3a..8b5c9f7b 100644
--- a/src/Entity/PriceInformations/Orderdetail.php
+++ b/src/Entity/PriceInformations/Orderdetail.php
@@ -71,23 +71,17 @@ use Symfony\Component\Validator\Constraints\Length;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/parts/{id}/orderdetails.{_format}',
+ uriVariables: ['id' => new Link(toProperty: 'part', fromClass: Part::class)],
+ normalizationContext: ['groups' => ['orderdetail:read', 'pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
+ openapi: new Operation(summary: 'Retrieves the orderdetails of a part.'),
+ security: 'is_granted("@parts.read")'
+ ),
],
normalizationContext: ['groups' => ['orderdetail:read', 'orderdetail:read:standalone', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['orderdetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/parts/{id}/orderdetails.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the orderdetails of a part.'),
- security: 'is_granted("@parts.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(toProperty: 'part', fromClass: Part::class)
- ],
- normalizationContext: ['groups' => ['orderdetail:read', 'pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["supplierpartnr", "supplier_product_url"])]
diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php
index a103d694..34e77051 100644
--- a/src/Entity/ProjectSystem/Project.php
+++ b/src/Entity/ProjectSystem/Project.php
@@ -66,23 +66,16 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
new Post(securityPostDenormalize: 'is_granted("create", object)'),
new Patch(security: 'is_granted("edit", object)'),
new Delete(security: 'is_granted("delete", object)'),
+ new GetCollection(
+ uriTemplate: '/projects/{id}/children.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'children', fromClass: Project::class)],
+ openapi: new Operation(summary: 'Retrieves the children elements of a project.'),
+ security: 'is_granted("@projects.read")'
+ ),
],
normalizationContext: ['groups' => ['project:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['project:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/projects/{id}/children.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the children elements of a project.'),
- security: 'is_granted("@projects.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'children', fromClass: Project::class)
- ],
- normalizationContext: ['groups' => ['project:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
@@ -117,7 +110,7 @@ class Project extends AbstractStructuralDBElement
/**
* @var string|null The current status of the project
*/
- #[Assert\Choice(['draft', 'planning', 'in_production', 'finished', 'archived'])]
+ #[Assert\Choice(choices: ['draft', 'planning', 'in_production', 'finished', 'archived'])]
#[Groups(['extended', 'full', 'project:read', 'project:write', 'import'])]
#[ORM\Column(type: Types::STRING, length: 64, nullable: true)]
protected ?string $status = null;
diff --git a/src/Entity/ProjectSystem/ProjectBOMEntry.php b/src/Entity/ProjectSystem/ProjectBOMEntry.php
index 2a7862ec..c016d741 100644
--- a/src/Entity/ProjectSystem/ProjectBOMEntry.php
+++ b/src/Entity/ProjectSystem/ProjectBOMEntry.php
@@ -63,23 +63,16 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
new Post(uriTemplate: '/project_bom_entries.{_format}', securityPostDenormalize: 'is_granted("create", object)',),
new Patch(uriTemplate: '/project_bom_entries/{id}.{_format}', security: 'is_granted("edit", object)',),
new Delete(uriTemplate: '/project_bom_entries/{id}.{_format}', security: 'is_granted("delete", object)',),
+ new GetCollection(
+ uriTemplate: '/projects/{id}/bom.{_format}',
+ uriVariables: ['id' => new Link(fromProperty: 'bom_entries', fromClass: Project::class)],
+ openapi: new Operation(summary: 'Retrieves the BOM entries of the given project.'),
+ security: 'is_granted("@projects.read")'
+ ),
],
normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
denormalizationContext: ['groups' => ['bom_entry:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
)]
-#[ApiResource(
- uriTemplate: '/projects/{id}/bom.{_format}',
- operations: [
- new GetCollection(
- openapi: new Operation(summary: 'Retrieves the BOM entries of the given project.'),
- security: 'is_granted("@projects.read")'
- )
- ],
- uriVariables: [
- 'id' => new Link(fromProperty: 'bom_entries', fromClass: Project::class)
- ],
- normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
-)]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", 'mountnames'])]
#[ApiFilter(RangeFilter::class, properties: ['quantity'])]
diff --git a/src/Form/Extension/DateTimeModelTimezoneExtension.php b/src/Form/Extension/DateTimeModelTimezoneExtension.php
new file mode 100644
index 00000000..3c4818ea
--- /dev/null
+++ b/src/Form/Extension/DateTimeModelTimezoneExtension.php
@@ -0,0 +1,79 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace App\Form\Extension;
+
+use Symfony\Component\Form\AbstractTypeExtension;
+use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormEvent;
+use Symfony\Component\Form\FormEvents;
+
+/**
+ * Catches timezone mismatches between a DateTimeInterface model value and the effective
+ * model_timezone configured on the field.
+ *
+ * Doctrine's UTCDateTimeImmutableType always returns UTC DateTimeImmutable objects, so any
+ * date/datetime field that omits `model_timezone: 'UTC'` will silently corrupt stored values
+ * (the transformer treats the UTC instant as if it were in the user's local timezone).
+ * This extension throws a \LogicException early so the mistake is caught at development time.
+ */
+class DateTimeModelTimezoneExtension extends AbstractTypeExtension
+{
+ public static function getExtendedTypes(): iterable
+ {
+ return [DateTimeType::class, DateType::class];
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options): void
+ {
+ $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void {
+ $data = $event->getData();
+
+ if (!$data instanceof \DateTimeInterface) {
+ return;
+ }
+
+ // Resolve the effective model timezone: explicit option or the PHP default set at build time.
+ // This mirrors what BaseDateTimeTransformer does in its constructor.
+ $modelTimezone = $options['model_timezone'] ?? date_default_timezone_get();
+
+ $dataOffset = $data->getTimezone()->getOffset($data);
+ $modelOffset = (new \DateTimeZone($modelTimezone))->getOffset($data);
+
+ if ($dataOffset !== $modelOffset) {
+ throw new \LogicException(sprintf(
+ 'Form field "%s" received a %s with timezone "%s" (UTC offset %+d s), '
+ . 'but the effective model_timezone is "%s" (UTC offset %+d s). '
+ . 'Set the "model_timezone" option to match the timezone of your data source.',
+ $event->getForm()->getName(),
+ get_debug_type($data),
+ $data->getTimezone()->getName(),
+ $dataOffset,
+ $modelTimezone,
+ $modelOffset
+ ));
+ }
+ });
+ }
+}
diff --git a/src/Form/InfoProviderSystem/InfoProviderReferenceType.php b/src/Form/InfoProviderSystem/InfoProviderReferenceType.php
new file mode 100644
index 00000000..73fc8fe3
--- /dev/null
+++ b/src/Form/InfoProviderSystem/InfoProviderReferenceType.php
@@ -0,0 +1,113 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Form\InfoProviderSystem;
+
+use App\Entity\Parts\InfoProviderReference;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\DataMapperInterface;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\Extension\Core\Type\UrlType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class InfoProviderReferenceType extends AbstractType implements DataMapperInterface
+{
+ public function buildForm(FormBuilderInterface $builder, array $options): void
+ {
+ $builder
+ ->setDataMapper($this)
+ ->add('provider_key', ProviderSelectType::class, [
+ 'label' => 'info_providers.provider_key',
+ 'input' => 'string',
+ 'multiple' => false,
+ 'required' => false,
+ 'only_active' => false,
+ ])
+ ->add('provider_id', TextType::class, [
+ 'label' => 'info_providers.provider_id',
+ 'required' => false,
+ ])
+ ->add('provider_url', UrlType::class, [
+ 'label' => 'info_providers.provider_url',
+ 'required' => false,
+ ])
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'data_class' => InfoProviderReference::class,
+ ]);
+ }
+
+
+ public function mapDataToForms(mixed $viewData, \Traversable $forms): void
+ {
+ if ($viewData === null) {
+ return;
+ }
+
+ if (!$viewData instanceof InfoProviderReference) {
+ return;
+ }
+
+ /** @var FormInterface[] $forms */
+ $forms = iterator_to_array($forms);
+
+ $forms['provider_key']->setData($viewData->getProviderKey());
+ $forms['provider_id']->setData($viewData->getProviderId());
+ $forms['provider_url']->setData($viewData->getProviderUrl());
+ }
+
+ public function mapFormsToData(\Traversable $forms, mixed &$viewData): void
+ {
+ /** @var FormInterface[] $forms */
+ $forms = iterator_to_array($forms);
+
+ $providerKey = $forms['provider_key']->getData();
+ $providerId = $forms['provider_id']->getData();
+ $providerUrl = $forms['provider_url']->getData();
+
+ if ($viewData === null) {
+ $viewData = InfoProviderReference::noProvider();
+ }
+
+ if (!$viewData instanceof InfoProviderReference) {
+ return;
+ }
+
+ $oldDate = $viewData->getLastUpdated();
+
+ //If all fields are empty, we set the view data to a new instance without provider information
+ if ($providerKey === null && $providerId === null && $providerUrl === null) {
+ $viewData = InfoProviderReference::noProvider();
+ return;
+ }
+
+ $viewData = InfoProviderReference::create($providerKey, $providerId, $providerUrl, $oldDate);
+
+ }
+}
diff --git a/src/Form/InfoProviderSystem/ProviderSelectType.php b/src/Form/InfoProviderSystem/ProviderSelectType.php
index bad3edaa..e59dc85f 100644
--- a/src/Form/InfoProviderSystem/ProviderSelectType.php
+++ b/src/Form/InfoProviderSystem/ProviderSelectType.php
@@ -31,12 +31,12 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Translation\StaticMessage;
+use Symfony\Component\Translation\TranslatableMessage;
class ProviderSelectType extends AbstractType
{
public function __construct(private readonly ProviderRegistry $providerRegistry)
{
-
}
public function getParent(): string
@@ -46,17 +46,22 @@ class ProviderSelectType extends AbstractType
public function configureOptions(OptionsResolver $resolver): void
{
- $providers = $this->providerRegistry->getActiveProviders();
-
$resolver->setDefault('input', 'object');
$resolver->setAllowedTypes('input', 'string');
//Either the form returns the provider objects or their keys
$resolver->setAllowedValues('input', ['object', 'string']);
$resolver->setDefault('multiple', true);
- $resolver->setDefault('choices', function (Options $options) use ($providers) {
+ //Only show active providers in the list, or also inactive ones
+ $resolver->setDefault('only_active', true);
+ $resolver->setAllowedTypes('only_active', 'bool');
+
+
+ $resolver->setDefault('choices', function (Options $options) {
+ $providers = $options['only_active'] ? $this->providerRegistry->getActiveProviders() : $this->providerRegistry->getProviders();
+
if ('object' === $options['input']) {
- return $this->providerRegistry->getActiveProviders();
+ return $providers;
}
$tmp = [];
@@ -69,20 +74,35 @@ class ProviderSelectType extends AbstractType
});
//The choice_label and choice_value only needs to be set if we want the objects
- $resolver->setDefault('choice_label', function (Options $options){
+ $resolver->setDefault('choice_label', function (Options $options) {
if ('object' === $options['input']) {
- return ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => new StaticMessage($choice?->getProviderInfo()['name']));
+ return ChoiceList::label($this, static fn(?InfoProviderInterface $choice
+ ) => new StaticMessage($choice?->getProviderInfo()['name']));
}
- return static fn ($choice, $key, $value) => new StaticMessage($key);
+ return static fn($choice, $key, $value) => new StaticMessage($key);
});
$resolver->setDefault('choice_value', function (Options $options) {
if ('object' === $options['input']) {
- return ChoiceList::value($this, static fn(?InfoProviderInterface $choice) => $choice?->getProviderKey());
+ return ChoiceList::value($this,
+ static fn(?InfoProviderInterface $choice) => $choice?->getProviderKey());
}
return null;
});
+ $resolver->setDefault('group_by', function (Options $options) {
+ //Do not show groups when only active providers are shown, because then all providers are active and the group would be useless
+ if ($options['only_active']) {
+ return null;
+ }
+
+ return function ($choice, $key, string $value) {
+ if ($this->providerRegistry->getProviderByKey($value)->isActive()) {
+ return new TranslatableMessage('info_providers.providers_list.active');
+ }
+ return new TranslatableMessage('info_providers.providers_list.disabled');
+ };
+ });
}
}
diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php
index a31f2469..afef8fdb 100644
--- a/src/Form/Part/PartBaseType.php
+++ b/src/Form/Part/PartBaseType.php
@@ -33,6 +33,7 @@ use App\Entity\Parts\Part;
use App\Entity\Parts\PartCustomState;
use App\Entity\PriceInformations\Orderdetail;
use App\Form\AttachmentFormType;
+use App\Form\InfoProviderSystem\InfoProviderReferenceType;
use App\Form\ParameterType;
use App\Form\Part\EDA\EDAPartInfoType;
use App\Form\Type\MasterPictureAttachmentType;
@@ -225,6 +226,10 @@ class PartBaseType extends AbstractType
'empty_data' => null,
'label' => 'part.gtin',
])
+ ->add('providerReference', InfoProviderReferenceType::class, [
+ 'label' => false,
+ 'required' => false,
+ ])
;
//Comment section
diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php
index fc330bb1..ef49c57e 100644
--- a/src/Form/Part/PartLotType.php
+++ b/src/Form/Part/PartLotType.php
@@ -115,8 +115,10 @@ class PartLotType extends AbstractType
$builder->add('last_stocktake_at', DateTimeType::class, [
'label' => 'part_lot.edit.last_stocktake_at',
'widget' => 'single_text',
+ 'model_timezone' => 'UTC', // The database stores the datetime in UTC, so we need to set the model timezone to UTC
'disabled' => !$this->security->isGranted('@parts_stock.stocktake'),
'required' => false,
+ 'with_seconds' => true,
]);
}
diff --git a/src/Form/Type/AttachmentTypeType.php b/src/Form/Type/AttachmentTypeType.php
index 099ed282..95b5a254 100644
--- a/src/Form/Type/AttachmentTypeType.php
+++ b/src/Form/Type/AttachmentTypeType.php
@@ -38,7 +38,7 @@ class AttachmentTypeType extends AbstractType
return StructuralEntityType::class;
}
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
$resolver->define('attachment_filter_class')->allowedTypes('null', 'string')->default(null);
diff --git a/src/Helpers/Projects/ProjectBuildRequest.php b/src/Helpers/Projects/ProjectBuildRequest.php
index 430d37b5..d2dfe139 100644
--- a/src/Helpers/Projects/ProjectBuildRequest.php
+++ b/src/Helpers/Projects/ProjectBuildRequest.php
@@ -84,8 +84,10 @@ final class ProjectBuildRequest
$remaining_amount = $this->getNeededAmountForBOMEntry($bom_entry);
foreach($this->getPartLotsForBOMEntry($bom_entry) as $lot) {
//If the lot has instock use it for the build
- $this->withdraw_amounts[$lot->getID()] = min($remaining_amount, $lot->getAmount());
- $remaining_amount -= max(0, $this->withdraw_amounts[$lot->getID()]);
+ $id = $lot->getID() ?? throw new \RuntimeException("Part lot needs to have an ID!");
+
+ $this->withdraw_amounts[$id] = min($remaining_amount, $lot->getAmount());
+ $remaining_amount -= max(0, $this->withdraw_amounts[$id]);
}
}
}
@@ -176,6 +178,10 @@ final class ProjectBuildRequest
{
$lot_id = $lot instanceof PartLot ? $lot->getID() : $lot;
+ if ($lot_id === null) {
+ throw new \InvalidArgumentException('The given lot must have an ID!');
+ }
+
if (! array_key_exists($lot_id, $this->withdraw_amounts)) {
throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!');
}
@@ -192,10 +198,12 @@ final class ProjectBuildRequest
{
if ($lot instanceof PartLot) {
$lot_id = $lot->getID();
- } elseif (is_int($lot)) {
- $lot_id = $lot;
} else {
- throw new \InvalidArgumentException('The given lot must be an instance of PartLot or an ID of a PartLot!');
+ $lot_id = $lot;
+ }
+
+ if ($lot_id === null) {
+ throw new \InvalidArgumentException('The given lot must have an ID!');
}
$this->withdraw_amounts[$lot_id] = $amount;
@@ -296,7 +304,7 @@ final class ProjectBuildRequest
* @param bool $dont_check_quantity
* @return $this
*/
- public function setDontCheckQuantity(bool $dont_check_quantity): ProjectBuildRequest
+ public function setDontCheckQuantity(bool $dont_check_quantity): self
{
$this->dont_check_quantity = $dont_check_quantity;
return $this;
diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php
index f737d91d..6e13aff7 100644
--- a/src/Repository/DBElementRepository.php
+++ b/src/Repository/DBElementRepository.php
@@ -158,7 +158,6 @@ class DBElementRepository extends EntityRepository
{
$reflection = new ReflectionClass($element::class);
$property = $reflection->getProperty($field);
- $property->setAccessible(true);
$property->setValue($element, $new_value);
}
}
diff --git a/src/Services/AI/AIPlatforms.php b/src/Services/AI/AIPlatforms.php
index 2f4d6317..318819a2 100644
--- a/src/Services/AI/AIPlatforms.php
+++ b/src/Services/AI/AIPlatforms.php
@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Services\AI;
use App\Settings\AISettings\LMStudioSettings;
+use App\Settings\AISettings\OllamaSettings;
use App\Settings\AISettings\OpenRouterSettings;
use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -32,6 +33,7 @@ enum AIPlatforms: string implements TranslatableInterface
{
case OPENROUTER = 'openrouter';
case LMSTUDIO = 'lmstudio';
+ case OLLAMA = 'ollama';
/**
* Returns the name attribute of the service tag for this platform, which is used to register the platform in the AIPlatformRegistry
@@ -52,6 +54,7 @@ enum AIPlatforms: string implements TranslatableInterface
return match ($this) {
self::LMSTUDIO => LMStudioSettings::class,
self::OPENROUTER => OpenRouterSettings::class,
+ self::OLLAMA => OllamaSettings::class,
};
}
diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php
index 2e40f1f5..1b90091f 100644
--- a/src/Services/Attachments/AttachmentSubmitHandler.php
+++ b/src/Services/Attachments/AttachmentSubmitHandler.php
@@ -543,8 +543,10 @@ class AttachmentSubmitHandler
return $attachment;
}
+ $guessed_mime_type = $this->mimeTypes->guessMimeType($path);
+
//Check if the file is an SVG
- if ($attachment->getExtension() === "svg") {
+ if ($guessed_mime_type === "image/svg+xml" || $attachment->getExtension() === "svg") {
$this->SVGSanitizer->sanitizeFile($path);
}
diff --git a/src/Services/ImportExportSystem/BOMValidationService.php b/src/Services/ImportExportSystem/BOMValidationService.php
index 74f81fe3..9f4cf5b8 100644
--- a/src/Services/ImportExportSystem/BOMValidationService.php
+++ b/src/Services/ImportExportSystem/BOMValidationService.php
@@ -29,13 +29,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Service for validating BOM import data with comprehensive validation rules
- * and user-friendly error messages.
+ * and user-friendly error messages. The results are not HTML safe, and must be escaped before display!
*/
-class BOMValidationService
+readonly class BOMValidationService
{
public function __construct(
- private readonly EntityManagerInterface $entityManager,
- private readonly TranslatorInterface $translator
+ private EntityManagerInterface $entityManager,
+ private TranslatorInterface $translator
) {
}
@@ -473,4 +473,4 @@ class BOMValidationService
: 0,
];
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php
index 08b1c301..cbfb4ee0 100644
--- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php
+++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php
@@ -233,7 +233,6 @@ trait PKImportHelperTrait
$reflectionClass = new \ReflectionClass($entity);
$property = $reflectionClass->getProperty('addedDate');
- $property->setAccessible(true);
$property->setValue($entity, $date);
}
diff --git a/src/Services/InfoProviderSystem/DTOJsonSchemaConverter.php b/src/Services/InfoProviderSystem/DTOJsonSchemaConverter.php
index a61e7465..3c2cbf64 100644
--- a/src/Services/InfoProviderSystem/DTOJsonSchemaConverter.php
+++ b/src/Services/InfoProviderSystem/DTOJsonSchemaConverter.php
@@ -42,7 +42,7 @@ final class DTOJsonSchemaConverter
public function getJSONSchema(): array
{
return [
- 'name' => 'clock',
+ 'name' => 'part_detail',
'strict' => true,
'schema' => [
'type' => 'object',
diff --git a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php
index 6539e69b..be91041e 100644
--- a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php
+++ b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php
@@ -282,6 +282,10 @@ final class AIWebProvider implements InfoProviderInterface
try {
$aiPlatform = $this->AIPlatformRegistry->getPlatform($this->settings->platform ?? throw new \RuntimeException('No AI platform selected') );
+ // AI inference can take much longer than PHP's default max_execution_time (typically 30s).
+ // The HTTP client timeout already enforces the configured limit; disable PHP's constraint here.
+ set_time_limit(0);
+
//'openai/gpt-5-mini'
$result = $aiPlatform->invoke($this->settings->model ?? throw new \RuntimeException('No model selected'), $input, [
'response_format' => [
diff --git a/src/Settings/AISettings/AISettings.php b/src/Settings/AISettings/AISettings.php
index 732eb597..659577b6 100644
--- a/src/Settings/AISettings/AISettings.php
+++ b/src/Settings/AISettings/AISettings.php
@@ -35,9 +35,14 @@ class AISettings
{
use SettingsTrait;
+ public const TIMEOUT_LIMIT = 600;
+
#[EmbeddedSettings]
public ?OpenRouterSettings $openRouter = null;
#[EmbeddedSettings]
public ?LMStudioSettings $lmstudio = null;
+
+ #[EmbeddedSettings]
+ public ?OllamaSettings $ollama = null;
}
diff --git a/src/Settings/AISettings/LMStudioSettings.php b/src/Settings/AISettings/LMStudioSettings.php
index 2bdad06e..d92ce97e 100644
--- a/src/Settings/AISettings/LMStudioSettings.php
+++ b/src/Settings/AISettings/LMStudioSettings.php
@@ -23,16 +23,17 @@ declare(strict_types=1);
namespace App\Settings\AISettings;
-use App\Form\Type\APIKeyType;
use App\Services\AI\AIPlatformSettingsInterface;
use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Translation\StaticMessage;
use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
#[Settings(name: 'ai_lmstudio', label: new TM("settings.ai.lmstudio"))]
#[SettingsIcon("fa-robot")]
@@ -46,6 +47,14 @@ class LMStudioSettings implements AIPlatformSettingsInterface
envVar: "AI_LMSTUDIO_HOSTURL", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $hostURL = null;
+ #[SettingsParameter(label: new TM("settings.ai.timeout"),
+ description: new TM("settings.ai.timeout.help"),
+ formType: NumberType::class,
+ formOptions: ["scale" => 0, "attr" => ["min" => 1]],
+ )]
+ #[Assert\Range(min: 1, max: AISettings::TIMEOUT_LIMIT)]
+ public int $timeout = 180;
+
public function isAIPlatformEnabled(): bool
{
return $this->hostURL !== null && $this->hostURL !== "";
diff --git a/src/Settings/AISettings/OllamaSettings.php b/src/Settings/AISettings/OllamaSettings.php
new file mode 100644
index 00000000..7ca0e5a0
--- /dev/null
+++ b/src/Settings/AISettings/OllamaSettings.php
@@ -0,0 +1,68 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Settings\AISettings;
+
+use App\Form\Type\APIKeyType;
+use App\Services\AI\AIPlatformSettingsInterface;
+use App\Settings\SettingsIcon;
+use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
+use Jbtronics\SettingsBundle\Settings\Settings;
+use Jbtronics\SettingsBundle\Settings\SettingsParameter;
+use Jbtronics\SettingsBundle\Settings\SettingsTrait;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
+use Symfony\Component\Form\Extension\Core\Type\UrlType;
+use Symfony\Component\Translation\StaticMessage;
+use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[Settings(name: 'ai_ollama', label: new TM("settings.ai.ollama"))]
+#[SettingsIcon("fa-robot")]
+class OllamaSettings implements AIPlatformSettingsInterface
+{
+ use SettingsTrait;
+
+ #[SettingsParameter(label: new TM("settings.ai.ollama.endpoint"),
+ formType: UrlType::class,
+ formOptions: ["attr" => ["placeholder" => new StaticMessage("http://localhost:11434")]],
+ envVar: "AI_OLLAMA_ENDPOINT", envVarMode: EnvVarMode::OVERWRITE)]
+ public ?string $endpoint = null;
+
+ #[SettingsParameter(label: new TM("settings.ai.ollama.apiKey"),
+ formType: APIKeyType::class,
+ envVar: "AI_OLLAMA_API_KEY", envVarMode: EnvVarMode::OVERWRITE)]
+ public ?string $apiKey = null;
+
+ #[SettingsParameter(label: new TM("settings.ai.timeout"),
+ description: new TM("settings.ai.timeout.help"),
+ formType: NumberType::class,
+ formOptions: ["scale" => 0, "attr" => ["min" => 1]]
+ )]
+ #[Assert\Range(min: 1, max: AISettings::TIMEOUT_LIMIT)]
+ public int $timeout = 180;
+
+ public function isAIPlatformEnabled(): bool
+ {
+ return $this->endpoint !== null && $this->endpoint !== "";
+ }
+}
diff --git a/src/Settings/AISettings/OpenRouterSettings.php b/src/Settings/AISettings/OpenRouterSettings.php
index e083513a..16665554 100644
--- a/src/Settings/AISettings/OpenRouterSettings.php
+++ b/src/Settings/AISettings/OpenRouterSettings.php
@@ -30,7 +30,9 @@ use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
#[Settings(name: 'ai_openrouter', label: new TM("settings.ai.openrouter"), description: "settings.ai.openrouter.help")]
#[SettingsIcon("fa-robot")]
@@ -43,6 +45,14 @@ class OpenRouterSettings implements AIPlatformSettingsInterface
formOptions: ["help_html" => true], envVar: "AI_OPENROUTER_KEY", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $apiKey = null;
+ #[SettingsParameter(label: new TM("settings.ai.timeout"),
+ description: new TM("settings.ai.timeout.help"),
+ formType: NumberType::class,
+ formOptions: ["scale" => 0, "attr" => ["min" => 1]],
+ envVar: "int:AI_OPENROUTER_TIMEOUT", envVarMode: EnvVarMode::OVERWRITE)]
+ #[Assert\Range(min: 1, max: AISettings::TIMEOUT_LIMIT)]
+ public int $timeout = 90;
+
public function isAIPlatformEnabled(): bool
{
return $this->apiKey !== null && $this->apiKey !== "";
diff --git a/symfony.lock b/symfony.lock
index f8f88675..94af6e6a 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -411,6 +411,18 @@
"config/packages/ai_lm_studio_platform.yaml"
]
},
+ "symfony/ai-ollama-platform": {
+ "version": "0.10",
+ "recipe": {
+ "repo": "github.com/symfony/recipes",
+ "branch": "main",
+ "version": "0.1",
+ "ref": "2f0ac0a8bc59c4e46b47a962a3ad7fe8104457d6"
+ },
+ "files": [
+ "config/packages/ai_ollama_platform.yaml"
+ ]
+ },
"symfony/ai-open-router-platform": {
"version": "0.8",
"recipe": {
diff --git a/templates/log_system/details/_extra_collection_element_deleted.html.twig b/templates/log_system/details/_extra_collection_element_deleted.html.twig
index 221fae95..1c2ce2f3 100644
--- a/templates/log_system/details/_extra_collection_element_deleted.html.twig
+++ b/templates/log_system/details/_extra_collection_element_deleted.html.twig
@@ -4,7 +4,7 @@
{% trans %}log.collection_deleted.deleted{% endtrans %} :
- {{ entity_type_label(entry.deletedElementClass) }} #{{ entry.deletedElementID }}
+ {{ type_label(entry.deletedElementClass) }} #{{ entry.deletedElementID }}
{% if entry.oldName is not empty %}
({{ entry.oldName }})
{% endif %}
@@ -12,4 +12,4 @@
{% trans %}log.collection_deleted.on_collection{% endtrans %} :
{{ log_helper.translate_field(entry.collectionName) }}
-
\ No newline at end of file
+
diff --git a/templates/log_system/details/log_details.html.twig b/templates/log_system/details/log_details.html.twig
index aff127f4..2255dd97 100644
--- a/templates/log_system/details/log_details.html.twig
+++ b/templates/log_system/details/log_details.html.twig
@@ -58,7 +58,7 @@
{% trans %}log.target{% endtrans %}
- {{ target_html|raw }}
+ {{ target_html|sanitize_html }}
@@ -111,7 +111,7 @@
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\CollectionElementDeleted') %}
{% include "log_system/details/_extra_collection_element_deleted.html.twig" %}
{% else %}
- {{ extra_html | raw }}
+ {{ extra_html | sanitize_html }}
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/parts/edit/_advanced.html.twig b/templates/parts/edit/_advanced.html.twig
index 30479d11..f18dba58 100644
--- a/templates/parts/edit/_advanced.html.twig
+++ b/templates/parts/edit/_advanced.html.twig
@@ -15,3 +15,24 @@
{{ form_row(form.partUnit) }}
{{ form_row(form.partCustomState) }}
{{ form_row(form.gtin) }}
+
+
diff --git a/templates/parts/info/_extended_infos.html.twig b/templates/parts/info/_extended_infos.html.twig
index 9cb4e4e5..acc197e3 100644
--- a/templates/parts/info/_extended_infos.html.twig
+++ b/templates/parts/info/_extended_infos.html.twig
@@ -76,7 +76,7 @@
{% endif %}
{{ info_provider_label(part.providerReference.providerKey)|default(part.providerReference.providerKey) }} : {{ part.providerReference.providerId }}
- ({{ part.providerReference.lastUpdated | format_datetime() }})
+ ({{ part.providerReference.lastUpdated ? (part.providerReference.lastUpdated | format_datetime()) : ("part.info_provider_reference.updated_never"|trans) }})
{% if part.providerReference.providerUrl %}
{% endif %}
diff --git a/templates/parts/info/_picture.html.twig b/templates/parts/info/_picture.html.twig
index e6aa74b3..db6c59ca 100644
--- a/templates/parts/info/_picture.html.twig
+++ b/templates/parts/info/_picture.html.twig
@@ -19,7 +19,7 @@
{{ pic.name }}
{% if pic.filename %}({{ pic.filename }}) {% endif %}
-
{{ entity_type_label(pic.element) }}
+
{{ type_label(pic.element) }}
{% endif %}
@@ -41,4 +41,4 @@
{% else %}
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/templates/parts/info/show_part_info.html.twig b/templates/parts/info/show_part_info.html.twig
index 96b5e209..b36ab047 100644
--- a/templates/parts/info/show_part_info.html.twig
+++ b/templates/parts/info/show_part_info.html.twig
@@ -22,7 +22,7 @@
({{ timeTravel | format_datetime('short') }})
{% endif %}
{% if part.projectBuildPart %}
- ({{ entity_type_label(part.builtProject) }} : {{ part.builtProject.name }} )
+ ({{ type_label(part.builtProject) }} : {{ part.builtProject.name }} )
{% endif %}
diff --git a/templates/projects/_bom_validation_results.html.twig b/templates/projects/_bom_validation_results.html.twig
index 68f1b827..cb92e7bc 100644
--- a/templates/projects/_bom_validation_results.html.twig
+++ b/templates/projects/_bom_validation_results.html.twig
@@ -68,7 +68,7 @@
{% trans %}project.bom_import.validation.errors.description{% endtrans %}
{% for error in validation_result.errors %}
- {{ error|raw }}
+ {{ error }}
{% endfor %}
@@ -80,7 +80,7 @@
{% trans %}project.bom_import.validation.warnings.description{% endtrans %}
{% for warning in validation_result.warnings %}
- {{ warning|raw }}
+ {{ warning }}
{% endfor %}
@@ -91,7 +91,7 @@
{% trans %}project.bom_import.validation.info.title{% endtrans %}
{% for info in validation_result.info %}
- {{ info|raw }}
+ {{ info }}
{% endfor %}
@@ -139,21 +139,21 @@
{% if line_result.errors is not empty %}
{% for error in line_result.errors %}
-
{{ error|raw }}
+
{{ error }}
{% endfor %}
{% endif %}
{% if line_result.warnings is not empty %}
{% for warning in line_result.warnings %}
-
{{ warning|raw }}
+
{{ warning }}
{% endfor %}
{% endif %}
{% if line_result.info is not empty %}
{% for info in line_result.info %}
-
{{ info|raw }}
+
{{ info }}
{% endfor %}
{% endif %}
diff --git a/templates/projects/import_bom_map_fields.html.twig b/templates/projects/import_bom_map_fields.html.twig
index ee1e23ef..b272be64 100644
--- a/templates/projects/import_bom_map_fields.html.twig
+++ b/templates/projects/import_bom_map_fields.html.twig
@@ -132,8 +132,8 @@