Files
roi-theme/Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php
FrankZamora 4c807e1cf2 Backup pre-corrección namespaces: mejoras schemas y componentes
Cambios incluidos:
- Actualización de copy/textos en 7 schemas JSON
- Mejoras en AdminAjaxHandler con mapeos adicionales
- Refactorización de FormBuilders y Renderers
- Correcciones en dashboard admin JS
- Nuevo ContactFormRenderer funcional

NOTA: Este commit sirve como respaldo antes de corregir
inconsistencias de case en namespaces (API→Api, WordPress→Wordpress)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 17:59:01 -06:00

204 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace ROITheme\Public\Footer\Infrastructure\Api\WordPress;
use ROITheme\Shared\Domain\Contracts\ComponentSettingsRepositoryInterface;
/**
* NewsletterAjaxHandler - Procesa suscripciones al newsletter
*
* RESPONSABILIDAD: Recibir email y enviarlo al webhook configurado
*
* SEGURIDAD:
* - Verifica nonce
* - Webhook URL nunca se expone al cliente
* - Rate limiting basico
* - Sanitizacion de inputs
*
* @package ROITheme\Public\Footer\Infrastructure\Api\WordPress
*/
final class NewsletterAjaxHandler
{
private const NONCE_ACTION = 'roi_newsletter_nonce';
private const COMPONENT_NAME = 'footer';
public function __construct(
private ComponentSettingsRepositoryInterface $settingsRepository
) {}
/**
* Registrar hooks AJAX
*/
public function register(): void
{
add_action('wp_ajax_roi_newsletter_subscribe', [$this, 'handleSubscribe']);
add_action('wp_ajax_nopriv_roi_newsletter_subscribe', [$this, 'handleSubscribe']);
}
/**
* Procesar suscripcion
*/
public function handleSubscribe(): void
{
// 1. Verificar nonce
$nonce = sanitize_text_field($_POST['nonce'] ?? '');
if (!wp_verify_nonce($nonce, self::NONCE_ACTION)) {
wp_send_json_error([
'message' => __('Error de seguridad. Por favor recarga la pagina.', 'roi-theme')
], 403);
return;
}
// 2. Rate limiting (1 suscripcion por IP cada 60 segundos)
if (!$this->checkRateLimit()) {
wp_send_json_error([
'message' => __('Por favor espera un momento antes de intentar de nuevo.', 'roi-theme')
], 429);
return;
}
// 3. Validar y sanitizar campos
$email = sanitize_email($_POST['email'] ?? '');
$name = sanitize_text_field($_POST['name'] ?? '');
$whatsapp = sanitize_text_field($_POST['whatsapp'] ?? '');
if (empty($email) || !is_email($email)) {
wp_send_json_error([
'message' => __('Por favor ingresa un email valido.', 'roi-theme')
], 422);
return;
}
// 4. Obtener configuracion del componente
$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;
}
$newsletter = $settings['newsletter'] ?? [];
$webhookUrl = $newsletter['newsletter_webhook_url'] ?? '';
$successMsg = $newsletter['newsletter_success_message'] ?? __('Gracias por suscribirte!', 'roi-theme');
$errorMsg = $newsletter['newsletter_error_message'] ?? __('Error al suscribirse. Intenta de nuevo.', 'roi-theme');
if (empty($webhookUrl)) {
// Si no hay webhook, simular exito para UX pero loguear warning
error_log('ROI Theme Newsletter: No webhook URL configured');
wp_send_json_success([
'message' => $successMsg
]);
return;
}
// 5. Preparar payload
$payload = [
'email' => $email,
'name' => $name,
'whatsapp' => $whatsapp,
'source' => 'newsletter-footer',
'pageUrl' => sanitize_url($_POST['pageUrl'] ?? ''),
'pageTitle' => sanitize_text_field($_POST['pageTitle'] ?? ''),
'timestamp' => current_time('c'),
'timezone' => wp_timezone_string(),
'siteName' => get_bloginfo('name'),
'siteUrl' => home_url(),
];
// Debug: Log payload enviado
error_log('ROI Theme Newsletter: Enviando a webhook - ' . wp_json_encode($payload));
// 6. Enviar a webhook
$result = $this->sendToWebhook($webhookUrl, $payload);
if ($result['success']) {
wp_send_json_success([
'message' => $successMsg
]);
} else {
error_log('ROI Theme Newsletter webhook error: ' . $result['error']);
wp_send_json_error([
'message' => $errorMsg
], 500);
}
}
/**
* Enviar datos al webhook
*/
private function sendToWebhook(string $url, array $payload): array
{
error_log('ROI Theme Newsletter: Webhook URL - ' . $url);
$response = wp_remote_post($url, [
'timeout' => 30,
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'body' => wp_json_encode($payload),
]);
if (is_wp_error($response)) {
error_log('ROI Theme Newsletter: WP Error - ' . $response->get_error_message());
return [
'success' => false,
'error' => $response->get_error_message()
];
}
$statusCode = wp_remote_retrieve_response_code($response);
$responseBody = wp_remote_retrieve_body($response);
error_log('ROI Theme Newsletter: Response Code - ' . $statusCode);
error_log('ROI Theme Newsletter: Response Body - ' . $responseBody);
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 por IP
*/
private function checkRateLimit(): bool
{
$ip = $this->getClientIP();
$transientKey = 'roi_newsletter_' . md5($ip);
$lastSubmit = get_transient($transientKey);
if ($lastSubmit !== false) {
return false;
}
set_transient($transientKey, time(), 60);
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;
}
}