From 0bfbbc961d4ee3939807ebdf2a753c3dbd3d5263 Mon Sep 17 00:00:00 2001 From: Sebastian Almberg <83243306+Sebbeben@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:18:21 +0100 Subject: [PATCH] Fix update confirmation dialog not blocking form submission The previous implementation used inline onsubmit handlers with return confirmVersionChange(...), which could fail silently if any JavaScript error occurred on the page, causing the form to submit without confirmation. Fixes: - Use event.preventDefault() FIRST to ensure form never submits by default - Use DOMContentLoaded event listeners instead of inline handlers - Properly escape translation strings using json_encode filter - Wrap in IIFE with 'use strict' for better error handling - Use data-attributes to identify forms and pass isDowngrade state Fix DOMContentLoaded race condition in update form handlers The event listener was not attaching if DOMContentLoaded had already fired by the time the script executed. Now checks document.readyState and attaches handlers immediately if DOM is already ready. Added console.log statements to help debug form handler attachment. Use Stimulus controller for update confirmation dialogs The inline script was blocked by Content Security Policy (CSP). Stimulus controllers are bundled with webpack and properly allowed by CSP. - Create update_confirm_controller.js Stimulus controller - Remove inline script from template - Pass translation strings via data-* attributes --- .../controllers/update_confirm_controller.js | 81 +++++++++++++++++++ .../admin/update_manager/index.html.twig | 48 +++-------- 2 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 assets/controllers/update_confirm_controller.js diff --git a/assets/controllers/update_confirm_controller.js b/assets/controllers/update_confirm_controller.js new file mode 100644 index 00000000..c30a433e --- /dev/null +++ b/assets/controllers/update_confirm_controller.js @@ -0,0 +1,81 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Controller } from '@hotwired/stimulus'; + +/** + * Stimulus controller for update/downgrade confirmation dialogs. + * Intercepts form submission and shows a confirmation dialog before proceeding. + */ +export default class extends Controller { + static values = { + isDowngrade: { type: Boolean, default: false }, + targetVersion: { type: String, default: '' }, + confirmUpdate: { type: String, default: 'Are you sure you want to update Part-DB?' }, + confirmDowngrade: { type: String, default: 'Are you sure you want to downgrade Part-DB?' }, + downgradeWarning: { type: String, default: 'WARNING: This version does not include the Update Manager.' }, + minUpdateManagerVersion: { type: String, default: '2.6.0' }, + }; + + connect() { + this.element.addEventListener('submit', this.handleSubmit.bind(this)); + } + + handleSubmit(event) { + // Always prevent default first + event.preventDefault(); + + const targetClean = this.targetVersionValue.replace(/^v/, ''); + let message; + + if (this.isDowngradeValue) { + // Check if downgrading to a version without Update Manager + if (this.compareVersions(targetClean, this.minUpdateManagerVersionValue) < 0) { + message = this.confirmDowngradeValue + '\n\n⚠️ ' + this.downgradeWarningValue; + } else { + message = this.confirmDowngradeValue; + } + } else { + message = this.confirmUpdateValue; + } + + // Only submit if user confirms + if (confirm(message)) { + // Remove the event listener to prevent infinite loop, then submit + this.element.removeEventListener('submit', this.handleSubmit.bind(this)); + this.element.submit(); + } + } + + /** + * Compare two version strings (e.g., "2.5.0" vs "2.6.0") + * Returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2 + */ + compareVersions(v1, v2) { + const parts1 = v1.split('.').map(Number); + const parts2 = v2.split('.').map(Number); + for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { + const p1 = parts1[i] || 0; + const p2 = parts2[i] || 0; + if (p1 < p2) return -1; + if (p1 > p2) return 1; + } + return 0; + } +} diff --git a/templates/admin/update_manager/index.html.twig b/templates/admin/update_manager/index.html.twig index d9dccbcf..1ab6c89e 100644 --- a/templates/admin/update_manager/index.html.twig +++ b/templates/admin/update_manager/index.html.twig @@ -133,7 +133,13 @@ {% if status.update_available and status.can_auto_update and validation.valid %} -
+ @@ -237,7 +243,12 @@ {% if release.version != status.current_version and status.can_auto_update and validation.valid %} + data-controller="update-confirm" + data-update-confirm-is-downgrade-value="{{ release.version < status.current_version ? 'true' : 'false' }}" + data-update-confirm-target-version-value="{{ release.tag }}" + data-update-confirm-confirm-update-value="{{ 'update_manager.confirm_update'|trans }}" + data-update-confirm-confirm-downgrade-value="{{ 'update_manager.confirm_downgrade'|trans }}" + data-update-confirm-downgrade-warning-value="{{ 'update_manager.downgrade_removes_update_manager'|trans }}"> @@ -371,37 +382,4 @@ {% endif %} - - {% endblock %}