prepare('SELECT MAI_Objet, MAI_Corps, MAI_CorpsHtml FROM ASP_Mails WHERE MAI_Code = ?'); $stmt->execute([$codeMail]); $modele = $stmt->fetch(); if (!$modele) { error_log("ASPIC prospects: modèle de mail introuvable ($codeMail)"); return false; } $variables = $variables + ['LogoUrl' => LOGO_URL]; $objet = $modele['MAI_Objet']; $corpsTexte = $modele['MAI_Corps']; $corpsHtml = $modele['MAI_CorpsHtml']; foreach ($variables as $cle => $valeur) { $motif = '{(' . $cle . ')}'; $objet = str_replace($motif, (string) $valeur, $objet); $corpsTexte = str_replace($motif, (string) $valeur, $corpsTexte); if ($corpsHtml !== null) { $corpsHtml = str_replace($motif, (string) $valeur, $corpsHtml); } } $apiKey = getenv('BREVO_API_KEY'); if (!$apiKey) { error_log('ASPIC prospects: BREVO_API_KEY non configurée'); return false; } $donneesEnvoi = [ 'sender' => ['name' => BREVO_FROM_NAME, 'email' => BREVO_FROM_EMAIL], 'to' => [['email' => $destinataire]], 'replyTo' => ['email' => BREVO_REPLY_TO], 'subject' => $objet, 'textContent' => $corpsTexte, ]; if ($corpsHtml !== null && trim($corpsHtml) !== '') { $donneesEnvoi['htmlContent'] = $corpsHtml; } $payload = json_encode($donneesEnvoi); $ch = curl_init('https://api.brevo.com/v3/smtp/email'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'accept: application/json', 'api-key: ' . $apiKey, 'content-type: application/json', ], CURLOPT_TIMEOUT => 10, ]); $reponse = curl_exec($ch); $codeHttp = curl_getinfo($ch, CURLINFO_HTTP_CODE); $erreurCurl = curl_error($ch); curl_close($ch); if ($codeHttp < 200 || $codeHttp >= 300) { error_log("ASPIC prospects: échec envoi Brevo (HTTP $codeHttp) : $reponse $erreurCurl"); return false; } return true; } /** * Journalise une opération sur un prospect dans ASP_Operations. * OPE_CodeUser est toujours 'Vik' (Viktor) : ces événements sont automatiques, * jamais déclenchés par un utilisateur ASPIC connecté. */ function journaliserOperation(int $idProspect, string $codeAction, ?string $commentaire = null): void { $pdo = getDbConnection(); $stmt = $pdo->prepare( 'INSERT INTO ASP_Operations (OPE_IncrProspect, OPE_CodeUser, OPE_Date, OPE_Action, OPE_Comment) VALUES (?, ?, NOW(), ?, ?)' ); $stmt->execute([$idProspect, 'Vik', $codeAction, $commentaire]); } /** * Calcule la signature HMAC des paramètres d'un lien de documentation (a/b/c), * pour empêcher qu'un visiteur ne les modifie sans que ça se voie. * La clé ne doit JAMAIS être exposée côté client. */ function signerParametresDoc(int $idProspect, int $idDoc, int $idCampagne): string { $cle = getenv('LIEN_DOC_CLE_SECRETE'); if (!$cle) { error_log('ASPIC prospects: LIEN_DOC_CLE_SECRETE non configurée'); $cle = ''; } return substr(hash_hmac('sha256', "$idProspect-$idDoc-$idCampagne", $cle), 0, 16); } /** * Construit un lien de documentation signé, à insérer dans un mail. */ function genererLienDoc(int $idProspect, int $idDoc, int $idCampagne): string { $signature = signerParametresDoc($idProspect, $idDoc, $idCampagne); return ASPIC_BASE_URL . '/prospect_doc.php?a=' . $idProspect . '&b=' . $idDoc . '&c=' . $idCampagne . '&d=' . $signature; } /** * Détermine si un numéro de téléphone saisi va dans PSP_Tel ou PSP_Portable : * préfixe 06/07 (après suppression des espaces) => portable, sinon => fixe. */ function routerTelephone(string $telephoneBrut): array { $nettoye = preg_replace('/\s+/', '', $telephoneBrut); if ($nettoye === '') { return ['tel' => null, 'portable' => null]; } $prefixe = substr($nettoye, 0, 2); if ($prefixe === '06' || $prefixe === '07') { return ['tel' => null, 'portable' => $nettoye]; } return ['tel' => $nettoye, 'portable' => null]; } /** * Traite la soumission du formulaire public pour une campagne donnée. * Dérive automatiquement les codes ASP_Actions et le code du mail de vérification * à partir du code de campagne, suivant la convention "-CRE/-VER/-VAL/-DOC/-LEC" * (codes ASP_Actions) et "-DEM" (mail de vérification). */ function traiterAjoutProspect(string $codeCampagne): void { autoriserCorsSiNecessaire(); header('Content-Type: application/json; charset=utf-8'); $data = json_decode(file_get_contents('php://input'), true) ?? []; // Honeypot anti-spam : champ invisible pour un humain, rempli seulement par un robot. if (!empty($data['site_web'] ?? '')) { http_response_code(200); echo json_encode(['success' => true]); exit; } $nom = trim((string) ($data['nom'] ?? '')); $prenom = trim((string) ($data['prenom'] ?? '')); $telephone = trim((string) ($data['telephone'] ?? '')); $mail = trim((string) ($data['mail'] ?? '')); if ($mail === '' && $telephone === '') { http_response_code(400); echo json_encode(['error' => 'Merci de renseigner au moins une adresse mail ou un numéro de téléphone.']); exit; } if (strlen($nom) > 40 || strlen($prenom) > 40 || strlen($mail) > 40) { http_response_code(400); echo json_encode(['error' => 'Un des champs saisis est trop long.']); exit; } $telNettoye = preg_replace('/\s+/', '', $telephone); if (strlen($telNettoye) > 14) { http_response_code(400); echo json_encode(['error' => 'Le numéro de téléphone saisi est trop long.']); exit; } if ($mail !== '' && !filter_var($mail, FILTER_VALIDATE_EMAIL)) { http_response_code(400); echo json_encode(['error' => "L'adresse mail saisie n'est pas valide."]); exit; } $tel = routerTelephone($telephone); try { $pdo = getDbConnection(); $token = genererTokenVerification(); $expiration = date('Y-m-d H:i:s', strtotime('+48 hours')); $stmt = $pdo->prepare( 'INSERT INTO ASP_Prospects (PSP_Nom, PSP_Prenom, PSP_Tel, PSP_Portable, PSP_Mail, PSP_CodeCampagne, PSP_MailVerifie, PSP_TokenVerification, PSP_TokenExpiration, PSP_Actif) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, 1)' ); $stmt->execute([ $nom !== '' ? $nom : null, $prenom !== '' ? $prenom : null, $tel['tel'], $tel['portable'], $mail !== '' ? $mail : null, $codeCampagne, $token, $expiration, ]); $idProspect = (int) $pdo->lastInsertId(); journaliserOperation($idProspect, $codeCampagne . '-CRE'); if ($mail !== '') { $lien = ASPIC_BASE_URL . '/prospects_verifier.php?token=' . urlencode($token); $envoiOk = envoyerMail($codeCampagne . '-DEM', $mail, [ 'Prenom' => $prenom, 'Nom' => $nom, 'Lien' => $lien, ]); if ($envoiOk) { journaliserOperation($idProspect, $codeCampagne . '-VER'); } else { error_log("ASPIC prospects: échec envoi mail de vérification pour le prospect $idProspect"); } } echo json_encode(['success' => true]); } catch (PDOException $e) { error_log('ASPIC prospects: erreur PDO - ' . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'Erreur serveur, merci de réessayer plus tard.']); } }