'forbidden']); exit; } function db() { static $pdo; if (!$pdo) $pdo = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4', DB_USER, DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]); return $pdo; } function body() { return json_decode(file_get_contents('php://input'), true) ?: []; } function out($x) { echo json_encode($x); exit; } function syncro($path) { $url = rtrim(SYNCRO_BASE, '/') . $path . (strpos($path, '?') === false ? '?' : '&') . 'api_key=' . SYNCRO_KEY; $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => ['Accept: application/json']]); $raw = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $raw = preg_replace('/[\x00-\x1F]/', ' ', $raw); // strip control chars Syncro leaks return [$code, json_decode($raw, true)]; } $action = $_GET['action'] ?? ''; // ---- Syncro client lookup by phone ---- if ($action === 'lookup') { $phone = preg_replace('/\D/', '', body()['phone'] ?? ''); if (strlen($phone) < 7) out(['error' => 'phone too short']); [$c, $res] = syncro('/customers?phone=' . urlencode($phone)); $cust = $res['customers'][0] ?? null; if (!$cust) { [$c, $res] = syncro('/customers?query=' . urlencode($phone)); $cust = $res['customers'][0] ?? null; } if (!$cust) out(['error' => 'no Syncro customer for that phone']); $domain = ''; if (!empty($cust['email']) && strpos($cust['email'], '@') !== false) $domain = substr(strrchr($cust['email'], '@'), 1); out(['prefill' => [ 'business_name' => $cust['business_name'] ?: trim(($cust['firstname'] ?? '') . ' ' . ($cust['lastname'] ?? '')), 'syncro_customer_id' => $cust['id'] ?? '', 'address' => trim(($cust['address'] ?? '') . ' ' . ($cust['city'] ?? '') . ' ' . ($cust['state'] ?? '') . ' ' . ($cust['zip'] ?? '')), 'primary_contact' => trim(($cust['firstname'] ?? '') . ' ' . ($cust['lastname'] ?? '')), 'contact_email' => $cust['email'] ?? '', 'email_domains' => $domain ? [$domain] : [], 'tenant_domain' => $domain, 'assessment_date' => date('Y-m-d'), ], 'rmm' => null]); } // ---- Consent link generation ---- if ($action === 'consent') { $b = body(); $provider = $b['provider'] ?? ''; $domain = trim($b['domain'] ?? ''); if ($provider === 'm365') { if (!$domain) out(['error' => 'need tenant domain']); // Admin-consent prompt for our read-only Security Investigator app (multi-tenant). out(['url' => 'https://login.microsoftonline.com/' . rawurlencode($domain) . '/adminconsent?client_id=' . M365_INVESTIGATOR_APP_ID . '&redirect_uri=' . rawurlencode(CONSENT_REDIRECT)]); } if ($provider === 'google') { if (GOOGLE_CLIENT_ID === '') out(['error' => 'Google client not configured yet']); out(['url' => 'https://accounts.google.com/o/oauth2/v2/auth?client_id=' . rawurlencode(GOOGLE_CLIENT_ID) . '&response_type=code&access_type=offline&prompt=consent' . '&scope=' . rawurlencode(GOOGLE_SCOPES) . '&redirect_uri=' . rawurlencode(GOOGLE_REDIRECT) . ($domain ? '&hd=' . rawurlencode($domain) : '')]); } out(['error' => 'unknown provider']); } // ---- Save / upsert ---- if ($action === 'save') { $b = body(); $data = json_encode($b['data'] ?? []); $consent = json_encode($b['consent'] ?? []); if (!empty($b['id'])) { db()->prepare('UPDATE assessments SET phone=?, business_name=?, data=?, consent=?, updated=NOW() WHERE id=?') ->execute([$b['phone'] ?? '', $b['business_name'] ?? '', $data, $consent, $b['id']]); out(['id' => (int)$b['id']]); } db()->prepare('INSERT INTO assessments (phone, business_name, data, consent, created, updated) VALUES (?,?,?,?,NOW(),NOW())') ->execute([$b['phone'] ?? '', $b['business_name'] ?? '', $data, $consent]); out(['id' => (int)db()->lastInsertId()]); } // ---- Load ---- if ($action === 'load') { $row = db()->prepare('SELECT data, consent FROM assessments WHERE id=?'); $row->execute([$_GET['id'] ?? 0]); $r = $row->fetch(); out($r ? ['data' => json_decode($r['data'], true), 'consent' => json_decode($r['consent'], true)] : ['error' => 'not found']); } // ---- List ---- if ($action === 'list') { $rows = db()->query('SELECT id, phone, business_name, DATE_FORMAT(updated, "%Y-%m-%d %H:%i") updated FROM assessments ORDER BY updated DESC LIMIT 100')->fetchAll(); out(['items' => $rows]); } // ---- Export (HTML report-ready intake) ---- if ($action === 'export') { $row = db()->prepare('SELECT * FROM assessments WHERE id=?'); $row->execute([$_GET['id'] ?? 0]); $r = $row->fetch(); if (!$r) { http_response_code(404); exit('not found'); } $data = json_decode($r['data'], true); $consent = json_decode($r['consent'], true); $q = json_decode(file_get_contents(__DIR__ . '/questions.json'), true); header('Content-Type: text/html; charset=utf-8'); echo '