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: 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] 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__ . '/../../../src/AdminAuth.php';
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', APP_ROOT . '/../error.log');
|
||||
// Only suppress display_errors in production (cli-server = dev mode).
|
||||
if (php_sapi_name() !== 'cli-server') {
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', APP_ROOT . '/../error.log');
|
||||
}
|
||||
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ function old($key, $default = "") {
|
||||
global $formData;
|
||||
if (!isset($formData[$key])) return $default;
|
||||
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) {
|
||||
|
||||
@@ -25,7 +25,8 @@ function old($key, $default = "") {
|
||||
global $formData;
|
||||
if (!isset($formData[$key])) return $default;
|
||||
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 {
|
||||
|
||||
@@ -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)
|
||||
$contextNote = $this->sanitiseString($post['context_note'] ?? '');
|
||||
if (strlen($contextNote) > 1500) {
|
||||
@@ -539,7 +530,6 @@ class ThesisCreateController
|
||||
'authorNames',
|
||||
'mail',
|
||||
'showContact',
|
||||
'contactInterne',
|
||||
'annee',
|
||||
'orientationId',
|
||||
'apProgramId',
|
||||
|
||||
@@ -959,13 +959,9 @@ class Database
|
||||
$author = $stmt->fetch();
|
||||
|
||||
if ($author) {
|
||||
if ($email && $email !== '') {
|
||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET email = ?, show_contact = ? WHERE id = ?');
|
||||
$updateStmt->execute([$email, $showContact ? 1 : 0, $author['id']]);
|
||||
} else {
|
||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET show_contact = ? WHERE id = ?');
|
||||
$updateStmt->execute([$showContact ? 1 : 0, $author['id']]);
|
||||
}
|
||||
// Always update email (may be null to clear) and show_contact.
|
||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET email = ?, show_contact = ? WHERE id = ?');
|
||||
$updateStmt->execute([$email && $email !== '' ? $email : null, $showContact ? 1 : 0, $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)
|
||||
+\\\\\\\ to: szktqmnn 29b3397f "Error tests, FK violations fix" (rebased revision)
|
||||
++ $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'])) : '';
|
||||
?>
|
||||
<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 -->
|
||||
<div class="licence-choice">
|
||||
<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): ?>
|
||||
<div class="licence-degree">
|
||||
@@ -43,7 +60,7 @@ $adminMode = $adminMode ?? false;
|
||||
hx-target=".licence-license-choice"
|
||||
hx-swap="outerHTML"
|
||||
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.
|
||||
</label>
|
||||
</div>
|
||||
@@ -57,7 +74,7 @@ $adminMode = $adminMode ?? false;
|
||||
hx-target=".licence-license-choice"
|
||||
hx-swap="outerHTML"
|
||||
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.
|
||||
</label>
|
||||
</div>
|
||||
@@ -71,7 +88,7 @@ $adminMode = $adminMode ?? false;
|
||||
hx-target=".licence-license-choice"
|
||||
hx-swap="outerHTML"
|
||||
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.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -511,20 +511,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
<small>Case logistique : cocher si un exemplaire physique est disponible à l'ERG.</small>
|
||||
</div>
|
||||
|
||||
<!-- 7. Contact interne -->
|
||||
<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 -->
|
||||
<!-- 7. Publication -->
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="is_published" value="1"
|
||||
|
||||
@@ -51,11 +51,11 @@ if ($addMode && function_exists('old')) {
|
||||
}
|
||||
for ($i = 0; $i < 10; $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++) {
|
||||
$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