Admin emails : bouton « Voir » ouvre le contenu dans une nouvelle fenêtre #52

Closed
opened 2026-05-13 21:17:02 +00:00 by cedricAbonnel · 1 comment
Owner

Contexte

Dans /admin/emails, la colonne « Contenu » affiche un bouton « Voir » qui déplie le corps de l'email inline dans la ligne du tableau via un élément HTML <details>/<summary>. Seul le content_text (texte brut) est affiché.

Comportement souhaité

Cliquer sur « Voir » ouvre une nouvelle fenêtre/onglet avec le contenu complet de l'email envoyé (version HTML si disponible, texte sinon).


Analyse technique

Structure de données

La table journal_smtp contient :

  • content_html — corps HTML complet (inséré par src/mailer.php:100)
  • content_text — version texte brut (strip_tags du HTML)

La requête actuelle dans public/index.php:2313-2318 sélectionne content_text mais omet content_html.

Contrainte CSP

La CSP applique script-src 'self' — tout script inline est bloqué. L'implémentation doit donc reposer sur un simple lien <a target="_blank">, sans JS inline ni window.open().


Implémentation

1. Nouvelle route GET /admin/email-preview/{id}

Dans public/index.php, ajouter une route protégée par isAdmin() :

// Route : /admin/email-preview/{id}
if (preg_match('#^/admin/email-preview/(\d+)$#', $path, $m)) {
    if (!isAdmin()) { http_response_code(403); exit; }
    $row = dbPdo()->prepare(
        'SELECT subject, to_email, created_at, content_html, content_text FROM journal_smtp WHERE id = :id'
    );
    $row->execute([':id' => (int)$m[1]]);
    $email = $row->fetch(PDO::FETCH_ASSOC);
    if (!$email) { http_response_code(404); exit; }

    header('Content-Type: text/html; charset=UTF-8');
    // Afficher le HTML natif si disponible, sinon texte brut dans un <pre>
    if (!empty($email['content_html'])) {
        echo $email['content_html'];
    } else {
        echo '<pre>' . htmlspecialchars((string)$email['content_text']) . '</pre>';
    }
    exit;
}

2. Modifier le bouton dans templates/admin.php

Remplacer le bloc <details> actuel (lignes 833–846) par :

<td>
    <a href="/admin/email-preview/<?= (int)$em['id'] ?>"
       target="_blank" rel="noopener"
       class="btn btn-outline-secondary btn-sm">Voir</a>
</td>

3. Supprimer content_text de la requête admin

Dans public/index.php:2314, content_text n'est plus utilisé côté template — retirer ce champ de la SELECT (ou le garder pour compatibilité, au choix).


Fichiers concernés

  • public/index.php — ajout de la route /admin/email-preview/{id} + nettoyage de la SELECT
  • templates/admin.php — remplacement du <details> par un lien target="_blank"

Sécurité

Le content_html est généré exclusivement par l'application (magic links, vérification de commentaires, etc.) — pas de contenu utilisateur direct. Le risque XSS est faible, mais la page de preview étant isolée dans un onglet séparé sans accès aux cookies de session admin (elle n'inclut pas le layout), l'impact est contenu.

## Contexte Dans `/admin/emails`, la colonne « Contenu » affiche un bouton « Voir » qui déplie le corps de l'email **inline dans la ligne du tableau** via un élément HTML `<details>/<summary>`. Seul le `content_text` (texte brut) est affiché. ## Comportement souhaité Cliquer sur « Voir » ouvre une **nouvelle fenêtre/onglet** avec le contenu complet de l'email envoyé (version HTML si disponible, texte sinon). --- ## Analyse technique ### Structure de données La table `journal_smtp` contient : - `content_html` — corps HTML complet (inséré par `src/mailer.php:100`) - `content_text` — version texte brut (strip_tags du HTML) La requête actuelle dans `public/index.php:2313-2318` sélectionne `content_text` mais **omet `content_html`**. ### Contrainte CSP La CSP applique `script-src 'self'` — tout script inline est bloqué. L'implémentation doit donc reposer sur un simple lien `<a target="_blank">`, sans JS inline ni `window.open()`. --- ## Implémentation ### 1. Nouvelle route `GET /admin/email-preview/{id}` Dans `public/index.php`, ajouter une route protégée par `isAdmin()` : ```php // Route : /admin/email-preview/{id} if (preg_match('#^/admin/email-preview/(\d+)$#', $path, $m)) { if (!isAdmin()) { http_response_code(403); exit; } $row = dbPdo()->prepare( 'SELECT subject, to_email, created_at, content_html, content_text FROM journal_smtp WHERE id = :id' ); $row->execute([':id' => (int)$m[1]]); $email = $row->fetch(PDO::FETCH_ASSOC); if (!$email) { http_response_code(404); exit; } header('Content-Type: text/html; charset=UTF-8'); // Afficher le HTML natif si disponible, sinon texte brut dans un <pre> if (!empty($email['content_html'])) { echo $email['content_html']; } else { echo '<pre>' . htmlspecialchars((string)$email['content_text']) . '</pre>'; } exit; } ``` ### 2. Modifier le bouton dans `templates/admin.php` Remplacer le bloc `<details>` actuel (lignes 833–846) par : ```php <td> <a href="/admin/email-preview/<?= (int)$em['id'] ?>" target="_blank" rel="noopener" class="btn btn-outline-secondary btn-sm">Voir</a> </td> ``` ### 3. Supprimer `content_text` de la requête admin Dans `public/index.php:2314`, `content_text` n'est plus utilisé côté template — retirer ce champ de la `SELECT` (ou le garder pour compatibilité, au choix). --- ## Fichiers concernés - `public/index.php` — ajout de la route `/admin/email-preview/{id}` + nettoyage de la SELECT - `templates/admin.php` — remplacement du `<details>` par un lien `target="_blank"` ## Sécurité Le `content_html` est généré exclusivement par l'application (magic links, vérification de commentaires, etc.) — pas de contenu utilisateur direct. Le risque XSS est faible, mais la page de preview étant isolée dans un onglet séparé sans accès aux cookies de session admin (elle n'inclut pas le layout), l'impact est contenu.
Author
Owner

Ticket migré vers le dépôt Folio : cedricAbonnel/folio#37

Ticket migré vers le dépôt Folio : https://git.abonnel.fr/cedricAbonnel/folio/issues/37
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cedricAbonnel/varlog#52