mirror of
https://github.com/JMP-MS/ASPIC.git
synced 2026-08-28 22:23:14 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
// prospects_core.php
|
||||
// Logique commune à tous les scripts publics de prospection ASPIC.
|
||||
// Réutilise db.php (déjà en production) pour la connexion PDO.
|
||||
|
||||
require __DIR__ . '/db.php';
|
||||
|
||||
// --- Configuration ---------------------------------------------------
|
||||
|
||||
const BREVO_FROM_EMAIL = 'viktor@ms.bzh';
|
||||
const BREVO_FROM_NAME = 'Viktor - Mevelia Solutions';
|
||||
const BREVO_REPLY_TO = 'contact@mevelia-solutions.bzh';
|
||||
const ASPIC_BASE_URL = 'https://aspic.mvl-tls.fr';
|
||||
|
||||
// URL publique du logo, injectée automatiquement dans toutes les variables
|
||||
// disponibles pour les modèles de mail (utilisable via {(LogoUrl)}).
|
||||
const LOGO_URL = 'https://aspic.mvl-tls.fr/logo.png';
|
||||
|
||||
const ORIGINES_CORS_AUTORISEES = [
|
||||
'https://mevelia-solutions.bzh',
|
||||
];
|
||||
|
||||
// --- Utilitaires génériques ------------------------------------------
|
||||
|
||||
/**
|
||||
* Envoie les en-têtes CORS nécessaires si l'origine de la requête est autorisée,
|
||||
* et répond immédiatement aux requêtes de pré-vérification (OPTIONS).
|
||||
*/
|
||||
function autoriserCorsSiNecessaire(): void
|
||||
{
|
||||
$origine = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origine, ORIGINES_CORS_AUTORISEES, true)) {
|
||||
header('Access-Control-Allow-Origin: ' . $origine);
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
}
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un token de vérification imprévisible (64 caractères hexadécimaux).
|
||||
*/
|
||||
function genererTokenVerification(): string
|
||||
{
|
||||
return bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un mail depuis un modèle de ASP_Mails, en substituant les variables
|
||||
* de la forme {(NomVariable)} dans l'objet et le corps (texte et HTML).
|
||||
* {(LogoUrl)} est toujours disponible, sans avoir à la passer explicitement.
|
||||
* Retourne true si l'envoi (via l'API Brevo) a réussi.
|
||||
*/
|
||||
function envoyerMail(string $codeMail, string $destinataire, array $variables): bool
|
||||
{
|
||||
$pdo = getDbConnection();
|
||||
$stmt = $pdo->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 "<campagne>-CRE/-VER/-VAL/-DOC/-LEC"
|
||||
* (codes ASP_Actions) et "<campagne>-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.']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user