Ajout du dossier api avec la géolocalisation automatique des casernes de pompiers
This commit is contained in:
300
api/src/Services/ApiService.php
Normal file
300
api/src/Services/ApiService.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
require_once __DIR__ . '/EmailTemplates.php';
|
||||
|
||||
class ApiService {
|
||||
|
||||
/**
|
||||
* Envoie un email selon un type prédéfini
|
||||
*
|
||||
* @param string $email Email du destinataire
|
||||
* @param string $name Nom du destinataire
|
||||
* @param string $type Type d'email ('welcome', 'lostpwd', etc.)
|
||||
* @param array $data Données supplémentaires pour le template
|
||||
* @return int 1 si succès, 0 si échec
|
||||
*/
|
||||
public static function sendEmail(string $email, string $name, string $type, array $data = []): int {
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
$name = ucwords($name);
|
||||
try {
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
// Récupération des paramètres SMTP depuis la configuration
|
||||
$appConfig = AppConfig::getInstance();
|
||||
$smtpConfig = $appConfig->getSmtpConfig();
|
||||
$emailConfig = $appConfig->getEmailConfig();
|
||||
|
||||
// Configuration du serveur
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $smtpConfig['host'];
|
||||
$mail->SMTPAuth = $smtpConfig['auth'];
|
||||
$mail->Username = $smtpConfig['user'];
|
||||
$mail->Password = $smtpConfig['pass'];
|
||||
$mail->SMTPSecure = $smtpConfig['secure'];
|
||||
$mail->Port = $smtpConfig['port'];
|
||||
|
||||
// Configuration de base
|
||||
$mail->setFrom($emailConfig['from'], 'GEOSECTOR');
|
||||
$mail->addAddress($email, $name);
|
||||
$mail->isHTML(true);
|
||||
$mail->CharSet = 'UTF-8';
|
||||
|
||||
// Configuration selon le type d'email
|
||||
switch ($type) {
|
||||
case 'welcome':
|
||||
$mail->Subject = 'Bienvenue sur GEOSECTOR';
|
||||
$mail->Body = EmailTemplates::getWelcomeTemplate($name, $data['username'] ?? '', $data['password']);
|
||||
break;
|
||||
|
||||
case 'lostpwd':
|
||||
$mail->Subject = 'Réinitialisation de votre mot de passe GEOSECTOR';
|
||||
$mail->Body = EmailTemplates::getLostPasswordTemplate($name, $data['username'] ?? '', $data['password']);
|
||||
break;
|
||||
|
||||
case 'alert':
|
||||
$mail->Subject = $data['subject'] ?? 'Alerte GEOSECTOR';
|
||||
$mail->Body = EmailTemplates::getAlertTemplate($data['subject'] ?? 'Alerte', $data['message'] ?? '');
|
||||
break;
|
||||
|
||||
case 'receipt':
|
||||
$mail->Subject = 'Reçu de passage GEOSECTOR';
|
||||
$mail->Body = EmailTemplates::getReceiptTemplate(
|
||||
$name,
|
||||
$data['date'] ?? date('d/m/Y'),
|
||||
$data['address'] ?? '',
|
||||
$data['amount'] ?? '0',
|
||||
$data['paymentMethod'] ?? 'Espèces'
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Type d'email non reconnu");
|
||||
}
|
||||
|
||||
$mail->send();
|
||||
LogService::log("Email '$type' envoyé avec succès", [
|
||||
'level' => 'info',
|
||||
'email' => $email,
|
||||
'type' => $type
|
||||
]);
|
||||
|
||||
return 1;
|
||||
} catch (Exception $e) {
|
||||
LogService::log("Échec d'envoi d'email", [
|
||||
'level' => 'error',
|
||||
'error' => $e->getMessage(),
|
||||
'email' => $email,
|
||||
'type' => $type
|
||||
]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Pour les données qui servent de clé de recherche (comme l'email)
|
||||
public static function encryptSearchableData(string $data): string {
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Forcer un padding cohérent en ajoutant un caractère spécial de contrôle
|
||||
$data = $data . "\x01"; // Garantit que même un bloc parfait reçoit du padding
|
||||
|
||||
$keyBase64 = AppConfig::getInstance()->getEncryptionKey();
|
||||
$key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
|
||||
$iv = str_repeat("\0", 16); // IV fixe
|
||||
|
||||
// Expliciter les options de padding
|
||||
$options = 0; // PKCS7 padding par défaut
|
||||
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, $options, $iv);
|
||||
|
||||
return base64_encode($encrypted);
|
||||
}
|
||||
|
||||
// Pour les données qui ne servent pas de clé de recherche (comme le nom)
|
||||
public static function encryptData(string $data): string {
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$keyBase64 = AppConfig::getInstance()->getEncryptionKey();
|
||||
$key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
|
||||
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('AES-256-CBC'));
|
||||
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
|
||||
return base64_encode($iv . $encrypted);
|
||||
}
|
||||
|
||||
public static function decryptSearchableData(string $encryptedData): string {
|
||||
if (empty($encryptedData)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Décoder la chaîne base64
|
||||
$encrypted = base64_decode($encryptedData);
|
||||
|
||||
// Vérifier que le décodage base64 a fonctionné
|
||||
if ($encrypted === false) {
|
||||
return ''; // Échec du décodage, retourner une chaîne vide
|
||||
}
|
||||
|
||||
// Méthode simple et robuste utilisant le même IV fixe que pour le chiffrement
|
||||
$keyBase64 = AppConfig::getInstance()->getEncryptionKey();
|
||||
$key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
|
||||
$iv = str_repeat("\0", 16); // IV fixe identique à celui utilisé pour le chiffrement
|
||||
|
||||
// Déchiffrer avec la méthode standard
|
||||
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
|
||||
|
||||
// Si le déchiffrement a échoué, retourner une chaîne vide
|
||||
if ($decrypted === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Supprimer uniquement le caractère de contrôle ajouté
|
||||
if (substr($decrypted, -1) === "\x01") {
|
||||
return substr($decrypted, 0, -1);
|
||||
}
|
||||
|
||||
return $decrypted; // Pour la rétrocompatibilité avec les anciennes données
|
||||
}
|
||||
|
||||
public static function decryptData(string $encryptedData): string {
|
||||
if (empty($encryptedData)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$keyBase64 = AppConfig::getInstance()->getEncryptionKey();
|
||||
$key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
|
||||
|
||||
$data = base64_decode($encryptedData);
|
||||
|
||||
// Vérifier que le décodage base64 a fonctionné
|
||||
if ($data === false) {
|
||||
return $encryptedData; // Retourner la chaîne d'origine en cas d'échec
|
||||
}
|
||||
|
||||
$ivLength = openssl_cipher_iv_length('AES-256-CBC');
|
||||
|
||||
// Vérifier que les données sont assez longues pour contenir l'IV
|
||||
if (strlen($data) <= $ivLength) {
|
||||
return $encryptedData; // Retourner la chaîne d'origine en cas d'échec
|
||||
}
|
||||
|
||||
$iv = substr($data, 0, $ivLength);
|
||||
$encrypted = substr($data, $ivLength);
|
||||
|
||||
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
|
||||
|
||||
// Si le déchiffrement échoue, retourner la chaîne d'origine
|
||||
return $decrypted !== false ? $decrypted : $encryptedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un nom d'utilisateur unique à partir du nom, du code postal et de la ville
|
||||
*
|
||||
* @param PDO $db Instance de la base de données
|
||||
* @param string $name Nom de l'utilisateur
|
||||
* @param string $postalCode Code postal
|
||||
* @param string $cityName Nom de la ville
|
||||
* @param int $minLength Longueur minimale du nom d'utilisateur (par défaut 10)
|
||||
* @return string Nom d'utilisateur généré
|
||||
*/
|
||||
public static function generateUserName(PDO $db, string $name, string $postalCode, string $cityName, int $minLength = 10): string {
|
||||
// Nettoyer et préparer les chaînes
|
||||
$name = preg_replace('/[^a-zA-Z0-9]/', '', strtolower($name));
|
||||
$postalCode = preg_replace('/[^0-9]/', '', $postalCode);
|
||||
$cityName = preg_replace('/[^a-zA-Z0-9]/', '', strtolower($cityName));
|
||||
|
||||
// Extraire les premières lettres du nom (2 à 5 caractères)
|
||||
$nameLength = min(5, max(2, rand(2, strlen($name))));
|
||||
$namePart = substr($name, 0, $nameLength);
|
||||
|
||||
// Extraire une partie du code postal (2 à 3 chiffres)
|
||||
$postalLength = min(3, max(2, rand(2, strlen($postalCode))));
|
||||
$postalPart = substr($postalCode, 0, $postalLength);
|
||||
|
||||
// Extraire une partie du nom de la ville (2 à 4 caractères)
|
||||
$cityLength = min(4, max(2, rand(2, strlen($cityName))));
|
||||
$cityPart = substr($cityName, 0, $cityLength);
|
||||
|
||||
// Combiner les parties avec un séparateur aléatoire
|
||||
$separators = ['', '.', '_', '-'];
|
||||
$separator1 = $separators[array_rand($separators)];
|
||||
$separator2 = $separators[array_rand($separators)];
|
||||
|
||||
// Ajouter un nombre aléatoire pour garantir l'unicité
|
||||
$randomNum = rand(10, 999);
|
||||
|
||||
// Construire le nom d'utilisateur
|
||||
$username = $namePart . $separator1 . $postalPart . $separator2 . $cityPart . $randomNum;
|
||||
|
||||
// S'assurer que la longueur minimale est respectée
|
||||
while (strlen($username) < $minLength) {
|
||||
$username .= rand(0, 9);
|
||||
}
|
||||
|
||||
// Vérifier l'unicité du nom d'utilisateur dans la base de données
|
||||
$isUnique = false;
|
||||
$attempts = 0;
|
||||
$originalUsername = $username;
|
||||
|
||||
while (!$isUnique && $attempts < 10) {
|
||||
// Chiffrer le nom d'utilisateur pour la recherche
|
||||
$encryptedUsername = self::encryptSearchableData($username);
|
||||
|
||||
// Vérifier si le nom d'utilisateur existe déjà
|
||||
$stmt = $db->prepare('SELECT COUNT(*) as count FROM users WHERE encrypted_user_name = ?');
|
||||
$stmt->execute([$encryptedUsername]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result && $result['count'] == 0) {
|
||||
$isUnique = true;
|
||||
} else {
|
||||
// Ajouter un nombre aléatoire supplémentaire
|
||||
$username = $originalUsername . rand(100, 999);
|
||||
$attempts++;
|
||||
}
|
||||
}
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un mot de passe sécurisé aléatoire
|
||||
*
|
||||
* @param int $minLength Longueur minimale du mot de passe (par défaut 12)
|
||||
* @param int $maxLength Longueur maximale du mot de passe (par défaut 16)
|
||||
* @return string Mot de passe généré
|
||||
*/
|
||||
public static function generateSecurePassword(int $minLength = 12, int $maxLength = 16): string {
|
||||
$lowercase = 'abcdefghijklmnopqrstuvwxyz';
|
||||
$uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$numbers = '0123456789';
|
||||
$special = '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
||||
|
||||
$length = rand($minLength, $maxLength);
|
||||
$password = '';
|
||||
|
||||
// Au moins un de chaque type
|
||||
$password .= $lowercase[rand(0, strlen($lowercase) - 1)];
|
||||
$password .= $uppercase[rand(0, strlen($uppercase) - 1)];
|
||||
$password .= $numbers[rand(0, strlen($numbers) - 1)];
|
||||
$password .= $special[rand(0, strlen($special) - 1)];
|
||||
|
||||
// Compléter avec des caractères aléatoires
|
||||
$all = $lowercase . $uppercase . $numbers . $special;
|
||||
for ($i = strlen($password); $i < $length; $i++) {
|
||||
$password .= $all[rand(0, strlen($all) - 1)];
|
||||
}
|
||||
|
||||
// Mélanger le mot de passe
|
||||
return str_shuffle($password);
|
||||
}
|
||||
}
|
||||
76
api/src/Services/EmailTemplates.php
Normal file
76
api/src/Services/EmailTemplates.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class EmailTemplates {
|
||||
/**
|
||||
* Template d'email de bienvenue
|
||||
*/
|
||||
public static function getWelcomeTemplate(string $name, string $username, string $password): string {
|
||||
return "
|
||||
Bonjour $name,<br><br>
|
||||
Votre compte a été créé avec succès sur <b>GeoSector</b>.<br><br>
|
||||
<b>Identifiant :</b> $username<br>
|
||||
<b>Mot de passe :</b> $password<br><br>
|
||||
Vous pouvez vous connecter dès maintenant sur <a href=\"https://app.geosector.fr\">app.geosector.fr</a><br><br>
|
||||
À très bientôt,<br>
|
||||
L'équipe GeoSector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Template d'email pour mot de passe perdu
|
||||
*/
|
||||
public static function getLostPasswordTemplate(string $name, string $username, string $password): string {
|
||||
return "
|
||||
Bonjour $name,<br><br>
|
||||
Vous avez demandé la réinitialisation de votre mot de passe sur <b>GeoSector</b>.<br><br>
|
||||
<b>Nouveau mot de passe :</b> $password<br><br>
|
||||
Vous pouvez vous connecter avec ce nouveau mot de passe sur <a href=\"https://app.geosector.fr\">app.geosector.fr</a><br><br>
|
||||
À très bientôt,<br>
|
||||
L'équipe GeoSector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Template d'email pour alerte
|
||||
*/
|
||||
public static function getAlertTemplate(string $subject, string $message): string {
|
||||
return "
|
||||
<h2>$subject</h2>
|
||||
<p>$message</p>
|
||||
<br>
|
||||
<p>L'équipe GeoSector</p>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Template de reçu de passage
|
||||
*/
|
||||
public static function getReceiptTemplate(string $name, string $date, string $address, string $amount, string $paymentMethod): string {
|
||||
return "
|
||||
<h2>Reçu de passage GeoSector</h2>
|
||||
<p>Bonjour $name,</p>
|
||||
<p>Nous vous remercions pour votre contribution lors de notre passage.</p>
|
||||
<br>
|
||||
<table style='width:100%; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding:8px; border:1px solid #ddd;'><b>Date :</b></td>
|
||||
<td style='padding:8px; border:1px solid #ddd;'>$date</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style='padding:8px; border:1px solid #ddd;'><b>Adresse :</b></td>
|
||||
<td style='padding:8px; border:1px solid #ddd;'>$address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style='padding:8px; border:1px solid #ddd;'><b>Montant :</b></td>
|
||||
<td style='padding:8px; border:1px solid #ddd;'>$amount €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style='padding:8px; border:1px solid #ddd;'><b>Mode de paiement :</b></td>
|
||||
<td style='padding:8px; border:1px solid #ddd;'>$paymentMethod</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<p>Votre soutien est précieux pour notre amicale. Nous vous en remercions chaleureusement.</p>
|
||||
<p>À bientôt,</p>
|
||||
<p>L'équipe GeoSector</p>";
|
||||
}
|
||||
}
|
||||
117
api/src/Services/LogService.php
Normal file
117
api/src/Services/LogService.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
class LogService {
|
||||
public static function log(string $message, array $metadata = []): void {
|
||||
// Obtenir les informations client via ClientDetector
|
||||
$clientInfo = ClientDetector::getClientInfo();
|
||||
$clientType = $clientInfo['type'];
|
||||
|
||||
$defaultMetadata = [
|
||||
'level' => 'info',
|
||||
'client' => [
|
||||
'ip' => $clientInfo['ip'],
|
||||
'browser' => $clientInfo['browser'],
|
||||
'os' => $clientInfo['os'],
|
||||
'screenResolution' => 'N/A',
|
||||
'userAgent' => $clientInfo['userAgent']
|
||||
],
|
||||
'client_type' => $clientType
|
||||
];
|
||||
|
||||
// Si c'est une app mobile, ajouter l'identifiant de l'app
|
||||
if ($clientType === 'mobile' && isset($clientInfo['appIdentifier'])) {
|
||||
$defaultMetadata['app_identifier'] = $clientInfo['appIdentifier'];
|
||||
}
|
||||
|
||||
$metadata = array_merge_recursive($defaultMetadata, $metadata);
|
||||
|
||||
$logData = [
|
||||
'metadata' => $metadata,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
try {
|
||||
// Récupérer la configuration de l'application
|
||||
$appConfig = AppConfig::getInstance();
|
||||
$appName = $appConfig->getName();
|
||||
|
||||
// Récupérer l'environnement défini dans la configuration
|
||||
$environment = $appConfig->getEnvironment();
|
||||
|
||||
// Définir le chemin du dossier logs à la racine du projet
|
||||
$logDir = __DIR__ . '/../../logs';
|
||||
|
||||
// Créer le nom du fichier basé sur l'application et l'environnement
|
||||
// Format: geosector-production-2025-03-28.log, geosector-recette-2025-03-28.log, geosector-development-2025-03-28.log
|
||||
$filename = $logDir . '/' . $appName . '-' . $environment . '-' . date('Y-m-d') . '.log';
|
||||
|
||||
// Créer le dossier logs s'il n'existe pas
|
||||
if (!is_dir($logDir)) {
|
||||
if (!mkdir($logDir, 0777, true)) {
|
||||
error_log("Impossible de créer le dossier de logs: {$logDir}");
|
||||
return; // Sortir de la fonction si on ne peut pas créer le dossier
|
||||
}
|
||||
// S'assurer que les permissions sont correctes
|
||||
chmod($logDir, 0777);
|
||||
}
|
||||
|
||||
// Vérifier si le dossier est accessible en écriture
|
||||
if (!is_writable($logDir)) {
|
||||
error_log("Le dossier de logs n'est pas accessible en écriture: {$logDir}");
|
||||
return; // Sortir de la fonction si on ne peut pas écrire dans le dossier
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log("Erreur lors de la configuration des logs: " . $e->getMessage());
|
||||
return; // Sortir de la fonction en cas d'erreur
|
||||
}
|
||||
|
||||
try {
|
||||
// Formater la ligne de log au format plat demandé
|
||||
// timestamp;browser.name@browser.version;os.name@os.version;client_type;$metadata;$message
|
||||
$timestamp = date('Y-m-d\TH:i:s');
|
||||
$browserInfo = $clientInfo['browser']['name'] . '@' . $clientInfo['browser']['version'];
|
||||
$osInfo = $clientInfo['os']['name'] . '@' . $clientInfo['os']['version'];
|
||||
|
||||
// Extraire le niveau de log
|
||||
$level = isset($metadata['level']) ? (is_array($metadata['level']) ? 'info' : $metadata['level']) : 'info';
|
||||
|
||||
// Préparer les métadonnées supplémentaires (exclure celles déjà incluses dans le format)
|
||||
$additionalMetadata = [];
|
||||
foreach ($metadata as $key => $value) {
|
||||
if (!in_array($key, ['browser', 'os', 'client_type', 'side', 'version', 'level', 'environment', 'client'])) {
|
||||
if (is_array($value)) {
|
||||
$additionalMetadata[$key] = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$additionalMetadata[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Joindre les métadonnées supplémentaires avec des virgules
|
||||
$metadataStr = !empty($additionalMetadata) ? implode(',', array_map(function($k, $v) {
|
||||
return $k . '=' . $v;
|
||||
}, array_keys($additionalMetadata), $additionalMetadata)) : '-';
|
||||
|
||||
// Construire la ligne de log au format demandé
|
||||
$logLine = implode(';', [
|
||||
$timestamp,
|
||||
$browserInfo,
|
||||
$osInfo,
|
||||
$clientType,
|
||||
$level,
|
||||
$metadataStr,
|
||||
$message
|
||||
]) . "\n";
|
||||
|
||||
// Écrire dans le fichier avec gestion d'erreur
|
||||
if (file_put_contents($filename, $logLine, FILE_APPEND) === false) {
|
||||
error_log("Impossible d'écrire dans le fichier de logs: {$filename}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log("Erreur lors de l'écriture des logs: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user