feat: add catalogue check by creating a small service runner

This commit is contained in:
2025-11-20 13:04:42 +01:00
parent 9ab4fcbe81
commit 74c8eacbf2
4 changed files with 364 additions and 3 deletions

View File

@@ -24,5 +24,8 @@ define('MAIL_TO', getenv('MAIL_TO') ?: 'semesterapparate@ph-freiburg.de');
define('BASE_PATH', __DIR__);
define('STATIC_PATH', '/static');
// Signature validation API (optional Python service)
define('SIGNATURE_API_URL', getenv('SIGNATURE_API_URL') ?: 'http://localhost:8001');
// Email regex pattern
define('EMAIL_REGEX', '/^[^@\s]+@[^@\s]+\.[^@\s]+$/');

View File

@@ -34,7 +34,7 @@
</p>
</div>
<form method="post" action="submit_elsa.php" class="request-form">
<form method="post" action="submit_elsa.php" class="request-form" data-api-url="<?php echo SIGNATURE_API_URL; ?>">
<h2>Allgemeine Informationen</h2>
<div class="grid-form">
<div class="form-field">
@@ -137,6 +137,16 @@
};
let sectionCounter = 0;
// Track signature validations
let signatureTracking = {};
let validationTimers = {};
// Get API URL from form data attribute
const getApiUrl = () => {
const form = document.querySelector('.request-form');
return form ? form.dataset.apiUrl : 'http://localhost:8001';
};
// Theme toggle functionality
(function() {
@@ -271,6 +281,7 @@
'<td><input type="number" name="monografie_pages_from[]" data-section="' + sectionId + '" required min="1"></td>' +
'<td><input type="number" name="monografie_pages_to[]" data-section="' + sectionId + '" required min="1"></td>' +
'<td><button type="button" class="btn-icon" onclick="removeRow(\'' + rowId + '\')" title="Zeile entfernen"><span class="mdi mdi-delete"></span></button></td>';
attachSignatureListeners(row);
} else if (type === 'zeitschriftenartikel') {
row.innerHTML = '<td><input type="text" name="zeitschrift_author[]" data-section="' + sectionId + '" required></td>' +
'<td><input type="text" name="zeitschrift_year[]" data-section="' + sectionId + '" required></td>' +
@@ -291,6 +302,7 @@
'<td><input type="text" name="herausgeber_signature[]" data-section="' + sectionId + '" placeholder="Optional"></td>' +
'<td><input type="number" name="herausgeber_pages_from[]" data-section="' + sectionId + '" required min="1"></td>' +
'<td><input type="number" name="herausgeber_pages_to[]" data-section="' + sectionId + '" required min="1"></td>' +
attachSignatureListeners(row);
'<td><button type="button" class="btn-icon" onclick="removeRow(\'' + rowId + '\')" title="Zeile entfernen"><span class="mdi mdi-delete"></span></button></td>';
}
}
@@ -298,21 +310,203 @@
function removeRow(rowId) {
const row = document.getElementById(rowId);
if (row) {
const signatureInput = row.querySelector('input[name*="_signature"]');
if (signatureInput) {
cleanupSignatureTracking(signatureInput);
}
row.remove();
updateSubmitButton();
}
}
function validateSignature(signatureInput, pagesFromInput, pagesToInput) {
const signature = signatureInput.value.trim();
if (!signature) {
signatureInput.classList.remove('signature-validating', 'signature-valid', 'signature-invalid');
return;
}
const inputId = signatureInput.id || signatureInput.name;
if (validationTimers[inputId]) {
clearTimeout(validationTimers[inputId]);
}
signatureInput.classList.add('signature-validating');
signatureInput.classList.remove('signature-valid', 'signature-invalid');
validationTimers[inputId] = setTimeout(async () => {
try {
const apiUrl = getApiUrl();
const response = await fetch(apiUrl + '/api/validate-signature?signature=' + encodeURIComponent(signature));
const data = await response.json();
if (data.valid) {
if (!signatureTracking[signature]) {
signatureTracking[signature] = {
totalPages: data.total_pages,
inputs: []
};
} else {
signatureTracking[signature].totalPages = data.total_pages;
}
const existingIndex = signatureTracking[signature].inputs.findIndex(
item => item.signature === signatureInput
);
if (existingIndex === -1) {
signatureTracking[signature].inputs.push({
signature: signatureInput,
pagesFrom: pagesFromInput,
pagesTo: pagesToInput
});
}
signatureInput.classList.remove('signature-validating');
signatureInput.classList.add('signature-valid');
signatureInput.title = 'Signatur gefunden: ' + data.total_pages + ' Seiten';
checkSignatureThreshold(signature);
} else {
signatureInput.classList.remove('signature-validating');
signatureInput.classList.add('signature-invalid');
signatureInput.title = data.error || 'Signatur nicht gefunden';
updateSubmitButton();
}
} catch (error) {
signatureInput.classList.remove('signature-validating');
signatureInput.classList.add('signature-invalid');
signatureInput.title = 'Validierungsfehler - API nicht erreichbar';
updateSubmitButton();
}
}, 800);
}
function checkSignatureThreshold(signature) {
const tracking = signatureTracking[signature];
if (!tracking) return;
let totalRequestedPages = 0;
const threshold = Math.ceil(tracking.totalPages * 0.15);
tracking.inputs.forEach(item => {
const from = parseInt(item.pagesFrom.value) || 0;
const to = parseInt(item.pagesTo.value) || 0;
if (from > 0 && to > 0 && to >= from) {
totalRequestedPages += (to - from + 1);
}
});
const isOverThreshold = totalRequestedPages > threshold;
tracking.inputs.forEach(item => {
const row = item.signature.closest('tr');
if (row) {
if (isOverThreshold) {
row.classList.add('threshold-exceeded');
item.signature.title = 'Warnung: Gesamtanzahl der Seiten (' + totalRequestedPages + ') überschreitet 15% (' + threshold + ' Seiten)';
} else {
row.classList.remove('threshold-exceeded');
item.signature.title = 'Signatur gefunden: ' + tracking.totalPages + ' Seiten (Aktuell: ' + totalRequestedPages + '/' + threshold + ')';
}
}
});
updateSubmitButton();
}
function cleanupSignatureTracking(signatureInput) {
const signature = signatureInput.value.trim();
if (signature && signatureTracking[signature]) {
signatureTracking[signature].inputs = signatureTracking[signature].inputs.filter(
item => item.signature !== signatureInput
);
if (signatureTracking[signature].inputs.length === 0) {
delete signatureTracking[signature];
} else {
checkSignatureThreshold(signature);
}
}
}
function updateSubmitButton() {
const submitButton = document.querySelector('button[type="submit"]');
const hasInvalidSignatures = document.querySelectorAll('.signature-invalid').length > 0;
const hasThresholdExceeded = document.querySelectorAll('.threshold-exceeded').length > 0;
if (hasInvalidSignatures || hasThresholdExceeded) {
submitButton.disabled = true;
if (hasThresholdExceeded) {
submitButton.title = 'Formular kann nicht abgesendet werden: 15%-Grenze überschritten';
} else {
submitButton.title = 'Formular kann nicht abgesendet werden: Ungültige Signaturen';
}
} else {
submitButton.disabled = false;
submitButton.title = '';
}
}
function attachSignatureListeners(row) {
const signatureInput = row.querySelector('input[name*="_signature"]');
const pagesFromInput = row.querySelector('input[name*="_pages_from"]');
const pagesToInput = row.querySelector('input[name*="_pages_to"]');
if (signatureInput && pagesFromInput && pagesToInput) {
if (!signatureInput.id) {
signatureInput.id = 'sig-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}
signatureInput.addEventListener('input', () => {
const oldSignature = signatureInput.dataset.lastSignature || '';
const newSignature = signatureInput.value.trim();
if (oldSignature && oldSignature !== newSignature) {
const tempInput = document.createElement('input');
tempInput.value = oldSignature;
cleanupSignatureTracking(tempInput);
}
signatureInput.dataset.lastSignature = newSignature;
validateSignature(signatureInput, pagesFromInput, pagesToInput);
});
pagesFromInput.addEventListener('input', () => {
const signature = signatureInput.value.trim();
if (signature && signatureTracking[signature]) {
checkSignatureThreshold(signature);
}
});
pagesToInput.addEventListener('input', () => {
const signature = signatureInput.value.trim();
if (signature && signatureTracking[signature]) {
checkSignatureThreshold(signature);
}
});
}
}
function removeMediaSection(sectionId) {
const section = document.getElementById(sectionId);
if (section) {
if (confirm('Möchten Sie diese Sektion wirklich entfernen?')) {
const type = section.getAttribute('data-type');
if (const rows = section.querySelectorAll('tr[id]');
rows.forEach(row => {
const signatureInput = row.querySelector('input[name*="_signature"]');
if (signatureInput) {
cleanupSignatureTracking(signatureInput);
}
});
section.remove();
const btn = document.getElementById('btn-' + type);
if (btn) {
btn.disabled = false;
btn.title = 'Sektion hinzufügen';
}
updateSubmitButton(); btn.disabled = false;
btn.title = 'Sektion hinzufügen';
}
}
}
}