#!/usr/bin/env php 1800) { unlink(LOCK_FILE); } else { die("Le processus est déjà en cours d'exécution\n"); } } // Créer le fichier de lock file_put_contents(LOCK_FILE, getmypid()); // Enregistrer un handler pour supprimer le lock en cas d'arrêt register_shutdown_function(function() { if (file_exists(LOCK_FILE)) { unlink(LOCK_FILE); } }); // Simuler l'environnement web pour AppConfig en CLI if (php_sapi_name() === 'cli') { // Détecter l'environnement basé sur le hostname ou un paramètre $hostname = gethostname(); if (strpos($hostname, 'prod') !== false) { $_SERVER['SERVER_NAME'] = 'app.geosector.fr'; } elseif (strpos($hostname, 'rec') !== false || strpos($hostname, 'rapp') !== false) { $_SERVER['SERVER_NAME'] = 'rapp.geosector.fr'; } else { $_SERVER['SERVER_NAME'] = 'dapp.geosector.fr'; // DVA par défaut } $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME']; $_SERVER['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'; // Définir getallheaders si elle n'existe pas (CLI) if (!function_exists('getallheaders')) { function getallheaders() { return []; } } } // Chargement de l'environnement require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/../../src/Config/AppConfig.php'; require_once __DIR__ . '/../../src/Core/Database.php'; require_once __DIR__ . '/../../src/Services/LogService.php'; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use PHPMailer\PHPMailer\Exception; try { // Initialisation de la configuration $appConfig = AppConfig::getInstance(); $dbConfig = $appConfig->getDatabaseConfig(); // Initialiser la base de données avec la configuration Database::init($dbConfig); $db = Database::getInstance(); // Log uniquement si mode debug activé // LogService::log('Démarrage du processeur de queue d\'emails', [ // 'level' => 'info', // 'script' => 'process_email_queue.php' // ]); // Récupérer les emails en attente $stmt = $db->prepare(' SELECT id, fk_pass, to_email, subject, body, headers, attempts FROM email_queue WHERE status = ? AND attempts < ? ORDER BY created_at ASC LIMIT ? '); $stmt->execute(['pending', MAX_ATTEMPTS, BATCH_SIZE]); $emails = $stmt->fetchAll(PDO::FETCH_ASSOC); if (empty($emails)) { // Ne pas logger quand il n'y a rien à faire (toutes les 5 minutes) // LogService::log('Aucun email en attente dans la queue', [ // 'level' => 'debug' // ]); exit(0); } LogService::log('Emails à traiter', [ 'level' => 'info', 'count' => count($emails) ]); // Configuration SMTP $smtpConfig = $appConfig->getSmtpConfig(); $emailConfig = $appConfig->getEmailConfig(); $successCount = 0; $failureCount = 0; // Traiter chaque email foreach ($emails as $emailData) { $emailId = $emailData['id']; $passageId = $emailData['fk_pass']; try { // Incrémenter le compteur de tentatives $stmt = $db->prepare('UPDATE email_queue SET attempts = attempts + 1 WHERE id = ?'); $stmt->execute([$emailId]); // Créer l'instance PHPMailer $mail = new PHPMailer(true); // Configuration du serveur SMTP $mail->isSMTP(); $mail->Host = $smtpConfig['host']; $mail->SMTPAuth = $smtpConfig['auth'] ?? true; $mail->Username = $smtpConfig['user']; $mail->Password = $smtpConfig['pass']; $mail->SMTPSecure = $smtpConfig['secure']; $mail->Port = $smtpConfig['port']; $mail->CharSet = 'UTF-8'; // Configuration de l'expéditeur $fromName = 'Amicale Sapeurs-Pompiers'; // Nom par défaut $mail->setFrom($emailConfig['from'], $fromName); // Destinataire $mail->addAddress($emailData['to_email']); // Sujet $mail->Subject = $emailData['subject']; // Headers personnalisés si présents if (!empty($emailData['headers'])) { // Les headers contiennent déjà les informations MIME pour la pièce jointe // On doit extraire le boundary et reconstruire le message if (preg_match('/boundary="([^"]+)"/', $emailData['headers'], $matches)) { $boundary = $matches[1]; // Le body contient déjà le message complet avec pièce jointe $mail->isHTML(false); $mail->Body = $emailData['body']; // Extraire le contenu HTML et la pièce jointe $parts = explode("--$boundary", $emailData['body']); foreach ($parts as $part) { if (strpos($part, 'Content-Type: text/html') !== false) { // Extraire le contenu HTML $htmlContent = trim(substr($part, strpos($part, "\r\n\r\n") + 4)); $mail->isHTML(true); $mail->Body = $htmlContent; } elseif (strpos($part, 'Content-Type: application/pdf') !== false) { // Extraire le PDF encodé en base64 if (preg_match('/filename="([^"]+)"/', $part, $fileMatches)) { $filename = $fileMatches[1]; $pdfContent = trim(substr($part, strpos($part, "\r\n\r\n") + 4)); // Supprimer les retours à la ligne du base64 $pdfContent = str_replace(["\r", "\n"], '', $pdfContent); // Ajouter la pièce jointe $mail->addStringAttachment( base64_decode($pdfContent), $filename, 'base64', 'application/pdf' ); } } } } } else { // Email simple sans pièce jointe $mail->isHTML(true); $mail->Body = $emailData['body']; } // Ajouter une copie si configuré if (!empty($emailConfig['contact'])) { $mail->addBCC($emailConfig['contact']); } // Envoyer l'email if ($mail->send()) { // Marquer comme envoyé $stmt = $db->prepare(' UPDATE email_queue SET status = ?, sent_at = NOW() WHERE id = ? '); $stmt->execute(['sent', $emailId]); // Mettre à jour le passage si nécessaire if ($passageId > 0) { $stmt = $db->prepare(' UPDATE ope_pass SET date_sent_recu = NOW(), chk_email_sent = 1 WHERE id = ? '); $stmt->execute([$passageId]); } $successCount++; LogService::log('Email envoyé avec succès', [ 'level' => 'info', 'emailId' => $emailId, 'passageId' => $passageId, 'to' => $emailData['to_email'] ]); } else { throw new Exception('Échec de l\'envoi'); } } catch (Exception $e) { $failureCount++; LogService::log('Erreur lors de l\'envoi de l\'email', [ 'level' => 'error', 'emailId' => $emailId, 'passageId' => $passageId, 'error' => $e->getMessage(), 'attempts' => $emailData['attempts'] + 1 ]); // Si on a atteint le nombre max de tentatives, marquer comme échoué if ($emailData['attempts'] + 1 >= MAX_ATTEMPTS) { $stmt = $db->prepare(' UPDATE email_queue SET status = ?, error_message = ? WHERE id = ? '); $stmt->execute(['failed', $e->getMessage(), $emailId]); LogService::log('Email marqué comme échoué après ' . MAX_ATTEMPTS . ' tentatives', [ 'level' => 'warning', 'emailId' => $emailId, 'passageId' => $passageId ]); } } // Pause courte entre chaque email pour éviter la surcharge usleep(500000); // 0.5 seconde } // Logger uniquement s'il y avait des emails à traiter if (count($emails) > 0) { LogService::log('Traitement de la queue terminé', [ 'level' => 'info', 'success' => $successCount, 'failures' => $failureCount, 'total' => count($emails) ]); } } catch (Exception $e) { LogService::log('Erreur fatale dans le processeur de queue', [ 'level' => 'critical', 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // Supprimer le lock en cas d'erreur if (file_exists(LOCK_FILE)) { unlink(LOCK_FILE); } exit(1); } // Nettoyer les vieux emails traités (optionnel) try { // Supprimer les emails envoyés de plus de 30 jours $stmt = $db->prepare(' DELETE FROM email_queue WHERE status = ? AND sent_at < DATE_SUB(NOW(), INTERVAL 30 DAY) '); $stmt->execute(['sent']); $deleted = $stmt->rowCount(); if ($deleted > 0) { LogService::log('Nettoyage des anciens emails', [ 'level' => 'info', 'deleted' => $deleted ]); } } catch (Exception $e) { LogService::log('Erreur lors du nettoyage des anciens emails', [ 'level' => 'warning', 'error' => $e->getMessage() ]); } // Supprimer le lock if (file_exists(LOCK_FILE)) { unlink(LOCK_FILE); } echo "Traitement terminé : $successCount envoyés, $failureCount échecs\n"; exit(0);