Error tests, FK violations fix

- ErrorHandler tests: 77 assertions covering FK extraction, normalization, dedup, edge cases. Fix FK table map for child tables.
- Fix FK violation: (int)null → 0 in createThesis for orientation/ap/finality/license FK columns. Add FK value logging to updateThesis.
- Add CURRENT_ISSUES.md with summary of FK violation, dev debugging, and tag dedup status for next conversation
This commit is contained in:
Pontoporeia
2026-05-09 21:36:42 +02:00
parent a80b2c08bf
commit 6cc0e407f3
38 changed files with 1515 additions and 82 deletions

View File

@@ -18,6 +18,7 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/SmtpRelay.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$db = Database::getInstance();
$logger = AdminLogger::make();
@@ -59,7 +60,7 @@ try {
$logger->logAccessRequest($requestId, 'approve', $request['email'], $request['title']);
App::flash('success', "Demande approuvée. Email envoyé à {$request['email']}.");
} catch (SmtpSendException $e) {
error_log('[access-request] Email delivery failed after approval: ' . $e->getMessage());
ErrorHandler::log('access_request_smtp', $e, ['request_id' => $requestId]);
$logger->logAccessRequest($requestId, 'approve', $request['email'], $request['title']);
$smtpMsg = $e->isRecipientRejected()
? "Demande approuvée, mais l'email n'a pas pu être délivré : adresse inconnue ({$request['email']})."
@@ -78,7 +79,7 @@ try {
exit;
} catch (Exception $e) {
error_log('Access request action failed: ' . $e->getMessage());
ErrorHandler::log('access_request', $e);
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Erreur lors du traitement']);
}

View File

@@ -21,6 +21,7 @@ if (!in_array($aproposKey, $allowedKeys)) {
require_once __DIR__ . '/../../../src/Database.php';
require_once __DIR__ . '/../../../src/AdminLogger.php';
require_once __DIR__ . '/../../../src/ErrorHandler.php';
try {
$db = new Database();
@@ -54,8 +55,8 @@ try {
AdminLogger::make()->logAproposEdit($aproposKey);
App::flash('success', "Contenu « $aproposKey » mis à jour avec succès.");
} catch (Exception $e) {
error_log("Apropos save error: " . $e->getMessage());
die("Erreur lors de la sauvegarde : " . htmlspecialchars($e->getMessage()));
ErrorHandler::log('apropos', $e);
die('Erreur lors de la sauvegarde : ' . htmlspecialchars(ErrorHandler::userMessage($e)));
}
header('Location: /admin/contenus.php');

View File

@@ -6,6 +6,7 @@ AdminAuth::requireLogin();
require_once __DIR__ . '/../../../src/Database.php';
require_once __DIR__ . '/../../../src/AdminLogger.php';
require_once __DIR__ . '/../../../src/ErrorHandler.php';
// CSRF validation
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
@@ -57,8 +58,8 @@ try {
}
} catch (Exception $e) {
error_log('delete.php error: ' . $e->getMessage());
App::flash('error', 'Erreur lors de la suppression : ' . $e->getMessage());
ErrorHandler::log('thesis_delete', $e);
App::flash('error', 'Erreur lors de la suppression : ' . ErrorHandler::userMessage($e));
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

View File

@@ -26,6 +26,7 @@ if ($thesisId <= 0) {
require_once APP_ROOT . '/src/Controllers/ThesisEditController.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
try {
$ctrl = ThesisEditController::create();
@@ -41,9 +42,8 @@ try {
exit();
} catch (Exception $e) {
error_log("Edit action error: " . $e->getMessage());
App::flash('error', $e->getMessage());
ErrorHandler::log('thesis_edit', $e, ['thesis_id' => $thesisId]);
App::flash('error', ErrorHandler::userMessage($e));
// WCAG 3.3.1 — map error message to field name for autofocus on re-render.
$autofocusField = ThesisEditController::autofocusFieldForError($e->getMessage());

View File

@@ -15,6 +15,7 @@ AdminAuth::requireLogin();
require_once APP_ROOT . '/src/Controllers/ExportController.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
try {
$controller = ExportController::create();
@@ -40,9 +41,9 @@ try {
// Clean up temp file
@unlink($zipPath);
} catch (Exception $e) {
error_log('Files export error: ' . $e->getMessage());
ErrorHandler::log('export_files', $e);
http_response_code(500);
exit('Erreur lors de la création de l\'archive : ' . htmlspecialchars($e->getMessage()));
exit('Erreur lors de la création de l\'archive : ' . htmlspecialchars(ErrorHandler::userMessage($e)));
}
exit;

View File

@@ -15,6 +15,7 @@ AdminAuth::requireLogin();
require_once APP_ROOT . '/src/Controllers/ExportController.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$doCsv = !empty($_GET['csv']);
$doFiles = !empty($_GET['files']);
@@ -69,9 +70,9 @@ if ($doFiles) {
readfile($zipPath);
@unlink($zipPath);
} catch (Exception $e) {
error_log('Files export error: ' . $e->getMessage());
ErrorHandler::log('export', $e);
http_response_code(500);
exit('Erreur lors de la création de l\'archive : ' . htmlspecialchars($e->getMessage()));
exit('Erreur lors de la création de l\'archive : ' . htmlspecialchars(ErrorHandler::userMessage($e)));
}
}

View File

@@ -19,6 +19,7 @@ $content = $_POST['content'] ?? '';
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$db = new Database();
if (!in_array($key, Database::FORM_HELP_KEYS, true)) {
@@ -32,8 +33,8 @@ try {
AdminLogger::make()->logFormStructureEdit($key);
App::flash('success', 'Bloc « ' . htmlspecialchars($key) . ' » mis à jour.');
} catch (Exception $e) {
error_log('form-help save error: ' . $e->getMessage());
App::flash('error', 'Erreur lors de la sauvegarde : ' . htmlspecialchars($e->getMessage()));
ErrorHandler::log('form_help', $e);
App::flash('error', 'Erreur lors de la sauvegarde : ' . ErrorHandler::userMessage($e));
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

View File

@@ -26,6 +26,7 @@ require_once APP_ROOT . '/src/Controllers/ThesisCreateController.php';
require_once APP_ROOT . '/src/AppLogger.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/DuplicateThesisException.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$logger = new AppLogger();
$adminLogger = AdminLogger::make();
@@ -47,8 +48,7 @@ try {
} catch (DuplicateThesisException $e) {
$logger->logDuplicate('admin', $authorName, $e->existingThesisId, $e->existingIdentifier);
error_log('ThesisCreateController duplicate: ' . $e->getMessage());
ErrorHandler::log('thesis_create_duplicate', $e, ['author' => $authorName]);
// Build a warning with a clickable link to the existing thesis.
$existingUrl = htmlspecialchars('/admin/edit.php?id=' . $e->existingThesisId);
@@ -68,10 +68,8 @@ try {
'author' => $authorName,
'post_keys' => array_keys($_POST),
]);
error_log('ThesisCreateController error: ' . $e->getMessage());
App::flash('error', $e->getMessage());
ErrorHandler::log('thesis_create', $e, ['author' => $authorName]);
App::flash('error', ErrorHandler::userMessage($e));
$_SESSION['form_data'] = $_POST;
$redirect = '../add.php';

View File

@@ -25,6 +25,7 @@ if (!in_array($slug, $allowedSlugs, true)) {
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/AdminLogger.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$db = new Database();
try {
@@ -32,8 +33,8 @@ try {
AdminLogger::make()->logPageEdit($slug);
App::flash('success', 'Page « ' . htmlspecialchars($slug) . ' » mise à jour.');
} catch (Exception $e) {
error_log('page save error: ' . $e->getMessage());
App::flash('error', 'Erreur lors de la sauvegarde : ' . htmlspecialchars($e->getMessage()));
ErrorHandler::log('page', $e);
App::flash('error', 'Erreur lors de la sauvegarde : ' . ErrorHandler::userMessage($e));
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

View File

@@ -6,6 +6,7 @@ AdminAuth::requireLogin();
require_once __DIR__ . '/../../../src/Database.php';
require_once __DIR__ . '/../../../src/AdminLogger.php';
require_once __DIR__ . '/../../../src/ErrorHandler.php';
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
@@ -61,8 +62,8 @@ try {
}
} catch (Exception $e) {
error_log('publish.php error: ' . $e->getMessage());
App::flash('error', 'Erreur lors de la modification : ' . $e->getMessage());
ErrorHandler::log('thesis_publish', $e);
App::flash('error', 'Erreur lors de la modification : ' . ErrorHandler::userMessage($e));
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

View File

@@ -12,6 +12,7 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST'
require_once __DIR__ . '/../../../src/Database.php';
require_once __DIR__ . '/../../../src/AdminLogger.php';
require_once __DIR__ . '/../../../src/ErrorHandler.php';
try {
$db = new Database();
@@ -48,7 +49,8 @@ try {
App::flash('success', "Opération effectuée.");
} catch (Exception $e) {
App::flash('error', $e->getMessage());
ErrorHandler::log('tag', $e);
App::flash('error', ErrorHandler::userMessage($e));
}
header('Location: /admin/tags.php');

View File

@@ -12,6 +12,7 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
require_once __DIR__ . '/../../../src/Database.php';
require_once __DIR__ . '/../../../src/AdminLogger.php';
require_once __DIR__ . '/../../../src/ErrorHandler.php';
$action = $_POST['action'] ?? ''; // 'set_visibility'
$accessTypeId = filter_var($_POST['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
@@ -51,8 +52,8 @@ try {
App::flash('success', "Visibilité mise à jour.");
}
} catch (Exception $e) {
error_log("visibility.php error: " . $e->getMessage());
App::flash('error', "Erreur : " . $e->getMessage());
ErrorHandler::log('visibility', $e);
App::flash('error', 'Erreur : ' . ErrorHandler::userMessage($e));
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));