mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Fix: email clearing in findOrCreateAuthor, htmlspecialchars(null) crash in old(), dead contact_interne field, access_type_id radio clearing
- findOrCreateAuthor: always update email column (pass null when empty/falsy) so clearing an email actually persists - admin/add.php & admin/edit.php old(): add null guard before htmlspecialchars, cast to string - jury-fieldset.php: guard against old() returning array for scalar-checked jury_lecteur keys - formulaire.php: only suppress display_errors in production (not cli-server dev mode) - Removed dead contact_interne field from backoffice form (no DB column, never saved) - Removed dead contactInterne validation from ThesisCreateController - Added "— Non défini" radio option for access_type_id in admin mode for clearing - Fixed strict int-vs-string comparison breaking radio button checked detection
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -69,3 +69,11 @@
|
|||||||
- [x] Mots-clés: interactive tag search with HTMX suggestions, pill display, round bin-icon remove buttons
|
- [x] Mots-clés: interactive tag search with HTMX suggestions, pill display, round bin-icon remove buttons
|
||||||
- [x] Mots-clés: lowercase enforcement, deduplication, absolute dropdown, keyboard arrows/enter/escape, blur hide, spacing + counter above input, CSV import lowercased, space-collapse normalization, minimum 3 keywords required
|
- [x] Mots-clés: lowercase enforcement, deduplication, absolute dropdown, keyboard arrows/enter/escape, blur hide, spacing + counter above input, CSV import lowercased, space-collapse normalization, minimum 3 keywords required
|
||||||
- [x] ErrorHandler: shared static helper for structured error_log + user-friendly messages with precise FK field extraction from SQLite errors. Applied to 12 action files + 6 public controllers + 2 form controllers + partage. Covers FK, UNIQUE, NOT NULL constraint types.
|
- [x] ErrorHandler: shared static helper for structured error_log + user-friendly messages with precise FK field extraction from SQLite errors. Applied to 12 action files + 6 public controllers + 2 form controllers + partage. Covers FK, UNIQUE, NOT NULL constraint types.
|
||||||
|
- [x] Fix: findOrCreateAuthor cannot clear email (empty string skips update, leaves old email)
|
||||||
|
- [ ] Fix: "NON" stored as literal email string in authors table (CSV import or old data)
|
||||||
|
- [x] Fix: contact_interne field in edit form never saved — removed dead field from form and dead validation from create controller
|
||||||
|
- [x] Fix: formulaire.php unconditionally suppresses display_errors even in dev mode
|
||||||
|
- [x] Fix: access_type_id radio has no "none" option — added "— Non défini" radio for admin mode
|
||||||
|
- [x] Fix: radio button checked detection broken (int vs string strict comparison in fieldset-licence-explanation.php)
|
||||||
|
- [x] Fix: htmlspecialchars(null) crash in old() on admin/add.php and admin/edit.php (null values in form data)
|
||||||
|
- [x] Fix: jury-fieldset.php old() return type confusion (array vs string) for jury_lecteur:_interne:_externe keys
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
require_once __DIR__ . '/../../../bootstrap.php';
|
require_once __DIR__ . '/../../../bootstrap.php';
|
||||||
require_once __DIR__ . '/../../../src/AdminAuth.php';
|
require_once __DIR__ . '/../../../src/AdminAuth.php';
|
||||||
|
|
||||||
ini_set('display_errors', 0);
|
// Only suppress display_errors in production (cli-server = dev mode).
|
||||||
ini_set('log_errors', 1);
|
if (php_sapi_name() !== 'cli-server') {
|
||||||
ini_set('error_log', APP_ROOT . '/../error.log');
|
ini_set('display_errors', 0);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_log', APP_ROOT . '/../error.log');
|
||||||
|
}
|
||||||
|
|
||||||
AdminAuth::requireLogin();
|
AdminAuth::requireLogin();
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ function old($key, $default = "") {
|
|||||||
global $formData;
|
global $formData;
|
||||||
if (!isset($formData[$key])) return $default;
|
if (!isset($formData[$key])) return $default;
|
||||||
if (is_array($formData[$key])) return $formData[$key]; // Return raw array for callers that handle it
|
if (is_array($formData[$key])) return $formData[$key]; // Return raw array for callers that handle it
|
||||||
return htmlspecialchars($formData[$key]);
|
if ($formData[$key] === null) return $default;
|
||||||
|
return htmlspecialchars((string)$formData[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wasSelected($key, $value) {
|
function wasSelected($key, $value) {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ function old($key, $default = "") {
|
|||||||
global $formData;
|
global $formData;
|
||||||
if (!isset($formData[$key])) return $default;
|
if (!isset($formData[$key])) return $default;
|
||||||
if (is_array($formData[$key])) return $formData[$key]; // Return raw array for callers that handle it
|
if (is_array($formData[$key])) return $formData[$key]; // Return raw array for callers that handle it
|
||||||
return htmlspecialchars($formData[$key]);
|
if ($formData[$key] === null) return $default;
|
||||||
|
return htmlspecialchars((string)$formData[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -507,15 +507,6 @@ class ThesisCreateController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contact interne (optional, admin-only)
|
|
||||||
$contactInterne = trim($post['contact_interne'] ?? '');
|
|
||||||
if ($contactInterne !== '') {
|
|
||||||
$contactInterne = filter_var($contactInterne, FILTER_VALIDATE_EMAIL);
|
|
||||||
if ($contactInterne === false) {
|
|
||||||
throw new Exception("L'adresse de contact interne n'est pas valide.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note contextuelle (optional, max 1500 chars)
|
// Note contextuelle (optional, max 1500 chars)
|
||||||
$contextNote = $this->sanitiseString($post['context_note'] ?? '');
|
$contextNote = $this->sanitiseString($post['context_note'] ?? '');
|
||||||
if (strlen($contextNote) > 1500) {
|
if (strlen($contextNote) > 1500) {
|
||||||
@@ -539,7 +530,6 @@ class ThesisCreateController
|
|||||||
'authorNames',
|
'authorNames',
|
||||||
'mail',
|
'mail',
|
||||||
'showContact',
|
'showContact',
|
||||||
'contactInterne',
|
|
||||||
'annee',
|
'annee',
|
||||||
'orientationId',
|
'orientationId',
|
||||||
'apProgramId',
|
'apProgramId',
|
||||||
|
|||||||
@@ -959,13 +959,9 @@ class Database
|
|||||||
$author = $stmt->fetch();
|
$author = $stmt->fetch();
|
||||||
|
|
||||||
if ($author) {
|
if ($author) {
|
||||||
if ($email && $email !== '') {
|
// Always update email (may be null to clear) and show_contact.
|
||||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET email = ?, show_contact = ? WHERE id = ?');
|
$updateStmt = $this->pdo->prepare('UPDATE authors SET email = ?, show_contact = ? WHERE id = ?');
|
||||||
$updateStmt->execute([$email, $showContact ? 1 : 0, $author['id']]);
|
$updateStmt->execute([$email && $email !== '' ? $email : null, $showContact ? 1 : 0, $author['id']]);
|
||||||
} else {
|
|
||||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET show_contact = ? WHERE id = ?');
|
|
||||||
$updateStmt->execute([$showContact ? 1 : 0, $author['id']]);
|
|
||||||
}
|
|
||||||
return $author['id'];
|
return $author['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,19 @@
|
|||||||
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
+\\\\\\\ to: szktqmnn 29b3397f "Error tests, FK violations fix" (rebased revision)
|
+\\\\\\\ to: szktqmnn 29b3397f "Error tests, FK violations fix" (rebased revision)
|
||||||
++ $linkName = $link['name'] ?? '';
|
++ $linkName = $link['name'] ?? '';
|
||||||
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%% diff from: szktqmnn 29b3397f "Error tests, FK violations fix" (rebased revision)
|
||||||
|
\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
- $linkName = $link['name'] ?? '';
|
||||||
|
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||||
|
\\\\\\\\\\\\\\\ to: vpwuyvyv 1573e164 "Fix: email clearing in findOrCreateAuthor, htmlspecialchars(null) crash in old(), dead contact_interne field, access_type_id radio clearing" (rebased revision)
|
||||||
|
$linkName = $link['name'] ?? '';
|
||||||
|
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
$linkLockedYear = $link['locked_year'] ?? null;
|
||||||
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
+\\\\\\\ to: vpwuyvyv f513921d "Fix: email clearing in findOrCreateAuthor, htmlspecialchars(null) crash in old(), dead contact_interne field, access_type_id radio clearing" (rebased revision)
|
||||||
|
++ $linkName = $link['name'] ?? '';
|
||||||
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
?>
|
?>
|
||||||
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
||||||
|
|||||||
@@ -33,7 +33,24 @@ $adminMode = $adminMode ?? false;
|
|||||||
<!-- Degré d'ouverture -->
|
<!-- Degré d'ouverture -->
|
||||||
<div class="licence-choice">
|
<div class="licence-choice">
|
||||||
<p class="licence-prompt">J'autorise l'erg à archiver mon TFE de la manière suivante :</p>
|
<p class="licence-prompt">J'autorise l'erg à archiver mon TFE de la manière suivante :</p>
|
||||||
<?php $selectedAccess = $formData['access_type_id'] ?? (string)$defaultAccessTypeId; ?>
|
<?php
|
||||||
|
// access_type_id may be null (meaning "not set"). Keep null to select "—" radio.
|
||||||
|
$selectedAccess = array_key_exists('access_type_id', $formData) ? $formData['access_type_id'] : $defaultAccessTypeId;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($adminMode): ?>
|
||||||
|
<div class="licence-degree">
|
||||||
|
<label class="admin-checkbox-label">
|
||||||
|
<input type="radio" name="access_type_id" value=""
|
||||||
|
hx-post="/admin/licence-fragment.php"
|
||||||
|
hx-target=".licence-license-choice"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-include="closest fieldset"
|
||||||
|
<?= $selectedAccess === '' || $selectedAccess === null ? 'checked' : '' ?>>
|
||||||
|
<strong>—</strong> Non défini
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($libreEnabled): ?>
|
<?php if ($libreEnabled): ?>
|
||||||
<div class="licence-degree">
|
<div class="licence-degree">
|
||||||
@@ -43,7 +60,7 @@ $adminMode = $adminMode ?? false;
|
|||||||
hx-target=".licence-license-choice"
|
hx-target=".licence-license-choice"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-include="closest fieldset"
|
hx-include="closest fieldset"
|
||||||
<?= $selectedAccess === '1' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
<?= (string)$selectedAccess === '1' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
||||||
<strong>🔓 Libre</strong> — Mon TFE est en libre accès à tout le monde sur la plateforme des TFE ainsi que dans la bibliothèque de l'erg.
|
<strong>🔓 Libre</strong> — Mon TFE est en libre accès à tout le monde sur la plateforme des TFE ainsi que dans la bibliothèque de l'erg.
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +74,7 @@ $adminMode = $adminMode ?? false;
|
|||||||
hx-target=".licence-license-choice"
|
hx-target=".licence-license-choice"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-include="closest fieldset"
|
hx-include="closest fieldset"
|
||||||
<?= $selectedAccess === '2' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
<?= (string)$selectedAccess === '2' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
||||||
<strong>🔒 Interne</strong> — Mon TFE n'est accessible que sur place en physique. Une note descriptive est disponible sur le site.
|
<strong>🔒 Interne</strong> — Mon TFE n'est accessible que sur place en physique. Une note descriptive est disponible sur le site.
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +88,7 @@ $adminMode = $adminMode ?? false;
|
|||||||
hx-target=".licence-license-choice"
|
hx-target=".licence-license-choice"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-include="closest fieldset"
|
hx-include="closest fieldset"
|
||||||
<?= $selectedAccess === '3' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
<?= (string)$selectedAccess === '3' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
||||||
<strong>🚫 Interdit</strong> — Mon TFE n'est pas disponible en physique ni sur le site. Une note descriptive est disponible sur le site.
|
<strong>🚫 Interdit</strong> — Mon TFE n'est pas disponible en physique ni sur le site. Une note descriptive est disponible sur le site.
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -511,20 +511,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
|||||||
<small>Case logistique : cocher si un exemplaire physique est disponible à l'ERG.</small>
|
<small>Case logistique : cocher si un exemplaire physique est disponible à l'ERG.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 7. Contact interne -->
|
<!-- 7. Publication -->
|
||||||
<div class="admin-form-group">
|
|
||||||
<label for="contact_interne">Contact interne :</label>
|
|
||||||
<input type="email" id="contact_interne" name="contact_interne"
|
|
||||||
value="<?= htmlspecialchars(
|
|
||||||
$currentRaw["contact_interne"] ??
|
|
||||||
($formData["contact_interne"] ??
|
|
||||||
($currentAuthorEmail ?? "")),
|
|
||||||
) ?>"
|
|
||||||
placeholder="ton.email@exemple.be">
|
|
||||||
<small>Adresse de contact interne (non visible publiquement).</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 8. Publication -->
|
|
||||||
<div class="admin-form-group">
|
<div class="admin-form-group">
|
||||||
<label class="admin-checkbox-label">
|
<label class="admin-checkbox-label">
|
||||||
<input type="checkbox" name="is_published" value="1"
|
<input type="checkbox" name="is_published" value="1"
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ if ($addMode && function_exists('old')) {
|
|||||||
}
|
}
|
||||||
for ($i = 0; $i < 10; $i++) {
|
for ($i = 0; $i < 10; $i++) {
|
||||||
$n = old("jury_lecteur_interne:$i");
|
$n = old("jury_lecteur_interne:$i");
|
||||||
if ($n !== '') $lecteursInternes[] = ['name' => $n];
|
if (is_string($n) && $n !== '') $lecteursInternes[] = ['name' => $n];
|
||||||
}
|
}
|
||||||
for ($i = 0; $i < 10; $i++) {
|
for ($i = 0; $i < 10; $i++) {
|
||||||
$n = old("jury_lecteur_externe:$i");
|
$n = old("jury_lecteur_externe:$i");
|
||||||
if ($n !== '') $lecteursExternes[] = ['name' => $n];
|
if (is_string($n) && $n !== '') $lecteursExternes[] = ['name' => $n];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
Reference in New Issue
Block a user