__('Error de seguridad. Por favor recarga la pagina.', 'roi-theme') ], 403); return; } // 2. Rate limiting basico (1 envio por IP cada 30 segundos) if (!$this->checkRateLimit()) { wp_send_json_error([ 'message' => __('Por favor espera un momento antes de enviar otro mensaje.', 'roi-theme') ], 429); return; } // 3. Sanitizar y validar inputs $formData = $this->sanitizeFormData($_POST); $validation = $this->validateFormData($formData); if (!$validation['valid']) { wp_send_json_error([ 'message' => $validation['message'], 'errors' => $validation['errors'] ], 422); return; } // 4. Obtener configuracion del componente (incluye webhook URL) $settings = $this->settingsRepository->getComponentSettings(self::COMPONENT_NAME); if (empty($settings)) { wp_send_json_error([ 'message' => __('Error de configuracion. Contacta al administrador.', 'roi-theme') ], 500); return; } $integration = $settings['integration'] ?? []; $webhookUrl = $integration['webhook_url'] ?? ''; $webhookMethod = $integration['webhook_method'] ?? 'POST'; $includePageUrl = $this->toBool($integration['include_page_url'] ?? true); $includeTimestamp = $this->toBool($integration['include_timestamp'] ?? true); if (empty($webhookUrl)) { // Si no hay webhook configurado, simular exito para UX // pero loguear warning para admin error_log('ROI Theme Contact Form: No webhook URL configured'); wp_send_json_success([ 'message' => $this->getSuccessMessage($settings) ]); return; } // 5. Preparar payload para webhook $payload = $this->preparePayload($formData, $includePageUrl, $includeTimestamp); // 6. Enviar a webhook $result = $this->sendToWebhook($webhookUrl, $webhookMethod, $payload); if ($result['success']) { wp_send_json_success([ 'message' => $this->getSuccessMessage($settings) ]); } else { error_log('ROI Theme Contact Form webhook error: ' . $result['error']); wp_send_json_error([ 'message' => $this->getErrorMessage($settings) ], 500); } } /** * Sanitizar datos del formulario */ private function sanitizeFormData(array $post): array { return [ 'fullName' => sanitize_text_field($post['fullName'] ?? ''), 'company' => sanitize_text_field($post['company'] ?? ''), 'whatsapp' => sanitize_text_field($post['whatsapp'] ?? ''), 'email' => sanitize_email($post['email'] ?? ''), 'message' => sanitize_textarea_field($post['message'] ?? ''), ]; } /** * Validar datos del formulario */ private function validateFormData(array $data): array { $errors = []; // Nombre requerido if (empty($data['fullName'])) { $errors['fullName'] = __('El nombre es obligatorio', 'roi-theme'); } // WhatsApp requerido if (empty($data['whatsapp'])) { $errors['whatsapp'] = __('El WhatsApp es obligatorio', 'roi-theme'); } // Email requerido y valido if (empty($data['email'])) { $errors['email'] = __('El email es obligatorio', 'roi-theme'); } elseif (!is_email($data['email'])) { $errors['email'] = __('Por favor ingresa un email valido', 'roi-theme'); } if (!empty($errors)) { return [ 'valid' => false, 'message' => __('Por favor corrige los errores del formulario', 'roi-theme'), 'errors' => $errors ]; } return ['valid' => true, 'message' => '', 'errors' => []]; } /** * Preparar payload para webhook */ private function preparePayload(array $formData, bool $includePageUrl, bool $includeTimestamp): array { $payload = [ 'fullName' => $formData['fullName'], 'company' => $formData['company'], 'whatsapp' => $formData['whatsapp'], 'email' => $formData['email'], 'message' => $formData['message'], ]; if ($includePageUrl) { $payload['pageUrl'] = sanitize_url($_POST['pageUrl'] ?? ''); $payload['pageTitle'] = sanitize_text_field($_POST['pageTitle'] ?? ''); } if ($includeTimestamp) { $payload['timestamp'] = current_time('c'); $payload['timezone'] = wp_timezone_string(); } // Metadata adicional util para el webhook $payload['source'] = 'contact-form'; $payload['siteName'] = get_bloginfo('name'); $payload['siteUrl'] = home_url(); return $payload; } /** * Enviar datos al webhook */ private function sendToWebhook(string $url, string $method, array $payload): array { $args = [ 'method' => strtoupper($method), 'timeout' => 30, 'redirection' => 5, 'httpversion' => '1.1', 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], ]; if ($method === 'POST') { $args['body'] = wp_json_encode($payload); } else { $url = add_query_arg($payload, $url); } $response = wp_remote_request($url, $args); if (is_wp_error($response)) { return [ 'success' => false, 'error' => $response->get_error_message() ]; } $statusCode = wp_remote_retrieve_response_code($response); // Considerar 2xx como exito if ($statusCode >= 200 && $statusCode < 300) { return ['success' => true, 'error' => '']; } return [ 'success' => false, 'error' => sprintf('HTTP %d: %s', $statusCode, wp_remote_retrieve_response_message($response)) ]; } /** * Rate limiting basico por IP */ private function checkRateLimit(): bool { $ip = $this->getClientIP(); $transientKey = 'roi_contact_form_' . md5($ip); $lastSubmit = get_transient($transientKey); if ($lastSubmit !== false) { return false; } set_transient($transientKey, time(), 30); return true; } /** * Obtener IP del cliente */ private function getClientIP(): string { $ip = ''; if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ip = sanitize_text_field($_SERVER['HTTP_CLIENT_IP']); } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = sanitize_text_field(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]); } elseif (!empty($_SERVER['REMOTE_ADDR'])) { $ip = sanitize_text_field($_SERVER['REMOTE_ADDR']); } return $ip; } /** * Obtener mensaje de exito desde configuracion */ private function getSuccessMessage(array $data): string { $messages = $data['messages'] ?? []; return $messages['success_message'] ?? __('¡Gracias por contactarnos! Te responderemos pronto.', 'roi-theme'); } /** * Obtener mensaje de error desde configuracion */ private function getErrorMessage(array $data): string { $messages = $data['messages'] ?? []; return $messages['error_message'] ?? __('Hubo un error al enviar el mensaje. Por favor intenta de nuevo.', 'roi-theme'); } /** * Convertir valor a boolean */ private function toBool($value): bool { return $value === true || $value === '1' || $value === 1; } }