src/Controller/LogVerifyController.php (nouveau):
- Route GET /admin/logs/verif/{id}/{hmac} accessible publiquement
- Le hmac dans l'URL est les 16 premiers caracteres du HMAC complet
(suffisant pour identifier le log sans exposer la signature entiere)
- Verifie que le log existe et que le hmac partiel correspond
- Affiche la page de verification avec statut integrite
src/Controller/Admin/LogsController.php - pdf():
- Generation du QR code via Endroid\QrCode pointant vers l'URL
de verification /admin/logs/verif/{id}/{hmac16}
- QR code encode en base64 et passe au template PDF
templates/admin/logs/verify.html.twig (nouveau):
- Page glassmorphism style attestation:
- Log introuvable: bandeau rouge avec croix
- Integrite verifiee: bandeau vert avec checkmark et message
"La signature HMAC-SHA256 a ete verifiee avec succes"
- Integrite compromise: bandeau rouge avec message d'alerte
- Tableau des details: ID, date, utilisateur, methode (badge colore),
action, URL, route, IP
- Signature HMAC-SHA256 affichee en bas
templates/admin/logs/pdf.html.twig:
- Ajout du bloc verification avec QR code (72x72px) et lien URL
identique au style des attestations RGPD (verify-box avec
bordure indigo, QR a gauche, texte a droite)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/Admin/LogsController.php:
- purge(): compte les logs avant suppression, supprime tous les logs,
puis cree un nouveau log via logDirect() avec le message
"Suppression de tous les logs (X entrees supprimees)" pour garder
une trace de la purge meme apres suppression
- delete(): nouvelle route POST /admin/logs/{id}/delete, supprime un
log individuel puis cree un log de trace avec le message
"Suppression du log #X (action du date)" pour conserver l'historique
src/Service/AppLoggerService.php:
- logDirect(): nouvelle methode qui cree un AppLog avec une action
personnalisee sans passer par le dictionnaire ROUTE_LABELS
(utilisee pour les traces de suppression)
templates/admin/logs/index.html.twig:
- Bouton supprimer (croix rouge) ajoute a cote du bouton PDF
sur chaque ligne du tableau, avec confirmation data-confirm
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/Admin/LogsController.php:
- Nouvelle route POST /admin/logs/purge: supprime tous les AppLog
via requete DQL DELETE, accessible uniquement ROLE_ROOT
(le controller entier est deja protege par ROLE_ROOT)
templates/admin/logs/index.html.twig:
- Bouton "Supprimer tous les logs" en haut a droite, rouge,
visible uniquement pour ROLE_ROOT
- Confirmation data-confirm avant suppression
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Entity/AppLog.php:
- createdAt initialise avec date('Y-m-d H:i:s') au lieu de
new DateTimeImmutable() pour tronquer les microsecondes
(PostgreSQL arrondit les microsecondes differemment de PHP,
ce qui causait des HMAC invalides a la relecture)
- generateHmac(): format Y-m-d\TH:i:s sans microsecondes
templates/admin/logs/pdf.html.twig (reecrit):
- Meme style que les attestations RGPD (templates/pdf/rgpd_*.html.twig):
banniere gold avec logo, doc-type badge indigo, titre italic uppercase,
info-grid avec cellules bordure indigo, tableaux data avec header dark,
bloc HMAC avec encadre vert/rouge, footer SARL SITECONSEIL
- Logo passe au template via base64
src/Controller/Admin/LogsController.php:
- pdf(): injection de kernel.project_dir, chargement du logo en base64
et passage au template
src/Command/PurgeEmailTrackingCommand.php:
- Ajout de la purge des AppLog de plus de 90 jours (meme seuil
que EmailTracking), affiche le nombre de logs supprimes
templates/components/pagination/glass.html.twig (nouveau):
- Template de pagination KnpPaginator style glassmorphism:
boutons glass avec hover, page active en glass-gold,
fleches precedent/suivant
config/packages/knp_paginator.yaml (nouveau):
- Configuration KnpPaginator pour utiliser le template glass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Entity/AppLog.php (nouveau):
- id, user (ManyToOne nullable, SET NULL on delete), method (GET/POST/etc),
url (500 chars), route (nom de la route Symfony), action (description
lisible de l'action), ip (nullable), hmac (SHA-256), createdAt
- Index sur created_at pour les requetes paginées
- HMAC genere dans le constructeur avec payload:
method|url|route|action|ip|userId|createdAt (microsecondes)
- verifyHmac(): verification timing-safe avec hash_equals
- Aucun setter sur les champs (immutable apres creation)
src/Repository/AppLogRepository.php (nouveau):
- createPaginatedQueryBuilder(): ORDER BY createdAt DESC avec jointure user
src/Service/AppLoggerService.php (nouveau):
- Dictionnaire ROUTE_LABELS: 30+ routes admin avec descriptions
lisibles (ex: app_admin_clients_create → "Creation d'un client")
- log(): cree un AppLog avec l'action lisible, persiste et flush
- verifyLog(): verifie le HMAC d'un log
- Si la route n'est pas dans le dictionnaire, utilise "Acces a {route}"
- Ajoute "(soumission)" pour les POST
src/EventListener/AdminLogListener.php (nouveau):
- Ecoute KernelEvents::CONTROLLER avec priorite -10
- Intercepte toutes les requetes dont la route commence par app_admin_
- Ignore les requetes AJAX de recherche (evite le spam)
- Recupere l'utilisateur connecte via TokenStorage
- Appelle AppLoggerService::log() dans un try/catch
(ne bloque jamais la requete si le logging echoue)
src/Controller/Admin/LogsController.php (nouveau):
- Route /admin/logs, ROLE_ROOT
- index(): pagination KnpPaginator (20 par page), verifie le HMAC
de chaque log affiche
- pdf(): genere un PDF Dompdf avec toutes les infos du log
+ verification HMAC (CONFORME vert / ALTEREES rouge)
templates/admin/logs/index.html.twig (nouveau):
- Tableau glassmorphism: date, utilisateur, methode (badge colore),
action, URL (tronquee), IP, colonne HMAC (rond vert/rouge),
bouton PDF par ligne
- Pagination KnpPaginator en bas
templates/admin/logs/pdf.html.twig (nouveau):
- PDF A4 avec tableau d'informations du log
- Bloc HMAC avec fond vert "INTEGRITE VERIFIEE" ou rouge
"INTEGRITE COMPROMISE" + signature HMAC complete
- Footer avec mention SARL SITECONSEIL
templates/admin/_layout.html.twig:
- Ajout lien "Logs" dans la sidebar Super Admin avec icone document
migrations/Version20260402211054.php:
- Table app_log avec FK user_id, index sur created_at
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/Admin/SyncController.php - index():
- Calcul du nombre de tarifs synchronises avec Stripe (stripeId non vide)
et non synchronises, passes au template
- Chargement des StripeWebhookSecret depuis la BDD pour afficher
le nombre de webhooks configures
templates/admin/sync/index.html.twig:
- Bloc Tarifs Stripe: affiche "X sync" (vert) + "Y non sync" (rouge si > 0)
+ "/ Z total" (gris) au lieu du texte statique
- Bloc Webhooks Stripe: affiche "X/4 configure(s)" en vert si 4/4,
orange si partiel, rouge si 0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Entity/StripeWebhookSecret.php (nouveau):
- Constantes TYPE_MAIN_LIGHT, TYPE_MAIN_INSTANT, TYPE_CONNECT_LIGHT,
TYPE_CONNECT_INSTANT pour les 4 types de webhook
- type: string(30) unique, identifie le webhook (main_light, etc.)
- secret: string(255), le signing secret retourne par Stripe (whsec_xxx)
- endpointId: string nullable, l'ID de l'endpoint Stripe (we_xxx)
- createdAt: DateTimeImmutable
src/Repository/StripeWebhookSecretRepository.php (nouveau):
- findByType(): trouve un secret par type
- getSecret(): retourne directement la valeur du secret ou null
src/Controller/WebhookStripeController.php (reecrit):
- Les 4 routes lisent le secret depuis la BDD via
StripeWebhookSecretRepository::getSecret() au lieu de variables d'env
- Retourne HTTP 503 si le secret n'est pas encore configure
- Plus besoin des variables STRIPE_WH_*_SECRET dans .env
src/Controller/Admin/SyncController.php:
- syncStripeWebhooks(): sauvegarde les secrets en BDD
(cree ou met a jour StripeWebhookSecret par type)
- Suppression de saveSecretsToEnvLocal() (plus de modification .env.local)
- URL de base lue depuis WEBHOOK_BASE_URL (env)
.env:
- Suppression des 4 variables STRIPE_WH_*_SECRET (stockees en BDD)
- Ajout WEBHOOK_BASE_URL (vide par defaut)
docker/ngrok/sync.sh:
- Ecrit aussi WEBHOOK_BASE_URL en plus de OUTSIDE_URL
ansible/env.local.j2:
- WEBHOOK_BASE_URL=https://stripe.siteconseil.fr pour la prod
migrations/Version20260402205935.php:
- Table stripe_webhook_secret avec type unique, secret, endpoint_id
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Service/StripeWebhookService.php (nouveau):
- createAllWebhooks(baseUrl): cree 4 webhook endpoints dans Stripe:
- /webhooks/stripe/main/light: customer.created/updated/deleted,
product.created/updated, price.created/updated, invoice.created/
finalized/payment_succeeded/payment_failed, subscription.created/
updated/deleted
- /webhooks/stripe/main/instant: checkout.session.completed/expired,
payment_intent.succeeded/payment_failed, charge.succeeded/failed/
refunded/dispute.created, invoice.paid/payment_failed,
customer.subscription.trial_will_end/deleted
- /webhooks/stripe/connect/light: account.updated/application.
authorized/deauthorized, transfer.created/updated, payout.created/
paid/failed
- /webhooks/stripe/connect/instant: payment_intent.succeeded/
payment_failed, charge.succeeded/failed/refunded,
checkout.session.completed
- Verification si le webhook existe deja par URL avant creation
(pas de doublon)
- listWebhooks(): liste tous les webhooks du compte
- deleteWebhook(): supprime un webhook par ID
src/Controller/WebhookStripeController.php (nouveau):
- 4 routes POST sans authentification (firewall webhooks):
/webhooks/stripe/main/light, /webhooks/stripe/main/instant,
/webhooks/stripe/connect/light, /webhooks/stripe/connect/instant
- Verification de signature Stripe via Stripe\Webhook::constructEvent()
- Log de chaque evenement avec channel, type et event_id
- TODO pour dispatcher vers les handlers specifiques
src/Controller/Admin/SyncController.php:
- Nouvelle route POST /admin/sync/stripe/webhooks: prend une URL
de base en parametre, appelle createAllWebhooks(), affiche les
resultats (cree/existe deja) en flash messages
templates/admin/sync/index.html.twig:
- Nouveau bloc "Webhooks Stripe" (bleu) avec champ URL de base
(pre-rempli avec l'URL courante), bouton "Creer les webhooks",
et liste des 4 endpoints avec leurs evenements
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Service/StripePriceService.php (nouveau):
- Utilise Stripe SDK v20 (StripeClient) avec STRIPE_SK
- syncPrice(): pour chaque PriceAutomatic, cree ou retrouve le produit
Stripe via metadata price_auto_type, puis cree le Stripe Price
(unique et/ou recurrent selon monthPrice)
- ensureProduct(): cherche un produit existant par metadata, le cree
sinon, met a jour nom/description si modifies
- createStripePrice(): cree un prix Stripe en centimes, avec
tax_behavior=exclusive, recurring si monthPrice > 0 avec
interval=month (ou year si period >= 12)
- updateStripePriceIfNeeded(): si le montant a change, archive l'ancien
prix Stripe et en cree un nouveau (Stripe ne permet pas de modifier
le montant d'un prix existant)
- syncAll(): synchronise tous les tarifs, retourne synced + errors
src/Service/TarificationService.php:
- Injection optionnelle de StripePriceService
- ensureDefaultPrices(): apres creation des tarifs, sync automatique
avec Stripe (cree produits + prix) en plus de Meilisearch
src/Controller/Admin/TarificationController.php:
- edit(): apres mise a jour d'un tarif, sync automatique avec Stripe
(cree/archive/recree les prix si montant change) et Meilisearch
- Flash d'erreur si Stripe echoue, les modifs locales sont sauvegardees
src/Controller/Admin/SyncController.php:
- Nouvelle route POST /admin/sync/stripe/prices: synchronise tous
les tarifs avec Stripe via StripePriceService::syncAll()
templates/admin/sync/index.html.twig:
- Section "Stripe" avec bouton "Synchroniser Stripe" (violet)
pour les tarifs, avec confirmation avant execution
- Section Meilisearch tarifs renommee "Tarifs - Meilisearch"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
templates/admin/tarification/index.html.twig:
- Champs Stripe Price ID (unique) et Stripe Price ID (abonnement)
remplaces par des input hidden pour conserver les valeurs existantes
sans les afficher dans le formulaire
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
templates/admin/sync/index.html.twig:
- Description changee de "Reindexe tous les clients et revendeurs"
vers "Reindexe tous les clients, revendeurs et tarifs dans Meilisearch"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Service/MeilisearchService.php:
- indexPrice(): indexe un PriceAutomatic dans l'index price_auto
- removePrice(): supprime un tarif de l'index
- searchPrices(): recherche dans les tarifs
- setupIndexes(): ajout de l'index price_auto avec searchableAttributes
(type, title, description) et filterableAttributes (type, period)
- serializePrice(): serialise id, type, title, description, priceHt,
monthPrice, period, stripeId, stripeAbonnementId
src/Service/TarificationService.php:
- Injection optionnelle de MeilisearchService dans le constructeur
- ensureDefaultPrices(): apres flush des nouveaux tarifs, les indexe
automatiquement dans Meilisearch
src/Controller/Admin/SyncController.php:
- Injection de PriceAutomaticRepository dans index() pour compter les tarifs
- Nouvelle route POST /admin/sync/prices: synchronise tous les tarifs
dans l'index price_auto de Meilisearch
- syncAll(): inclut maintenant les tarifs dans la synchronisation complete
templates/admin/sync/index.html.twig:
- Nouveau bloc "Tarifs" avec index price_auto, compteur en base,
bouton "Synchroniser" vert
templates/admin/tarification/index.html.twig:
- Header de chaque tarif: badges de statut Stripe
- "Stripe OK" (vert) si stripeId renseigne, "Non sync" (rouge) sinon
- "Abo OK" (vert) si stripeAbonnementId renseigne, "Abo non sync"
(rouge) sinon (affiche uniquement si monthPrice != 0.00)
- Champ Stripe Price ID abonnement masque si monthPrice == 0.00
(pas d'abonnement pour les paiements uniques)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Entity/PriceAutomatic.php:
- type: ajout contrainte unique pour eviter les doublons
- monthPrice: decimal(10,2) default 0.00, prix mensuel recurrent
- period: smallint default 1, duree de la periode en mois
(1=mensuel, 3=trimestriel, 12=annuel)
- stripeId: string nullable, ID du Stripe Price pour le paiement unique
- stripeAbonnementId: string nullable, ID du Stripe Price pour l'abonnement
src/Service/TarificationService.php (nouveau):
- Constante DEFAULT_PRICES avec 16 tarifs par defaut:
esyweb_business (500€ + 100€/mois), esyweb_premium (3200€ + 100€/mois),
ecommerce_business (999€ + 150€/mois), ecommerce_premium (5110€ + 150€/mois),
esymail (50€ + 30€/mois), esymailer (50€ + 30€/mois),
esydefender_pro (50€ + 60€/mois periode 3), esymeet (50€ + 30€/mois),
esytchat (50€ + 15€/mois), esycreator (500€ + 100€/mois periode 3),
ndd_depot (20€), ndd_renouvellement (20€/an), ndd_gestion (30€/an),
ndd_reactivation (50€), formation_pack10h (500€), formation_heure (70€)
- ensureDefaultPrices(): verifie les tarifs existants, cree ceux manquants
- getAll(), getByType(), getDefaultTypes()
src/Controller/Admin/TarificationController.php (nouveau):
- Route /admin/tarification, ROLE_ROOT
- index(): appelle ensureDefaultPrices() pour creer les tarifs manquants
automatiquement a chaque visite, affiche tous les tarifs editables
- edit(): met a jour titre, description, prixHt, monthPrice, period,
stripeId, stripeAbonnementId via formulaire POST
templates/admin/tarification/index.html.twig (nouveau):
- Liste de tous les tarifs sous forme de cards glassmorphism
- Header dark avec titre, type (badge) et prix
- Formulaire d'edition inline: titre, prix unique, prix mensuel,
periode (select 1/2/3/6/12 mois), Stripe Price ID unique,
Stripe Price ID abonnement, description (textarea)
- Bouton enregistrer par tarif
templates/admin/_layout.html.twig:
- Ajout lien "Tarification" dans la sidebar Super Admin avec icone dollar
migrations/Version20260402204223.php:
- Ajout colonnes month_price, period, stripe_id, stripe_abonnement_id
sur price_automatic + index unique sur type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/Admin/OrderNumberController.php (nouveau):
- Route /admin/numerotation, accessible ROLE_ROOT uniquement
- index(): affiche le prochain numero via OrderNumberService::preview()
et les 20 derniers numeros generes (ORDER BY id DESC)
- update(): modifie le prochain numero en creant une entree placeholder
avec le numero precedent (N-1) marque comme utilise, pour que le
prochain generate() retourne le numero souhaite
- Validation du format MM/YYYY-XXXXX via regex
- Verification que le numero n'existe pas deja
- Verification que le numero est au minimum 00001
templates/admin/order_number/index.html.twig (nouveau):
- Section "Prochain numero" : affiche le prochain numero en gros (gold)
avec formulaire pour le modifier (input avec pattern regex,
placeholder MM/YYYY-XXXXX, explication de l'utilite)
- Section "Derniers numeros generes" : tableau avec numero (font-mono),
date de creation, statut (Utilise vert / Reserve gris)
- Design glassmorphism (glass, input-glass, btn-gold, glass-dark header)
templates/admin/_layout.html.twig:
- Ajout du lien "Numerotation" dans la sidebar Super Admin avec
icone hash (#), route app_admin_order_number, style active-danger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/DnsReportController.php (nouveau):
- Route /email/configuration/{token} accessible via le lien dans le mail
- Utilise le messageId de l'EmailTracking comme token d'acces
(seuls les destinataires du mail ont le lien)
- Execute tous les checks en temps reel: DnsCheckService (SPF, DMARC, MX,
Bounce via dig @1.1.1.1), AwsSesService (domaine, 3 DKIM CNAME,
MAIL FROM MX/TXT, bounce notif), CloudflareService (zone, records),
MailcowService (domaine, DKIM, MX, autodiscover, autoconfig, SRV, MTA-STS)
- Enrichit chaque check avec la colonne Cloudflare
- Passe les resultats au template Twig pour affichage complet
templates/dns_report/index.html.twig (nouveau):
- Page glassmorphism avec header glass
- Resume en haut: 3 cards (verifications OK, erreurs, avertissements)
avec bordures laterales colorees vert/rouge/jaune
- Tableau par domaine avec 6 colonnes: Source (badge colore par type:
orange AWS, violet Mailcow, bleu Cloudflare, gris DNS), Verification,
Attendu, Dig (actuel), Cloudflare, Statut (rond colore)
- Section erreurs detaillees avec liste
- Section avertissements avec liste
- Footer "Esy-Infra - Service de monitoring d'infra"
templates/emails/dns_report.html.twig (simplifie):
- Mail ne contient plus les details: seulement un tableau avec
chaque domaine et son statut (OK vert / WARN jaune / KO rouge)
- Bouton "Voir le rapport complet" avec lien vers la page web
(VML fallback pour Outlook)
- Le lien utilise le placeholder __DNS_REPORT_URL__ remplace par
le MailerService avec le messageId du mail
src/Service/MailerService.php:
- Ajout du remplacement de __DNS_REPORT_URL__ par l'URL absolue
/email/configuration/{messageId} dans sendEmail(), au meme
endroit que __VIEW_URL__
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Service/DnsCheckService.php:
- Methode check() enrichie avec 4 nouveaux champs: expected (valeur attendue),
dig (valeur actuelle trouvee par dig), cloudflare (valeur dans la zone CF),
cf_status (statut de la colonne CF: ok/error/vide)
- checkSpf(): expected = "include:X dans le SPF", dig = contenu SPF complet
- checkDmarc(): expected = "p=reject ou p=quarantine", dig = contenu DMARC
- checkDkim(): expected = "FQDN CNAME/TXT", dig = cible CNAME ou debut TXT
- checkMx(): expected = MX attendu, dig = liste des MX trouves avec priorite
- checkBounce(): expected = "feedback-smtp.*.amazonses.com", dig = valeur trouvee
src/Command/CheckDnsCommand.php:
- Nouveau champ MONITOR_EMAIL = 'monitor@siteconseil.fr' pour l'envoi du rapport
- loadCloudflareRecords(): charge les records CF une seule fois par domaine
au debut de l'execution, retourne un array indexe par domaine
- enrichWithCloudflare(): apres chaque check DNS, parcourt les records CF
pour trouver l'enregistrement correspondant et remplir les colonnes
cloudflare et cf_status dans chaque check
- checkAwsSes(): utilise DnsCheckService::check() avec expected/dig
(ex: expected="Success", dig="Absent" pour la verification domaine)
- checkMailcow(): utilise DnsCheckService::check() avec expected/dig
(ex: expected="Cle Mailcow: abc...", dig="Cle DNS: xyz..." pour DKIM)
- sendReport(): envoie a MONITOR_EMAIL au lieu de l'admin email
templates/emails/dns_report.html.twig:
- Tableau par domaine avec 6 colonnes: Type, Check, Attendu, Dig (actuel),
Cloudflare, Statut (OK/erreur/warning)
- Colonne Dig coloree en vert/rouge/jaune selon le statut du check
- Colonne Cloudflare coloree selon cf_status
- Colonnes avec word-break: break-all pour les longues valeurs DNS
- Bandeau resume en haut avec compteurs succes/erreurs/warnings
avec bordures laterales colorees
- Pied de mail: "Rapport par Esy-Infra - Service de monitoring d'infra"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Architecture:
- Les domaines (siteconseil.fr, esy-web.dev) sont definis en constante
dans la commande uniquement, pas dans les services
- 3 services independants reutilisables:
src/Service/DnsCheckService.php (nouveau):
- Methodes publiques checkSpf(), checkDmarc(), checkDkim(), checkMx(),
checkBounce() qui prennent le domaine en parametre
- Verification SPF: presence des includes amazonses.com et mail.esy-web.dev
- Verification DMARC: politique, presence de rua
- Verification DKIM: test de 10 selecteurs en CNAME et TXT
- Verification MX: le MX attendu est passe en parametre par la commande
- Verification Bounce: MX/CNAME/TXT sur bounce.*
src/Service/AwsSesService.php (nouveau):
- Authentification AWS Signature V4 via HTTP direct (pas de SDK)
- isDomainVerified(): verification du statut du domaine dans SES
- getDkimStatus(): statut DKIM (enabled, verified, tokens)
- getNotificationStatus(): bounce_topic, complaint_topic, forwarding
- listVerifiedIdentities(): liste des domaines verifies
- isAvailable(): test de connectivite API
src/Service/CloudflareService.php (nouveau):
- Authentification Bearer token via HTTP direct (pas de SDK)
- getZoneId(): recupere le zone ID dynamiquement par nom de domaine
(plus besoin de CLOUDFLARE_ZONE_ID en dur)
- getDnsRecords(): tous les enregistrements d'une zone
- getDnsRecordsByType(): filtrage par type (TXT, MX, CNAME...)
- getZone(): informations d'une zone
- isAvailable(): verification du token API
src/Command/CheckDnsCommand.php (reecrit):
- Utilise les 3 services pour orchestrer les verifications
- Affichage console colore avec icones OK/ERREUR/ATTENTION
- Envoie un rapport email via le template Twig dns_report.html.twig
templates/emails/dns_report.html.twig (nouveau):
- Template email compatible tous clients (table-based, CSS inline,
margin/padding longhand, mso-line-height-rule, pas de rgba/border-radius)
- Bandeau colore vert/jaune/rouge selon le statut global
- Section succes avec checkmarks verts dans un tableau alterne
- Section erreurs en rouge avec croix dans un tableau fond #fef2f2
- Section avertissements en jaune avec triangles fond #fffbeb
- Detail par domaine avec tableau type/verification/statut
- Utilise le template email/base.html.twig (header gold, footer dark)
Variables d'environnement ajoutees:
- .env: AWS_PK, AWS_SECRET, AWS_REGION (eu-west-3), CLOUDFLARE_KEY (vides)
- .env.local: valeurs reelles des cles AWS et Cloudflare
- ansible/vault.yml: aws_pk, aws_secret, cloudflare_key
- ansible/env.local.j2: AWS_PK, AWS_SECRET, AWS_REGION, CLOUDFLARE_KEY
avec references au vault
- CLOUDFLARE_ZONE_ID supprime (recupere dynamiquement via l'API)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gmail, ProtonMail, SFR, Yahoo, Outlook.com, Orange et d'autres clients
email suppriment ou remplacent la balise <body> par un <div>, ce qui
fait perdre tous les styles inline definis sur le body.
templates/email/base.html.twig:
- Table wrapper principale: ajout de margin-top/right/bottom/left: 0,
padding-top/right/bottom/left: 0, font-family: Arial, color: #111827,
-webkit-text-size-adjust: 100%, -ms-text-size-adjust: 100%
en plus du background-color: #eeeef3 deja present
- Les styles restent aussi sur le body pour les clients qui le supportent
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Outlook Windows (2007-2019) ne supporte pas la propriete height sur
les elements div, span et p. AOL et Yahoo remplacent height par
min-height ce qui casse le rendu.
templates/emails/revendeur_created.html.twig:
- 2 div spacers <div style="height: 8px;"> remplaces par des table
spacers avec td font-size: 1px et mso-line-height-rule: exactly
line-height: 8px + pour forcer la hauteur sur tous les clients
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Command/TestMailCommand.php (nouveau fichier):
- Commande Symfony app:mail:test <email> --mode=dev|prod
- Envoie un email de test complet pour verifier le rendu sur tous les clients
- Option --mode pour afficher un bandeau DEV (jaune) ou PROD (rouge)
templates/emails/test_mail.html.twig (nouveau fichier):
- Bandeau environnement colore (dev: #f59e0b jaune, prod: #dc2626 rouge)
- Bloc info sombre avec destinataire, environnement et date/heure
- Tableau de donnees avec 3 services (Esy-Web, Esy-Mail, Esy-Defender Pro),
tarifs, statuts (ACTIF vert, IMPAYE rouge) et ligne TOTAL
- Liste de verification des styles: gras, italique, souligne, lien, couleurs
- Bloc alerte avec bordure laterale gold et fond gris
- 2 boutons centres (Gold #fabf04 et Dark #111827) avec fallback VML
pour Outlook via v:roundrect
- Tableau des 5 applications SITECONSEIL avec liens et descriptions
- Simulation de fichier joint avec icone trombone
- Pied de mail avec mention de la commande utilisee
- Tout en CSS inline compatible: padding longhand, margin longhand,
mso-line-height-rule:exactly, line-height en px, background-color,
pas de rgba/border-radius/box-shadow
templates/emails/test_mail.html.twig:
- Balise <u> remplacee par span avec text-decoration: underline en inline
(la balise <u> n'est pas supportee par ProtonMail, Orange iOS, SFR iOS)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Outlook Windows (2007-2019) a un bug avec line-height en em et sans unite:
le rendu est imprevisible. La solution est d'utiliser des valeurs en px
avec mso-line-height-rule:exactly.
templates/email/base.html.twig:
- cellule contenu: line-height: 1.6 remplace par line-height: 22px
avec ajout de mso-line-height-rule: exactly
templates/emails/2fa_code.html.twig:
- 3 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/forgot_password_code.html.twig:
- 3 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/membre_created.html.twig:
- 4 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
- 2 listes ul: line-height: 1.8 remplace par 25px (font 14px)
et 23px (font 13px) + mso-line-height-rule
templates/emails/password_changed.html.twig:
- 4 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/revendeur_created.html.twig:
- 5 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/rgpd_access.html.twig:
- 4 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/rgpd_attestation_signed.html.twig:
- 4 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/rgpd_deletion.html.twig:
- 4 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
templates/emails/rgpd_no_data.html.twig:
- 5 paragraphes: line-height: 1.6 remplace par 22px + mso-line-height-rule
Suppression de box-shadow dans tous les templates email (non supporte
par Outlook Windows, Orange, GMX, WEB.DE, Samsung Email).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Outlook Windows (2007-2019) a un bug ou le padding vertical d'une cellule
est applique a toutes les cellules de la meme ligne avec la plus grande
valeur. SFR, Samsung Email, GMX, WEB.DE, HEY ne supportent que
partiellement le shorthand.
templates/emails/2fa_code.html.twig:
- span code: padding: 16px 32px remplace par padding-top/right/bottom/left
templates/emails/forgot_password_code.html.twig:
- span code: padding: 16px 32px remplace par padding-top/right/bottom/left
templates/emails/membre_created.html.twig:
- bloc identifiants: padding: 20px remplace par les 4 proprietes longhand
- 2 divs internes: padding: 4px 0 remplace par longhand
- bouton connexion: padding: 12px 24px remplace par longhand
templates/emails/revendeur_created.html.twig:
- bloc identifiants: padding: 20px remplace par longhand
- 3 blocs etapes: padding: 12px 16px remplace par longhand
- bouton mot de passe: padding: 14px 24px remplace par longhand
- bouton connexion: padding: 12px 24px remplace par longhand
- bloc info: padding: 16px remplace par longhand
- 3 divs internes: padding: 2px 0 remplace par longhand
templates/emails/rgpd_attestation_signed.html.twig:
- 3 blocs info: padding: 8px 12px remplace par longhand
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Outlook Windows (2007-2019) ne supporte que background-color, pas
le shorthand background. rgba() et border-radius ne sont pas non plus
supportes par Outlook Windows Mail, GMX, Samsung Email, Orange.
templates/emails/2fa_code.html.twig:
- code de verification: background: #111827 remplace par background-color: #111827
templates/emails/forgot_password_code.html.twig:
- code de verification: background: #111827 remplace par background-color: #111827
templates/emails/membre_created.html.twig:
- bloc identifiants: background: #111827 remplace par background-color: #111827
- bouton connexion: background: #fabf04 remplace par background-color: #fabf04,
border rgba(0,0,0,0.1) remplace par #e5e5e5, border-radius: 8px supprime
templates/emails/revendeur_created.html.twig:
- bloc identifiants: background: #111827 remplace par background-color: #111827
- 3 blocs etapes: background: #f9fafb remplace par background-color: #f9fafb
- bouton mot de passe: background: #fabf04 remplace par background-color: #fabf04,
border rgba supprime, border-radius supprime
- bouton connexion: background: #fff remplace par background-color: #ffffff,
border rgba supprime, border-radius supprime
- bloc info: background: #f9fafb remplace par background-color: #f9fafb
templates/emails/rgpd_attestation_signed.html.twig:
- 2 blocs info: background: #f9fafb remplace par background-color: #f9fafb
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
templates/email/base.html.twig:
- Remplacement de la structure div par une structure table-based (role=presentation)
pour compatibilite Outlook/Orange/SFR/Yahoo
- Suppression de rgba(), linear-gradient(), border-radius, box-shadow
(non supportes par Outlook Windows 2007-2019)
- Ajout du doctype XHTML 1.0 Transitional pour Outlook
- Ajout du bloc conditionnel <!--[if mso]> pour forcer Arial sur Outlook
- Remplacement de margin: 0 par margin-top/right/bottom/left: 0
(margin shorthand non supporte par Orange, SFR, certains Outlook)
- Remplacement de padding shorthand par padding-top/right/bottom/left
dans le body et les cellules principales
- Ajout de -webkit-text-size-adjust et -ms-text-size-adjust sur body
- Logo: ajout de width et height auto explicites + border: 0
- Fond semi-transparent rgba(255,255,255,0.92) remplace par #ffffff opaque
- Gradient gold remplace par background-color: #fabf04 fixe
- Couleur footer rgba(255,255,255,0.7) remplacee par #b0b0b0 fixe
- Entite • remplacee par • pour compatibilite XHTML
templates/emails/*.html.twig (9 fichiers):
- Remplacement de toutes les proprietes margin shorthand par les
proprietes longhand margin-top/margin-right/margin-bottom/margin-left
dans 2fa_code, forgot_password_code, membre_created, password_changed,
revendeur_created, rgpd_access, rgpd_attestation_signed, rgpd_deletion,
rgpd_no_data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: OAuth login failed because the authenticator was checking for
old Keycloak group names (super_admin_asso, gp_member) that no longer
exist in the master realm.
Changes:
- src/Security/KeycloakAuthenticator.php:106: resolveRoles() now checks
for 'siteconseil_admin' instead of 'super_admin_asso' to grant ROLE_ROOT
- src/Controller/Admin/MembresController.php:140: member creation role
resolution updated from 'super_admin_asso' to 'siteconseil_admin'
- templates/admin/membres.html.twig: checkbox values updated from
'gp_member' to 'siteconseil_member' and 'super_admin_asso' to
'siteconseil_admin' in the member management form
- assets/app.js:5-6: JS mutual exclusion logic updated to use new
group values 'siteconseil_member' and 'siteconseil_admin'
- tests/Security/KeycloakAuthenticatorTest.php:79: test data updated
from 'super_admin_asso' to 'siteconseil_admin'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Email templates: replace table/tr/td with div-based CSS layout
- PDF templates: replace table with display:table/table-row/table-cell CSS (Dompdf compatible)
- Only table.data (RGPD access session data) remains as actual HTML table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add thead/th to commissions table in tarif.html.twig
- Add th headers to contrat_revendeur parties table
- Add role=presentation to PDF layout tables (info-grid, verify-box, signatures)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace email layout tables with CSS divs in base.html.twig
- Replace deprecated width/align attributes with CSS styles in PDF templates
- Add role="presentation" to email layout tables
- Convert td to th[scope=row] in profil and attestation verify tables
- Reduce function nesting in app.js by extracting renderHit/performSearch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add for/id attributes to all form labels for accessibility compliance
- Add <title> tags to PDF templates (rgpd_access, rgpd_no_data, rgpd_deletion, contrat_revendeur)
- Add role="presentation" to email layout tables
- Remove deprecated cellpadding/cellspacing attributes from all templates
- Fix PHPUnit notices by replacing createMock with createStub where no expectations are set
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>