mirror of
https://github.com/JMP-MS/ASPIC.git
synced 2026-08-28 21:13:13 +00:00
278 lines
9.7 KiB
PHP
278 lines
9.7 KiB
PHP
<?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.']);
|
|
}
|
|
}
|