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

@@ -49,6 +49,13 @@ if ($slug === 'licence-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
exit;
}
// Special route: /partage/tag-search-fragment (HTMX fragment — interactive tag search)
if ($slug === 'tag-search-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
App::boot();
require_once __DIR__ . '/tag-search-fragment.php';
exit;
}
// Special route: /partage/recapitulatif?id=N
if ($slug === 'recapitulatif' || $slug === 'recapitulatif.php') {
App::boot();
@@ -440,6 +447,7 @@ function handleShareLinkSubmission(string $slug): void
require_once APP_ROOT . '/src/StudentEmail.php';
require_once APP_ROOT . '/src/AppLogger.php';
require_once APP_ROOT . '/src/DuplicateThesisException.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$logger = new AppLogger();
$authorName = $_POST['auteurice'] ?? 'unknown';
@@ -467,6 +475,7 @@ function handleShareLinkSubmission(string $slug): void
$emailSent = StudentEmail::sendConfirmation(Database::getInstance(), $thesisId, $_POST);
$_SESSION['share_email_sent'] = $emailSent;
} catch (SmtpSendException $e) {
ErrorHandler::log('partage_smtp', $e, ['slug' => $slug, 'thesis_id' => $thesisId]);
if ($e->isRecipientRejected()) {
$_SESSION['share_email_retry_thesis'] = $thesisId;
$_SESSION['share_email_retry_error'] = $e->smtpResponse;
@@ -485,8 +494,7 @@ function handleShareLinkSubmission(string $slug): void
$logger->logDuplicate('partage', $authorName, $e->existingThesisId, $e->existingIdentifier, [
'share_slug' => $slug,
]);
error_log('Share link duplicate submission: ' . $e->getMessage());
ErrorHandler::log('partage_duplicate', $e, ['slug' => $slug, 'author' => $authorName]);
// Repopulate the form and surface a clear warning to the student.
// Store as plain text — htmlspecialchars() is applied at render time.
@@ -505,10 +513,9 @@ function handleShareLinkSubmission(string $slug): void
'author' => $authorName,
'post_keys' => array_keys($_POST),
]);
ErrorHandler::log('partage_submit', $e, ['slug' => $slug, 'author' => $authorName]);
error_log('Share link submission error: ' . $e->getMessage());
$_SESSION['_flash_error'] = $e->getMessage();
$_SESSION['_flash_error'] = ErrorHandler::userMessage($e);
$_SESSION['form_data_share_' . $slug] = $_POST;
$_SESSION[$shareCsrfKey] = bin2hex(random_bytes(32)); // Regenerate token

View File

@@ -0,0 +1,67 @@
<?php
/**
* tag-search-fragment.php
*
* Shared HTMX fragment: returns matching tag suggestions for the mot-clé
* interactive search input.
*
* Included by:
* - /admin/tag-search-fragment.php (AdminAuth gated)
* - partage/index.php special route (public, session already booted)
*
* Expected POST:
* q — search query string (partial tag name)
* tag[] — already selected tag names (sent via hx-include, to exclude from suggestions)
*/
require_once __DIR__ . '/../../src/Database.php';
$query = trim(preg_replace('/\s+/', ' ', strtolower($_POST['q'] ?? '')));
$currentTags = isset($_POST['tag']) && is_array($_POST['tag'])
? array_map(function($t) { return trim(preg_replace('/\s+/', ' ', strtolower($t))); }, $_POST['tag'])
: [];
$db = Database::getInstance();
$results = $db->searchTags($query);
// Deduplicate results by lowercase name
$seen = [];
$results = array_values(array_filter($results, function($tag) use (&$seen) {
$key = strtolower($tag['name']);
if (isset($seen[$key])) return false;
$seen[$key] = true;
return true;
}));
// Filter out already-selected tags (case-insensitive)
$results = array_values(array_filter($results, function($tag) use ($currentTags) {
return !in_array(strtolower($tag['name']), $currentTags, true);
}));
// Check if query exactly matches an existing tag (case-insensitive)
$exactExists = false;
foreach ($results as $tag) {
if (strcasecmp($tag['name'], $query) === 0) {
$exactExists = true;
break;
}
}
// If no exact match and query non-empty, suggest creation
$canCreate = ($query !== '' && !$exactExists && !in_array($query, $currentTags, true));
?>
<?php if (empty($results) && !$canCreate): ?>
<div class="tag-search-empty">Aucun mot-clé trouvé.</div>
<?php endif; ?>
<?php foreach ($results as $tag): ?>
<button type="button" class="tag-search-item" data-tag-id="<?= (int)$tag['id'] ?>" data-tag-name="<?= htmlspecialchars($tag['name']) ?>">
<span class="tag-search-item-name"><?= htmlspecialchars($tag['name']) ?></span>
<span class="tag-search-item-count">(<?= (int)$tag['thesis_count'] ?>)</span>
</button>
<?php endforeach; ?>
<?php if ($canCreate): ?>
<button type="button" class="tag-search-item tag-search-item--create" data-tag-name="<?= htmlspecialchars($query) ?>">
<span class="tag-search-item-name">Créer «&nbsp;<?= htmlspecialchars($query) ?>&nbsp;»</span>
</button>
<?php endif; ?>