nis2-agile/application/controllers/MktgLeadController.php
DevEnv nis2-agile b1dcd4cbd7 [FEAT] Standardizzazione lead form — allineamento a TRPG Agile
MktgLeadController.php:
- Endpoint POST /api/mktg-lead/submit (standard condiviso TRPG/NIS2)
- Proxy a mktg.agile.software/api/webhook/leads con X-Webhook-Key server-side
- Payload standard: name, email, phone, company, product_interest, source, notes
- source: "nis2-landing" per tracciamento CRM
- Fallback email a info@agile.software se webhook non raggiungibile
- Rate limit 3/10min per IP, supporto campi IT e EN

index.html — form allineato a TRPG:
- Aggiunto: telefono (opzionale)
- Aggiunto: tipo utilizzo (select 6 opzioni)
- N° dipendenti: fasce standardizzate (<50/50-249/250-999/1000+)
- Aggiunto: interesse (info/demo/accesso/integrazione B2B)
- Endpoint aggiornato a /api/mktg-lead/submit
- Payload mappato su campi standard EN + source: "nis2-landing"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 12:14:42 +01:00

196 lines
7.7 KiB
PHP

<?php
/**
* MktgLeadController — Raccolta lead marketing standardizzata
*
* Endpoint pubblico (no auth):
* POST /api/mktg-lead → riceve il lead e lo inoltra a mktg.agile.software
*
* Standard condiviso con TRPG Agile — la chiave webhook rimane server-side.
*/
require_once APP_PATH . '/controllers/BaseController.php';
require_once APP_PATH . '/services/EmailService.php';
class MktgLeadController extends BaseController
{
private const WEBHOOK_URL = 'https://mktg.agile.software/api/webhook/leads';
private const WEBHOOK_KEY = 'wh_nis2_2026_c1d2e3f4a5b6c7d8';
private const PRODUCT = 'NIS2 Agile';
private const SOURCE = 'nis2-landing';
private const NOTIFY_EMAIL = 'info@agile.software';
private EmailService $email;
public function __construct()
{
parent::__construct();
$this->email = new EmailService();
}
/**
* POST /api/mktg-lead
*/
public function submit(): void
{
// Rate limit: 3 richieste / 10 min per IP
$ip = $this->getClientIP();
$cacheFile = sys_get_temp_dir() . '/nis2_mktglead_' . md5($ip);
$now = time();
$cache = file_exists($cacheFile) ? json_decode(file_get_contents($cacheFile), true) : ['requests' => []];
$cache['requests'] = array_filter($cache['requests'] ?? [], fn($t) => $t > ($now - 600));
if (count($cache['requests']) >= 3) {
$this->jsonError('Troppe richieste. Riprova tra qualche minuto.', 429);
return;
}
$cache['requests'][] = $now;
file_put_contents($cacheFile, json_encode($cache));
// Input
$body = $this->getRequestBody();
// Supporta sia campi IT (form NIS2) che campi EN (standard mktg)
$name = trim($body['name'] ?? $body['nome'] ?? '');
$email = trim($body['email'] ?? '');
$phone = trim($body['phone'] ?? $body['telefono'] ?? '');
$company = trim($body['company'] ?? $body['azienda'] ?? '');
$role = trim($body['role'] ?? $body['tipo'] ?? '');
$size = trim($body['size'] ?? $body['n_dipendenti'] ?? '');
$interest = trim($body['product_interest'] ?? $body['interesse'] ?? '');
$notes = trim($body['notes'] ?? $body['messaggio'] ?? '');
$source = trim($body['source'] ?? self::SOURCE);
// Validazione
if (!$name || !$email || !$company) {
$this->jsonError('Compila i campi obbligatori: nome, email, azienda.', 422);
return;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->jsonError('Indirizzo email non valido.', 422);
return;
}
// Sanitize
$name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
$email = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
$phone = htmlspecialchars($phone, ENT_QUOTES, 'UTF-8');
$company = htmlspecialchars($company, ENT_QUOTES, 'UTF-8');
$role = htmlspecialchars($role, ENT_QUOTES, 'UTF-8');
$size = htmlspecialchars($size, ENT_QUOTES, 'UTF-8');
$interest = htmlspecialchars($interest, ENT_QUOTES, 'UTF-8');
$source = htmlspecialchars($source, ENT_QUOTES, 'UTF-8');
// Componi notes con campi aggiuntivi
$noteParts = [];
if ($role) $noteParts[] = "Ruolo: {$role}";
if ($size) $noteParts[] = "Dimensioni: {$size}";
if ($interest) $noteParts[] = "Interesse: {$interest}";
if ($notes) $noteParts[] = $notes;
$fullNotes = implode(' | ', $noteParts);
// Payload standard mktg
$payload = [
'name' => $name,
'email' => $email,
'phone' => $phone,
'company' => $company,
'product_interest' => $interest ?: self::PRODUCT,
'source' => $source,
'notes' => $fullNotes,
];
// 1. Prova webhook mktg.agile.software
$webhookOk = $this->sendWebhook($payload);
// 2. Fallback email se webhook fallisce
if (!$webhookOk) {
$this->sendFallbackEmail($payload, $ip);
}
$this->jsonSuccess(null, 'Richiesta inviata! Ti contatteremo entro 24 ore con il tuo codice di accesso.');
}
private function sendWebhook(array $payload): bool
{
$ch = curl_init(self::WEBHOOK_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Webhook-Key: ' . self::WEBHOOK_KEY,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 8,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error || $httpCode < 200 || $httpCode >= 300) {
error_log("MktgLead webhook failed [{$httpCode}]: {$error}");
return false;
}
return true;
}
private function sendFallbackEmail(array $payload, string $ip): void
{
$date = date('d/m/Y H:i');
$rows = '';
$labels = [
'name' => 'Nome',
'email' => 'Email',
'phone' => 'Telefono',
'company' => 'Azienda',
'product_interest' => 'Interesse',
'source' => 'Source',
'notes' => 'Note',
];
foreach ($labels as $key => $label) {
$val = $payload[$key] ?? '';
if (!$val) continue;
$rows .= "<tr style='border-top:1px solid rgba(239,68,68,0.1);'>
<td style='padding:10px 0;color:#94A3B8;font-size:13px;width:140px;vertical-align:top;'>{$label}</td>
<td style='padding:10px 0;color:#F8FAFC;font-size:14px;'>{$val}</td>
</tr>";
}
$html = "
<div style='font-family:Inter,Arial,sans-serif;max-width:600px;margin:0 auto;'>
<div style='background:#0F172A;padding:24px 32px;border-radius:12px 12px 0 0;'>
<h2 style='color:#EF4444;margin:0;font-size:20px;'>
🛡️ Nuovo lead NIS2 Agile — " . self::PRODUCT . "
</h2>
<p style='color:#94A3B8;margin:6px 0 0;font-size:13px;'>Ricevuto il {$date} · IP: {$ip}</p>
</div>
<div style='background:#1E293B;padding:28px 32px;border-radius:0 0 12px 12px;'>
<table style='width:100%;border-collapse:collapse;'>{$rows}</table>
<div style='margin-top:24px;padding:16px;background:rgba(239,68,68,0.05);border-radius:8px;border:1px solid rgba(239,68,68,0.15);'>
<p style='margin:0;color:#94A3B8;font-size:13px;'>
⚠️ Webhook mktg.agile.software non raggiungibile — lead salvato via email.<br>
Genera il codice invito: <a href='https://nis2.agile.software/licenseExt.html' style='color:#EF4444;'>licenseExt.html</a>
</p>
</div>
</div>
</div>";
$this->email->send(
self::NOTIFY_EMAIL,
"🛡️ Lead NIS2 Agile — {$payload['name']} ({$payload['company']})",
$html,
'noreply@agile.software'
);
}
private function getClientIP(): string
{
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
}