fix : formulaires imbriqués dans bulk-form (toggle à la une + dupliquer)

Les <form> admin_toggle_featured et duplicate étaient imbriqués dans
#bulk-form — HTML invalide, le navigateur soumettait le form parent
(suppression) au lieu du bon. Fix : attribut form="id" HTML5 + forms
cachés placés après le bulk-form. Ajoute note CSP + nested forms dans
consignes.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 11:30:12 +02:00
parent 430b7ddd6f
commit fabe5a9f53
2 changed files with 29 additions and 13 deletions
+1
View File
@@ -130,3 +130,4 @@ sudo git config --system --add safe.directory /var/www/lan.acegrp.abonnel-www
- Ne **jamais** versionner `data/`, `.env`, ou `vendor/` dans le dépôt folio. - Ne **jamais** versionner `data/`, `.env`, ou `vendor/` dans le dépôt folio.
- Toujours bumper la version **et** mettre à jour le changelog dans le même commit que la PR. - Toujours bumper la version **et** mettre à jour le changelog dans le même commit que la PR.
- Dans les pools PHP-FPM, toujours utiliser `user = www-data` / `group = www-data`. `cedrix` est un admin ordinaire, pas un compte de service. - Dans les pools PHP-FPM, toujours utiliser `user = www-data` / `group = www-data`. `cedrix` est un admin ordinaire, pas un compte de service.
- **CSP** : le header `Content-Security-Policy` est défini dans la config Apache (`varlog/server/apache/lan.acegrp.varlog.conf`), pas dans PHP. La directive `script-src 'self'` interdit les scripts inline — ne jamais écrire de `<script>` inline dans les templates ; toujours utiliser des fichiers `.js` externes dans `public/assets/js/`. Les erreurs CSP mentionnant `content.js` viennent d'extensions navigateur bloquées par le CSP (comportement normal, pas un bug Folio). Concernant les formulaires HTML : les `<form>` imbriqués sont invalides — un bouton submit dans un form imbriqué soumet le form parent. Utiliser l'attribut HTML5 `form="id-du-form"` pour associer un bouton à un form situé hors du form englobant.
+24 -9
View File
@@ -350,15 +350,11 @@ function adminStatusBadge(array $a, int $now): string
<td class="text-center"> <td class="text-center">
<?php if (isAdmin()): ?> <?php if (isAdmin()): ?>
<?php $_isFeatured = !empty($a['featured']); ?> <?php $_isFeatured = !empty($a['featured']); ?>
<?php $_backUrl = '/admin/articles?' . http_build_query(array_filter(['filter_author' => $adminData['filter_author'] ?? '', 'filter_category' => $adminData['filter_category'] ?? '', 'filter_status' => $adminData['filter_status'] ?? '', 'filter_search' => $adminData['filter_search'] ?? '', 'filter_featured' => $adminData['filter_featured'] ?? '', 'sort' => $_sortBy, 'dir' => $_sortDir], fn ($v) => $v !== '')); ?> <button type="submit" form="toggle-featured-<?= htmlspecialchars($a['uuid']) ?>"
<form method="post" action="/?action=admin_toggle_featured" class="d-inline m-0"> class="btn btn-link p-0 border-0 lh-1 fs-6"
<input type="hidden" name="uuid" value="<?= htmlspecialchars($a['uuid']) ?>">
<input type="hidden" name="_back" value="<?= htmlspecialchars($_backUrl) ?>">
<button type="submit" class="btn btn-link p-0 border-0 lh-1 fs-6"
title="<?= $_isFeatured ? 'Retirer de la une' : 'Mettre à la une' ?>"> title="<?= $_isFeatured ? 'Retirer de la une' : 'Mettre à la une' ?>">
<?= $_isFeatured ? '★' : '<span class="text-muted">☆</span>' ?> <?= $_isFeatured ? '★' : '<span class="text-muted">☆</span>' ?>
</button> </button>
</form>
<?php else: ?> <?php else: ?>
<?= !empty($a['featured']) ? '★' : '' ?> <?= !empty($a['featured']) ? '★' : '' ?>
<?php endif; ?> <?php endif; ?>
@@ -369,16 +365,35 @@ function adminStatusBadge(array $a, int $now): string
<td class="text-end text-nowrap"> <td class="text-end text-nowrap">
<a href="/edit/<?= htmlspecialchars($a['uuid']) ?>" <a href="/edit/<?= htmlspecialchars($a['uuid']) ?>"
class="btn btn-outline-secondary btn-sm">Modifier</a> class="btn btn-outline-secondary btn-sm">Modifier</a>
<form method="post" action="/duplicate/<?= htmlspecialchars($a['uuid']) ?>" class="d-inline ms-1"> <button type="submit" form="dup-<?= htmlspecialchars($a['uuid']) ?>"
<button type="submit" class="btn btn-outline-secondary btn-sm" class="btn btn-outline-secondary btn-sm ms-1"
title="Dupliquer en brouillon">⧉</button> title="Dupliquer en brouillon">⧉</button>
</form>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</form> </form>
<?php
/* Formulaires hors bulk-form (nested forms invalides en HTML) */
$_backUrl = '/admin/articles?' . http_build_query(array_filter([
'filter_author' => $adminData['filter_author'] ?? '',
'filter_category' => $adminData['filter_category'] ?? '',
'filter_status' => $adminData['filter_status'] ?? '',
'filter_search' => $adminData['filter_search'] ?? '',
'filter_featured' => $adminData['filter_featured'] ?? '',
'sort' => $_sortBy,
'dir' => $_sortDir,
], fn ($v) => $v !== ''));
foreach ($adminData['articles'] as $_fa):
?>
<form id="toggle-featured-<?= htmlspecialchars($_fa['uuid']) ?>" method="post" action="/?action=admin_toggle_featured" hidden>
<input type="hidden" name="uuid" value="<?= htmlspecialchars($_fa['uuid']) ?>">
<input type="hidden" name="_back" value="<?= htmlspecialchars($_backUrl) ?>">
</form>
<form id="dup-<?= htmlspecialchars($_fa['uuid']) ?>" method="post" action="/duplicate/<?= htmlspecialchars($_fa['uuid']) ?>" hidden>
</form>
<?php endforeach; ?>
<script src="/assets/js/admin.js" defer></script> <script src="/assets/js/admin.js" defer></script>
<?php endif; ?> <?php endif; ?>