$success, 'message' => $message, 'timestamp' => date('c') ]; // Only include data if not null and not in production (security) if ($data !== null && (!USE_PRODUCTION || DEBUG_MODE)) { $response['data'] = $data; } elseif ($data !== null && $success) { // In production, only return safe data $response['data'] = filterSafeData($data); } echo json_encode($response); exit(); } function filterSafeData($data) { // Remove sensitive fields from response $sensitiveFields = ['webhook_url', 'ip_address', 'user_agent', 'http_code', 'error']; if (is_array($data)) { foreach ($sensitiveFields as $field) { unset($data[$field]); } } return $data; } function getClientIP() { $ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR']; foreach ($ipKeys as $key) { if (array_key_exists($key, $_SERVER)) { foreach (explode(',', $_SERVER[$key]) as $ip) { $ip = trim($ip); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } return $_SERVER['REMOTE_ADDR'] ?? 'unknown'; } function sanitizeInput($input) { if (is_array($input)) { return array_map('sanitizeInput', $input); } return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); } function validateCSRFToken($token) { // Implement CSRF validation if needed return true; } function checkRateLimit($ip) { $rateLimitFile = __DIR__ . '/rate_limits.json'; $limits = []; if (file_exists($rateLimitFile)) { $limits = json_decode(file_get_contents($rateLimitFile), true) ?: []; } $now = time(); $windowStart = $now - RATE_LIMIT_WINDOW; // Clean old entries $limits = array_filter($limits, function($entry) use ($windowStart) { return $entry['time'] > $windowStart; }); // Count requests from this IP $ipRequests = array_filter($limits, function($entry) use ($ip) { return $entry['ip'] === $ip; }); if (count($ipRequests) >= RATE_LIMIT_REQUESTS) { return false; } // Add new request $limits[] = ['ip' => $ip, 'time' => $now]; file_put_contents($rateLimitFile, json_encode($limits)); return true; } // ========================================== // WEBHOOK FUNCTIONS // ========================================== function sendToWebhook($data, $webhookUrl = null) { $url = $webhookUrl ?? WEBHOOK_URL; $jsonData = json_encode($data); // Log the attempt error_log("Webhook attempt - URL: " . $url . " - Data length: " . strlen($jsonData)); // Try cURL first if available if (function_exists('curl_init')) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $jsonData, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json', 'User-Agent: Profice-Web-API/2.0' ], CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); $errno = curl_errno($ch); curl_close($ch); error_log("Webhook cURL response - HTTP: $httpCode, Error: $error, Response: " . substr($response, 0, 500)); if (!$error && $httpCode > 0) { return [ 'success' => $httpCode >= 200 && $httpCode < 300, 'http_code' => $httpCode, 'response' => $response, 'error' => $error, 'method' => 'curl' ]; } error_log("Webhook cURL failed [$errno]: $error - trying file_get_contents fallback"); } // Fallback to file_get_contents if cURL fails or unavailable $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\r\nAccept: application/json\r\nUser-Agent: Profice-Web-API/2.0\r\n", 'content' => $jsonData, 'timeout' => 30, 'ignore_errors' => true ], 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true ] ]); $response = @file_get_contents($url, false, $context); // Get HTTP response code from headers $httpCode = 0; if (isset($http_response_header) && is_array($http_response_header)) { foreach ($http_response_header as $header) { if (preg_match('/^HTTP\/\d+\.?\d*\s+(\d+)/', $header, $matches)) { $httpCode = (int)$matches[1]; } } } error_log("Webhook file_get_contents response - HTTP: $httpCode, Response: " . substr($response ?: '', 0, 500)); if ($response !== false) { return [ 'success' => $httpCode >= 200 && $httpCode < 300, 'http_code' => $httpCode, 'response' => $response, 'error' => null, 'method' => 'file_get_contents' ]; } $lastError = error_get_last(); error_log("Webhook all methods failed - Last error: " . json_encode($lastError)); return [ 'success' => false, 'error' => $lastError['message'] ?? 'All HTTP methods failed', 'errno' => 0, 'response' => null, 'method' => 'none' ]; } // ========================================== // TRACKING FUNCTIONS - SERVER SIDE // ========================================== function getTrackingConfig() { // Return tracking configuration for client // IDs are loaded from server, not exposed in JS return [ 'analytics_enabled' => !empty(GA_MEASUREMENT_ID) && GA_MEASUREMENT_ID !== 'G-XXXXXXXXXX', 'gtm_enabled' => !empty(GTM_CONTAINER_ID) && GTM_CONTAINER_ID !== 'GTM-XXXXXXX', 'fb_enabled' => !empty(FB_PIXEL_ID), 'gads_enabled' => !empty(GADS_CONVERSION_ID) && GADS_CONVERSION_ID !== 'AW-XXXXXXXXXX', 'linkedin_enabled' => !empty(LINKEDIN_PARTNER_ID) ]; } function loadTrackingScripts($preferences) { $scripts = []; // Google Analytics if ($preferences['analytics'] && !empty(GA_MEASUREMENT_ID) && GA_MEASUREMENT_ID !== 'G-XXXXXXXXXX') { $scripts['ga'] = [ 'type' => 'analytics', 'src' => 'https://www.googletagmanager.com/gtag/js?id=' . GA_MEASUREMENT_ID, 'config' => [ 'id' => GA_MEASUREMENT_ID ] ]; } // Google Tag Manager if ($preferences['analytics'] && !empty(GTM_CONTAINER_ID) && GTM_CONTAINER_ID !== 'GTM-XXXXXXX') { $scripts['gtm'] = [ 'type' => 'gtm', 'src' => 'https://www.googletagmanager.com/gtm.js?id=' . GTM_CONTAINER_ID, 'config' => [ 'id' => GTM_CONTAINER_ID ] ]; } // Facebook Pixel if ($preferences['marketing'] && !empty(FB_PIXEL_ID)) { $scripts['fb'] = [ 'type' => 'pixel', 'src' => 'https://connect.facebook.net/en_US/fbevents.js', 'config' => [ 'pixel_id' => FB_PIXEL_ID ] ]; } // Google Ads if ($preferences['marketing'] && !empty(GADS_CONVERSION_ID) && GADS_CONVERSION_ID !== 'AW-XXXXXXXXXX') { $scripts['gads'] = [ 'type' => 'conversion', 'src' => 'https://www.googletagmanager.com/gtag/js?id=' . GADS_CONVERSION_ID, 'config' => [ 'conversion_id' => GADS_CONVERSION_ID, 'conversion_label' => GADS_CONVERSION_LABEL ] ]; } // LinkedIn if ($preferences['marketing'] && !empty(LINKEDIN_PARTNER_ID)) { $scripts['linkedin'] = [ 'type' => 'insight', 'src' => 'https://snap.licdn.com/li.lms-analytics/insight.min.js', 'config' => [ 'partner_id' => LINKEDIN_PARTNER_ID ] ]; } return $scripts; } function sendEventToGA($data) { if (empty(GA_MEASUREMENT_ID) || GA_MEASUREMENT_ID === 'G-XXXXXXXXXX') { return false; } $postData = [ 'client_id' => $data['client_id'] ?? uniqid(), 'user_id' => $data['user_id'] ?? null, 'events' => [ [ 'name' => $data['event_name'], 'params' => $data['params'] ?? [] ] ] ]; $url = 'https://www.google-analytics.com/mp/collect'; $url .= '?measurement_id=' . GA_MEASUREMENT_ID; $url .= '&api_secret=' . GA_API_SECRET; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($postData), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 10 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode >= 200 && $httpCode < 300; } function sendEventToFB($data) { if (empty(FB_PIXEL_ID) || empty(FB_ACCESS_TOKEN)) { return false; } $postData = [ 'data' => [ [ 'event_name' => $data['event_name'], 'event_time' => time(), 'action_source' => 'website', 'user_data' => $data['user_data'] ?? [], 'custom_data' => $data['custom_data'] ?? [] ] ] ]; $url = 'https://graph.facebook.com/v18.0/' . FB_PIXEL_ID . '/events'; $url .= '?access_token=' . FB_ACCESS_TOKEN; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($postData), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 10 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode >= 200 && $httpCode < 300; } // ========================================== // STORAGE FUNCTIONS // ========================================== function storeLead($data) { $leadFile = __DIR__ . '/data/leads.json'; $dir = dirname($leadFile); if (!is_dir($dir)) { mkdir($dir, 0755, true); } $leads = []; if (file_exists($leadFile)) { $leads = json_decode(file_get_contents($leadFile), true) ?: []; } $lead = [ 'id' => uniqid('lead_', true), 'datum' => date('d.m.Y'), 'dienstleistung' => $data['service'] ?? 'Allgemein', 'status' => 'open', 'statusText' => 'Offen', 'description' => $data['description'] ?? '', 'name' => $data['name'] ?? '', 'contact' => $data['contact'] ?? '', 'organisation' => $data['organisation'] ?? '', 'timestamp' => date('c'), 'ip_address' => getClientIP() ]; array_unshift($leads, $lead); $leads = array_slice($leads, 0, 100); file_put_contents($leadFile, json_encode($leads, JSON_PRETTY_PRINT)); return $lead['id']; } function storeCookieConsent($data) { $consentFile = __DIR__ . '/data/cookie_consent.json'; $dir = dirname($consentFile); if (!is_dir($dir)) { mkdir($dir, 0755, true); } $consents = []; if (file_exists($consentFile)) { $consents = json_decode(file_get_contents($consentFile), true) ?: []; } $consent = [ 'id' => uniqid('consent_', true), 'timestamp' => date('c'), 'preferences' => $data['preferences'] ?? [], 'ip_address' => getClientIP(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '' ]; array_unshift($consents, $consent); $consents = array_slice($consents, 0, 1000); file_put_contents($consentFile, json_encode($consents, JSON_PRETTY_PRINT)); return $consent['id']; } function getCookieConsent() { $consentFile = __DIR__ . '/data/cookie_consent.json'; if (!file_exists($consentFile)) { return null; } $consents = json_decode(file_get_contents($consentFile), true) ?: []; return $consents[0] ?? null; } // ========================================== // REQUEST HANDLERS // ========================================== function handleContactForm($data) { $required = ['name', 'contact']; foreach ($required as $field) { if (empty($data[$field])) { sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400); } } $formData = [ 'name' => sanitizeInput($data['name']), 'organisation' => sanitizeInput($data['organisation'] ?? ''), 'contact' => sanitizeInput($data['contact']), 'service' => sanitizeInput($data['service'] ?? ''), 'budget' => sanitizeInput($data['budget'] ?? ''), 'description' => sanitizeInput($data['description'] ?? ''), 'timestamp' => date('c'), 'source' => 'contact_form', 'form_type' => 'offer', 'ip_address' => getClientIP() ]; // Store lead locally first $leadId = storeLead($formData); // Send to Offer webhook (contact form) $webhookResult = sendToWebhook($formData, WEBHOOK_URL); // Always return success to user, but include webhook info in debug mode $debugData = DEBUG_MODE ? [ 'lead_id' => $leadId, 'webhook_result' => $webhookResult, 'webhook_url' => WEBHOOK_URL, 'form_data' => $formData ] : null; if ($webhookResult['success']) { sendResponse(true, 'Formular erfolgreich gesendet', $debugData); } else { // Still return success to user but log the webhook error error_log('Webhook failed but form was stored locally: ' . json_encode($webhookResult)); sendResponse(true, 'Formular erfolgreich gesendet (Webhook-Fehler protokolliert)', $debugData); } } function handleLeadForm($data) { // Handle lead form submissions $leadId = storeLead($data); sendResponse(true, 'Lead gespeichert', ['lead_id' => $leadId]); } function handleCookieConsent($data) { $preferences = $data['preferences'] ?? []; $consentId = storeCookieConsent(['preferences' => $preferences]); // Load tracking scripts based on preferences $scripts = loadTrackingScripts($preferences); sendResponse(true, 'Cookie-Einstellungen gespeichert', [ 'consent_id' => $consentId, 'scripts' => $scripts ]); } function handleGetCookieConsent($data) { $consent = getCookieConsent(); if ($consent) { sendResponse(true, 'Cookie-Einstellungen gefunden', $consent); } else { sendResponse(false, 'Keine Cookie-Einstellungen gefunden', null, 404); } } function handleGetTrackingConfig($data) { $config = getTrackingConfig(); sendResponse(true, 'Tracking-Konfiguration', $config); } function handleChatMessage($data) { $message = sanitizeInput($data['message'] ?? ''); $sessionId = sanitizeInput($data['session_id'] ?? uniqid('chat_')); if (empty($message)) { sendResponse(false, 'Nachricht darf nicht leer sein', null, 400); } // Format data for n8n webhook - use 'chatInput' as the message field name // which is commonly expected by n8n AI chat workflows $chatData = [ 'type' => 'chat_message', 'session_id' => $sessionId, 'message' => $message, 'chatInput' => $message, // Alternative field name for n8n 'query' => $message, // Another common n8n field name 'timestamp' => date('c'), 'source' => 'website_chat', 'ip_address' => getClientIP(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '' ]; // Log the outgoing request for debugging error_log('KI Chat Request - URL: ' . KI_CHAT_WEBHOOK_URL . ' - Data: ' . json_encode($chatData)); // Send to KI Chat webhook $webhookResult = sendToWebhook($chatData, KI_CHAT_WEBHOOK_URL); // Log the response for debugging error_log('KI Chat Response: ' . json_encode($webhookResult)); $debugData = DEBUG_MODE ? [ 'session_id' => $sessionId, 'webhook_result' => $webhookResult, 'webhook_url' => KI_CHAT_WEBHOOK_URL ] : null; if ($webhookResult['success']) { // Try to parse response from webhook - handle various n8n response formats $response = json_decode($webhookResult['response'], true); $aiResponse = null; // Check common n8n response field names if ($response) { $aiResponse = $response['message'] ?? $response['output'] ?? $response['text'] ?? $response['response'] ?? $response['answer'] ?? $response['result'] ?? (is_array($response) && isset($response[0]['output']) ? $response[0]['output'] : null) ?? (is_array($response) && isset($response[0]['message']) ? $response[0]['message'] : null) ?? null; } // If still no response, use the raw response if it's a string if (!$aiResponse && is_string($webhookResult['response']) && !empty($webhookResult['response'])) { $aiResponse = $webhookResult['response']; } // Fallback message if (!$aiResponse) { $aiResponse = 'Vielen Dank für Ihre Nachricht. Ich melde mich so schnell wie möglich bei Ihnen.'; } sendResponse(true, 'Nachricht gesendet', array_merge($debugData ?? [], [ 'session_id' => $sessionId, 'ai_response' => $aiResponse, 'timestamp' => date('c') ])); } else { // Log detailed error for debugging error_log('Chat webhook failed - URL: ' . KI_CHAT_WEBHOOK_URL . ' - Result: ' . json_encode($webhookResult)); // Return a friendly message but still allow chat to work with fallback $fallbackResponse = 'Vielen Dank für Ihre Nachricht. Unser Team wird sich in Kürze bei Ihnen melden.'; sendResponse(true, 'Nachricht empfangen', array_merge($debugData ?? [], [ 'session_id' => $sessionId, 'ai_response' => $fallbackResponse, 'timestamp' => date('c'), 'fallback' => true ])); } } function handleTrackEvent($data) { $eventName = $data['event_name'] ?? ''; $eventData = $data['event_data'] ?? []; // Send to Google Analytics $gaSuccess = sendEventToGA([ 'event_name' => $eventName, 'params' => $eventData ]); // Send to Facebook $fbSuccess = sendEventToFB([ 'event_name' => $eventName, 'custom_data' => $eventData ]); sendResponse(true, 'Event gesendet', [ 'ga_success' => $gaSuccess, 'fb_success' => $fbSuccess ]); } // ========================================== // MAIN REQUEST HANDLER // ========================================== // Validate request method if ($_SERVER['REQUEST_METHOD'] !== 'POST') { sendResponse(false, 'Nur POST-Anfragen erlaubt', null, 405); } // Rate limiting $clientIP = getClientIP(); if (!checkRateLimit($clientIP)) { sendResponse(false, 'Zu viele Anfragen. Bitte später erneut versuchen.', null, 429); } // Get JSON input $jsonInput = file_get_contents('php://input'); $data = json_decode($jsonInput, true); if (!$data) { $data = $_POST; } if (!$data || empty($data)) { sendResponse(false, 'Keine Daten empfangen', null, 400); } $requestType = $data['type'] ?? 'contact'; // Debug endpoint to test webhook connectivity if ($requestType === 'test_webhook') { $testData = ['test' => true, 'timestamp' => date('c')]; $result = sendToWebhook($testData, KI_CHAT_WEBHOOK_URL); sendResponse(true, 'Webhook test completed', [ 'webhook_url' => KI_CHAT_WEBHOOK_URL, 'result' => $result, 'curl_available' => function_exists('curl_init'), 'allow_url_fopen' => ini_get('allow_url_fopen') ]); } try { switch ($requestType) { case 'contact': handleContactForm($data); break; case 'lead': handleLeadForm($data); break; case 'cookie_consent': handleCookieConsent($data); break; case 'get_cookie_consent': handleGetCookieConsent($data); break; case 'get_tracking_config': handleGetTrackingConfig($data); break; case 'track_event': handleTrackEvent($data); break; case 'chat': handleChatMessage($data); break; default: sendResponse(false, 'Ungültiger Anfragetyp', null, 400); } } catch (Exception $e) { error_log('API Error: ' . $e->getMessage()); error_log('Stack trace: ' . $e->getTraceAsString()); sendResponse(false, 'Interner Serverfehler', null, 500); } ?>