mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
fix: mark languages as required, add required-field visual indicators on both forms
- checkbox-list.php: support $required prop → adds required + aria-required on fieldset - add.php: languages checkbox now marked required (matches server-side validation) - partage/index.php: same for student form - admin.css: dashed border on required inputs, bold labels, red asterisk via :has(), "Champs obligatoires" note - Both forms now show "* Champs obligatoires" note at top Server-side required fields = titre, auteurice, synopsis, année, orientation, ap, finality, languages (≥1), access_type_id, confirmation_email. All now have required attribute + visual asterisk.
This commit is contained in:
95
TODO.md
95
TODO.md
@@ -1,89 +1,6 @@
|
||||
# TODO
|
||||
|
||||
- [x] Replace inline alert CSS in admin.css with floating bottom-center toast styles (fixed, z-index, animation)
|
||||
- [x] Update flash-messages.php partial to output `.toast` markup in hidden container for footer JS
|
||||
- [x] Add toast container HTML + JS to admin footer.php (centralised, 4s auto-dismiss with fade-out)
|
||||
- [x] Remove redundant flash-messages.php includes from all admin pages (8 files)
|
||||
- [x] Convert hardcoded alerts in login.php, thanks.php, index.php import to `.toast` class
|
||||
- [x] Update admin.css dialog rule from `[role=alert/status]` to `.toast`
|
||||
- [x] Commit with jj
|
||||
|
||||
- [x] Move DB export from admin/index.php to admin/parametres.php (maintenance section)
|
||||
|
||||
- [x] Reorganize src/ - move 7 controllers to src/Controllers/
|
||||
- [x] Create Controllers directory
|
||||
- [x] Move controller files (Home, Tfe, Search, ThesisCreate, ThesisEdit, Export, System)
|
||||
- [x] Update all require_once paths across codebase
|
||||
|
||||
- [x] Move stray test.db from root to storage/
|
||||
|
||||
- [x] Store admin password hash in DB (site_settings) instead of config file
|
||||
- [x] Create migration 013
|
||||
- [x] Update AdminAuth to read hash from DB
|
||||
- [x] Update bootstrap.php — remove credential file loading
|
||||
- [x] Update parametres.php — status check from DB
|
||||
- [x] Update actions/account.php — write hash to DB
|
||||
- [x] Update login.php — dev-mode check
|
||||
- [x] Update header.php — dev check
|
||||
- [x] Delete config/admin_credentials.example.php
|
||||
|
||||
## Now: Single Entry Point Routing
|
||||
|
||||
### Phase 1: Dispatcher refinement
|
||||
- [x] MediaController: extract media.php logic into MediaController class
|
||||
- [x] Create src/Controllers/MediaController.php
|
||||
- [x] Move path validation + storage jail + MIME check + streaming
|
||||
- [x] Wire into Dispatcher for /media route
|
||||
- [x] Delete app/public/media.php
|
||||
- [ ] Update Dispatcher to handle all routes directly (no require APP_ROOT/public/*.php)
|
||||
|
||||
### Phase 2: Single entry point
|
||||
- [x] Create app/public/index.php as front controller
|
||||
- [x] Move bootstrap logic into entry point (bootstrap.php stays for admin)
|
||||
- [x] Load and invoke Dispatcher
|
||||
- [x] Move old public/*.php views into templates/public/
|
||||
- [x] search.php → templates/public/search.php
|
||||
- [x] tfe.php → templates/public/tfe.php
|
||||
- [x] apropos.php → templates/public/about.php
|
||||
- [x] repertoire.php → templates/public/repertoire.php
|
||||
- [x] Delete old direct-access public/*.php files
|
||||
- [x] Delete public/index.php (replaced by front controller)
|
||||
- [x] Delete public/search.php
|
||||
- [x] Delete public/tfe.php
|
||||
- [x] Delete public/apropos.php
|
||||
- [x] Delete public/licence.php
|
||||
- [x] Delete public/repertoire.php
|
||||
- [x] Update Dispatcher.render to use templates/public/ views
|
||||
- [x] Update Dispatcher to render full pages (head + header + view + footer) instead of requiring bootstrap
|
||||
- [x] Ensure admin/index.php bootstraps its own path (not affected by front controller)
|
||||
|
||||
- [x] Fix config/config.php path mess — inline getDatabasePath() into Database.php, delete config/config.php
|
||||
|
||||
### Phase 3: Server config
|
||||
- [ ] Update router.php — route all PHP requests to Dispatcher
|
||||
- [ ] Update nginx config — point all public routes to index.php via try_files
|
||||
- [ ] Replace per-file `location ~ \.php$` with front-controller pattern
|
||||
- [x] Clean URL updates
|
||||
- [x] Remove .php from all internal links (header, views, controllers)
|
||||
- [x] Add clean routes to Dispatcher (/search, /tfe, /media)
|
||||
- [x] Update og:url tags in controllers to use clean URLs
|
||||
- [x] Update TfeController redirect to /
|
||||
- [x] Update header.php action URLs
|
||||
- [x] Commit current state
|
||||
- [ ] Test all routes (/, /search, /tfe, /repertoire, /apropos, /licence, /media, /live-reload)
|
||||
|
||||
# Now: Confirmation email on student form submission
|
||||
- [x] Create src/StudentEmail.php — builds HTML recap email, extracts email from contact field, uses SmtpRelay to send
|
||||
- [x] Wire StudentEmail::sendConfirmation() into partage/index.php handleShareLinkSubmission() after successful thesis creation
|
||||
- [x] Pass email-sent flag via session to /partage/thanks.php
|
||||
- [x] Update partage/thanks.php — show "email sent" notice with styled green badge when confirmation was sent
|
||||
|
||||
- [x] Add "Visiter" (👁 Visit) button to student link action row in acces-etudiante.php
|
||||
- [x] Add link (target _blank) to /partage/<slug>
|
||||
- [x] Add .admin-btn-visit / .admin-btn-visit:hover CSS in admin.css
|
||||
|
||||
- [x] Add required confirmation_email field to both student forms (partage/index.php + admin/add.php)
|
||||
- [x] New fieldset at end of form with type="email", required
|
||||
- [x] ThesisCreateController validates confirmation_email is present and valid
|
||||
- [x] StudentEmail uses confirmation_email directly (no more extractEmail hack)
|
||||
- [x] Autofocus mapping added for confirmation_email validation errors
|
||||
[x] Audit required fields in both admin and student forms
|
||||
[x] Fix: `checkbox-list.php` partial supports `$required` (fieldset gets `required` + `aria-required`)
|
||||
[x] Mark `languages` checkbox list as required in both forms
|
||||
[x] Added visual `*` indicator on all required fields (CSS bold labels + red asterisk)
|
||||
[x] Added "* Champs obligatoires" note at top of both forms
|
||||
[x] Verified: all server-side required fields match client-side `required` attrs and visual indicators
|
||||
|
||||
@@ -59,6 +59,7 @@ include APP_ROOT . '/templates/header.php';
|
||||
|
||||
|
||||
|
||||
<p class="required-note"><span class="asterisk">*</span> Champs obligatoires</p>
|
||||
<form action="actions/formulaire.php" method="post" enctype="multipart/form-data" class="admin-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||
|
||||
@@ -107,7 +108,7 @@ include APP_ROOT . '/templates/header.php';
|
||||
<?php $name = 'ap'; $label = 'Atelier pluridisciplinaire :'; $options = $apPrograms; $selected = $formData['ap'] ?? ''; $required = true; $placeholder = ''; $attrs = withAutofocus('ap'); include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
<?php $name = 'finality'; $label = 'Finalité du master :'; $options = $finalityTypes; $selected = $formData['finality'] ?? ''; $required = true; $placeholder = ''; $attrs = withAutofocus('finality'); include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
|
||||
<?php $name = 'languages'; $label = 'Langue(s) :'; $options = $languages; $checked = $formData['languages'] ?? []; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
<?php $name = 'languages'; $label = 'Langue(s) :'; $options = $languages; $checked = $formData['languages'] ?? []; $required = true; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
<?php $name = 'formats'; $label = 'Format(s) :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
|
||||
<?php $name = 'tag'; $label = 'Mots-clés :'; $value = old('tag'); $placeholder = 'sociologie, anthropologie, ...'; $hint = 'Séparez par des virgules. Max 10 mots-clés.'; $attrs = withAutofocus('tag'); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
|
||||
@@ -147,6 +147,43 @@
|
||||
padding-right: 1.2rem;
|
||||
}
|
||||
|
||||
/* Required-field indicator */
|
||||
.admin-form
|
||||
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not(
|
||||
[type="hidden"]
|
||||
):not([type="submit"]):required,
|
||||
.admin-form select:required,
|
||||
.admin-form textarea:required {
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
|
||||
.admin-form
|
||||
div:has(
|
||||
input:required:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"])) > label,
|
||||
.admin-form div:has(select:required) > label,
|
||||
.admin-form div:has(textarea:required) > label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Required-field indicator (student form / generic labels) */
|
||||
label:has(+ input:required:not([type="hidden"]))::after,
|
||||
label:has(+ select:required)::after,
|
||||
label:has(+ textarea:required)::after,
|
||||
label:has(+ div > input:required)::after {
|
||||
content: " *";
|
||||
color: var(--error, #c00);
|
||||
}
|
||||
|
||||
/* Visually-hidden "required fields marked with *" note */
|
||||
.required-note {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
.required-note .asterisk {
|
||||
color: var(--error, #c00);
|
||||
}
|
||||
|
||||
/* File inputs */
|
||||
.admin-file-input {
|
||||
display: flex;
|
||||
|
||||
@@ -308,6 +308,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
<div class="flash-success" role="alert"><?= htmlspecialchars($flashSuccess) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<p class="required-note"><span class="asterisk">*</span> Champs obligatoires</p>
|
||||
<form action="/partage/<?= urlencode($slug) ?>/submit" method="post" enctype="multipart/form-data" class="admin-form">
|
||||
<input type="hidden" name="share_link_token" value="<?= htmlspecialchars($shareCsrfToken) ?>">
|
||||
|
||||
@@ -376,7 +377,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
<?php $name = 'ap'; $label = 'Atelier pluridisciplinaire :'; $options = $apPrograms; $selected = isset($formData['ap']) ? $formData['ap'] : ''; $required = true; $placeholder = ''; include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
<?php $name = 'finality'; $label = 'Finalité du master :'; $options = $finalityTypes; $selected = isset($formData['finality']) ? $formData['finality'] : ''; $required = true; $placeholder = ''; include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
|
||||
<?php $name = 'languages'; $label = 'Langue(s) :'; $options = $languages; $checked = $formData['languages'] ?? []; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
<?php $name = 'languages'; $label = 'Langue(s) :'; $options = $languages; $checked = $formData['languages'] ?? []; $required = true; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
<?php $name = 'formats'; $label = 'Format(s) :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
|
||||
<?php $name = 'tag'; $label = 'Mots-clés :'; $value = old($formData, 'tag'); $placeholder = 'sociologie, anthropologie, ...'; $hint = 'Séparez par des virgules. Max 10 mots-clés.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
|
||||
@@ -12,13 +12,15 @@
|
||||
* string $label — group label text
|
||||
* array $options — each element must have 'id' and 'name' keys
|
||||
* array $checked — array of 'id' values that are currently checked
|
||||
* bool $required — whether at least one checkbox must be checked; default false
|
||||
*/
|
||||
|
||||
$checked = $checked ?? [];
|
||||
$checked = $checked ?? [];
|
||||
$required = $required ?? false;
|
||||
?>
|
||||
<div>
|
||||
<span class="admin-row-label"><?= htmlspecialchars($label) ?></span>
|
||||
<fieldset class="admin-checkbox-group">
|
||||
<fieldset class="admin-checkbox-group"<?= $required ? ' required aria-required="true"' : '' ?>>
|
||||
<legend class="sr-only"><?= htmlspecialchars($label) ?></legend>
|
||||
<ul>
|
||||
<?php foreach ($options as $opt): ?>
|
||||
|
||||
Reference in New Issue
Block a user