services¶
⬇️ Télécharger cette page en Markdown
EmailService¶
Fichier : src/EmailService.php
Service d'envoi des notifications email aux mairies lors d'un nouveau signalement.
Responsabilités¶
- Envoi SMTP multipart (HTML + texte) via
sendEmailSMTPMultipart() - Rendu des templates
incident_notification_htmletincident_notification_text - Gestion dynamique du mode test/production via
app_settings - Logging des succès et échecs dans les logs PHP
Mode test vs production (v3.0.1)¶
Le mode d'envoi est lu depuis la table app_settings à chaque appel — plus de valeur hardcodée :
$isTestMode = true;
try {
$row = Database::getInstance()->fetchOne(
"SELECT `value` FROM app_settings WHERE `key` = 'email_test_mode'"
);
$isTestMode = $row ? (bool)(int)$row['value'] : true;
} catch (Exception $e) {
error_log("EmailService: impossible de lire email_test_mode, fallback TEST: " . $e->getMessage());
}
$recipient = $isTestMode ? $testEmail : $mairieEmail;
$modeLabel = $isTestMode ? '[MODE TEST]' : '[PROD]';
| Mode | app_settings.value |
Destinataire | Préfixe log |
|---|---|---|---|
| TEST | '1' |
l'adresse de test | [MODE TEST] |
| PROD | '0' |
Email réel de la mairie | [PROD] |
Le fallback en cas d'erreur DB est toujours MODE TEST (fail-safe : aucun email ne part vers une mairie en cas de panne de la table de configuration).
Conditions d'envoi (submit_incident.php)¶
L'email est envoyé uniquement si toutes ces conditions sont vraies :
- La mairie n'a pas de compte administrateur actif —
SELECT COUNT(*) FROM users WHERE mairie_id = ? AND actif = 1→ 0 - La mairie a une adresse email renseignée (
mairies.emailnon vide) - L'email n'est pas invalide depuis moins de 7 jours (
email_invalid+ timestamp de moins de 7 jours) incidents.email_notification_sent = 0— l'email n'a pas déjà été envoyé pour cet incident
La condition 4 couvre le retry WorkManager : si l'incident est déjà en base suite à un crash 500, le retry détecte le doublon et n'envoie l'email que si email_notification_sent = 0.
Gestion des bounces SMTP¶
- Rejet au
RCPT TOouDATA: loggé dansemail_delivery_logs+mairies.email_invalid = 1 - Cooldown 7 jours : aucune tentative SMTP si
email_invalid = 1depuis moins de 7 jours - Après 7 jours : nouvelle tentative automatique — un succès remet
email_invalid = 0
Utilisation¶
// Dans submit_incident.php après insertion de l'incident
$emailService = EmailService::getInstance();
$sent = $emailService->sendNewIncidentNotification($incidentId, $mairieEmail);
if ($sent) {
$db->execute("UPDATE incidents SET email_notification_sent = 1 WHERE id = ?", [$incidentId]);
}
Méthode principale¶
Charge les données de l'incident + les photos, rend les templates, envoie via SMTP. Retourne true si l'envoi a réussi.
EncryptionManager¶
Fichier : src/EncryptionManager.php
Chiffrement AES-256-GCM des données sensibles (stacktraces de bug reports, etc.).
Utilisation (singleton uniquement)¶
// ✅ Correct
$encryption = EncryptionManager::getInstance();
// ❌ Incorrect — constructeur privé (fatal error)
$encryption = new EncryptionManager();
La clé AES est chargée depuis le secret Docker monté en /run/secrets/app_encryption_key (lecture seule).
api/get_photo.php — Déchiffrement à la volée (v3.1, 2026-08-25)¶
Fichier : public/api/get_photo.php
Sert une photo (GET ?id=<photo_id>), en la déchiffrant automatiquement si photos_incident.encrypted = 1. Détermine le Content-Type par analyse du contenu (finfo_buffer), pas par l'extension du fichier.
Deux bugs corrigés (endpoint jusque-là non fonctionnel sur les photos chiffrées) :
- Résolution de chemin fausse :
realpath(__DIR__ . '/../uploads')visaitpublic/uploads/(dossier vide resté dans le dépôt) au lieu deuploads/à la racine du projet. Corrigé en__DIR__ . '/../../uploads'. new EncryptionManager()au lieu deEncryptionManager::getInstance()— fatal error non catché (Error, pasException) sur toute photo chiffrée. Voir l'avertissement ci-dessus.
incident_detail.php utilise désormais systématiquement cet endpoint pour l'affichage des photos déclarant (<img src="/api/get_photo.php?id=...">), plutôt que le chemin filepath brut — nécessaire pour les photos chiffrées, sans effet sur les autres.
src/ExifHelper.php¶
Extraction EXIF (date de prise de vue + GPS) depuis des octets image bruts, à appeler avant chiffrement/floutage — voir Face Blurring — EXIF et floutage.