Migración completa a Clean Architecture con componentes funcionales
- Reorganización de estructura: Admin/, Public/, Shared/, Schemas/ - 12 componentes migrados: TopNotificationBar, Navbar, CtaLetsTalk, Hero, FeaturedImage, TableOfContents, CtaBoxSidebar, SocialShare, CtaPost, RelatedPost, ContactForm, Footer - Panel de administración con tabs Bootstrap 5 funcionales - Schemas JSON para configuración de componentes - Renderers dinámicos con CSSGeneratorService (cero CSS hardcodeado) - FormBuilders para UI admin con Design System consistente - Fix: Bootstrap JS cargado en header para tabs funcionales - Fix: buildTextInput maneja valores mixed (bool/string) - Eliminación de estructura legacy (src/, admin/, assets/css/componente-*) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
<?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 email
|
||||
$email = sanitize_email($_POST['email'] ?? '');
|
||||
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,
|
||||
'source' => 'newsletter-footer',
|
||||
'timestamp' => current_time('c'),
|
||||
'siteName' => get_bloginfo('name'),
|
||||
'siteUrl' => home_url(),
|
||||
];
|
||||
|
||||
// 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
|
||||
{
|
||||
$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)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $response->get_error_message()
|
||||
];
|
||||
}
|
||||
|
||||
$statusCode = wp_remote_retrieve_response_code($response);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
423
Public/Footer/Infrastructure/Ui/FooterRenderer.php
Normal file
423
Public/Footer/Infrastructure/Ui/FooterRenderer.php
Normal file
@@ -0,0 +1,423 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\Footer\Infrastructure\Ui;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Entities\Component;
|
||||
|
||||
/**
|
||||
* FooterRenderer - Renderiza el footer del sitio
|
||||
*
|
||||
* RESPONSABILIDAD: Generar HTML y CSS del footer con menus WP y newsletter
|
||||
*
|
||||
* SEGURIDAD:
|
||||
* - Webhook URL nunca se expone al cliente
|
||||
* - Escaping de todos los outputs
|
||||
* - Nonce para formulario newsletter
|
||||
*
|
||||
* @package ROITheme\Public\Footer\Infrastructure\Ui
|
||||
*/
|
||||
final class FooterRenderer implements RendererInterface
|
||||
{
|
||||
private const NONCE_ACTION = 'roi_newsletter_nonce';
|
||||
|
||||
public function __construct(
|
||||
private CSSGeneratorInterface $cssGenerator
|
||||
) {}
|
||||
|
||||
public function supports(string $componentType): bool
|
||||
{
|
||||
return $componentType === 'footer';
|
||||
}
|
||||
|
||||
public function render(Component $component): string
|
||||
{
|
||||
$data = $component->getData();
|
||||
|
||||
// Validar visibilidad
|
||||
$visibility = $data['visibility'] ?? [];
|
||||
if (!($visibility['is_enabled'] ?? true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Verificar visibilidad responsive
|
||||
$showDesktop = $visibility['show_on_desktop'] ?? true;
|
||||
$showMobile = $visibility['show_on_mobile'] ?? true;
|
||||
|
||||
if (!$showDesktop && !$showMobile) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Generar CSS
|
||||
$css = $this->generateCSS($data, $showDesktop, $showMobile);
|
||||
|
||||
// Generar HTML
|
||||
$html = $this->generateHTML($data);
|
||||
|
||||
// Generar JavaScript
|
||||
$js = $this->generateJS($data);
|
||||
|
||||
return $css . $html . $js;
|
||||
}
|
||||
|
||||
private function generateCSS(array $data, bool $showDesktop, bool $showMobile): string
|
||||
{
|
||||
$colors = $data['colors'] ?? [];
|
||||
$spacing = $data['spacing'] ?? [];
|
||||
$effects = $data['visual_effects'] ?? [];
|
||||
|
||||
// Valores con fallbacks
|
||||
$bgColor = $colors['bg_color'] ?? '#212529';
|
||||
$textColor = $colors['text_color'] ?? '#ffffff';
|
||||
$titleColor = $colors['title_color'] ?? '#ffffff';
|
||||
$linkColor = $colors['link_color'] ?? '#ffffff';
|
||||
$linkHoverColor = $colors['link_hover_color'] ?? '#FF8600';
|
||||
$inputBgColor = $colors['input_bg_color'] ?? '#ffffff';
|
||||
$inputTextColor = $colors['input_text_color'] ?? '#212529';
|
||||
$inputBorderColor = $colors['input_border_color'] ?? '#dee2e6';
|
||||
$buttonBgColor = $colors['button_bg_color'] ?? '#0d6efd';
|
||||
$buttonTextColor = $colors['button_text_color'] ?? '#ffffff';
|
||||
$buttonHoverBg = $colors['button_hover_bg'] ?? '#0b5ed7';
|
||||
$borderTopColor = $colors['border_top_color'] ?? 'rgba(255, 255, 255, 0.2)';
|
||||
|
||||
$paddingY = $spacing['padding_y'] ?? '3rem';
|
||||
$marginTop = $spacing['margin_top'] ?? '0';
|
||||
$widgetTitleMb = $spacing['widget_title_margin_bottom'] ?? '1rem';
|
||||
$linkMb = $spacing['link_margin_bottom'] ?? '0.5rem';
|
||||
$copyrightPy = $spacing['copyright_padding_y'] ?? '1.5rem';
|
||||
|
||||
$inputRadius = $effects['input_border_radius'] ?? '6px';
|
||||
$buttonRadius = $effects['button_border_radius'] ?? '6px';
|
||||
$transition = $effects['transition_duration'] ?? '0.3s';
|
||||
|
||||
$cssRules = [];
|
||||
|
||||
// Footer principal
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer', [
|
||||
'background-color' => $bgColor,
|
||||
'color' => $textColor,
|
||||
'padding-top' => $paddingY,
|
||||
'padding-bottom' => $paddingY,
|
||||
'margin-top' => $marginTop,
|
||||
]);
|
||||
|
||||
// Grid custom para 3+3+3+4 = 13 columnas
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-grid', [
|
||||
'display' => 'grid',
|
||||
'grid-template-columns' => 'repeat(4, 1fr)',
|
||||
'gap' => '2rem',
|
||||
]);
|
||||
|
||||
// En desktop: distribucion 3+3+3+4
|
||||
$cssRules[] = "@media (min-width: 768px) {
|
||||
.roi-footer .footer-grid {
|
||||
grid-template-columns: 23% 23% 23% 31%;
|
||||
}
|
||||
}";
|
||||
|
||||
// En mobile: 2 columnas
|
||||
$cssRules[] = "@media (max-width: 767px) {
|
||||
.roi-footer .footer-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.roi-footer .footer-widget-newsletter {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}";
|
||||
|
||||
// Titulos de widgets
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .widget-title', [
|
||||
'color' => $titleColor,
|
||||
'font-size' => '1.25rem',
|
||||
'font-weight' => '500',
|
||||
'margin-bottom' => $widgetTitleMb,
|
||||
]);
|
||||
|
||||
// Links de navegacion
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-nav', [
|
||||
'list-style' => 'none',
|
||||
'padding' => '0',
|
||||
'margin' => '0',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-nav li', [
|
||||
'margin-bottom' => $linkMb,
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-nav a', [
|
||||
'color' => $linkColor,
|
||||
'text-decoration' => 'none',
|
||||
'transition' => "color {$transition}",
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-nav a:hover', [
|
||||
'color' => $linkHoverColor,
|
||||
]);
|
||||
|
||||
// Newsletter description
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-description', [
|
||||
'color' => $textColor,
|
||||
'margin-bottom' => '1rem',
|
||||
'opacity' => '0.9',
|
||||
]);
|
||||
|
||||
// Input newsletter
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-input', [
|
||||
'width' => '100%',
|
||||
'padding' => '0.75rem 1rem',
|
||||
'background-color' => $inputBgColor,
|
||||
'color' => $inputTextColor,
|
||||
'border' => "1px solid {$inputBorderColor}",
|
||||
'border-radius' => $inputRadius,
|
||||
'margin-bottom' => '0.75rem',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-input:focus', [
|
||||
'outline' => 'none',
|
||||
'border-color' => $buttonBgColor,
|
||||
'box-shadow' => "0 0 0 0.2rem rgba(13, 110, 253, 0.25)",
|
||||
]);
|
||||
|
||||
// Boton newsletter
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-btn', [
|
||||
'width' => '100%',
|
||||
'padding' => '0.75rem 1.5rem',
|
||||
'background-color' => $buttonBgColor,
|
||||
'color' => $buttonTextColor,
|
||||
'border' => 'none',
|
||||
'border-radius' => $buttonRadius,
|
||||
'font-weight' => '500',
|
||||
'cursor' => 'pointer',
|
||||
'transition' => "background-color {$transition}",
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-btn:hover', [
|
||||
'background-color' => $buttonHoverBg,
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-btn:disabled', [
|
||||
'opacity' => '0.7',
|
||||
'cursor' => 'not-allowed',
|
||||
]);
|
||||
|
||||
// Mensaje newsletter
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-message', [
|
||||
'margin-top' => '0.75rem',
|
||||
'padding' => '0.5rem',
|
||||
'border-radius' => '4px',
|
||||
'font-size' => '0.875rem',
|
||||
'display' => 'none',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-message.success', [
|
||||
'background-color' => '#d1e7dd',
|
||||
'color' => '#0f5132',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-message.error', [
|
||||
'background-color' => '#f8d7da',
|
||||
'color' => '#842029',
|
||||
]);
|
||||
|
||||
// Footer bottom (copyright)
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-bottom', [
|
||||
'border-top' => "1px solid {$borderTopColor}",
|
||||
'padding-top' => $copyrightPy,
|
||||
'margin-top' => '2rem',
|
||||
'text-align' => 'center',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .copyright-text', [
|
||||
'margin' => '0',
|
||||
'opacity' => '0.9',
|
||||
]);
|
||||
|
||||
// Responsive visibility
|
||||
if (!$showDesktop) {
|
||||
$cssRules[] = "@media (min-width: 992px) { .roi-footer { display: none !important; } }";
|
||||
}
|
||||
if (!$showMobile) {
|
||||
$cssRules[] = "@media (max-width: 991px) { .roi-footer { display: none !important; } }";
|
||||
}
|
||||
|
||||
return '<style>' . implode("\n", $cssRules) . '</style>';
|
||||
}
|
||||
|
||||
private function generateHTML(array $data): string
|
||||
{
|
||||
$widget1 = $data['widget_1'] ?? [];
|
||||
$widget2 = $data['widget_2'] ?? [];
|
||||
$widget3 = $data['widget_3'] ?? [];
|
||||
$newsletter = $data['newsletter'] ?? [];
|
||||
$footerBottom = $data['footer_bottom'] ?? [];
|
||||
|
||||
$widget1Visible = $this->toBool($widget1['widget_1_visible'] ?? true);
|
||||
$widget2Visible = $this->toBool($widget2['widget_2_visible'] ?? true);
|
||||
$widget3Visible = $this->toBool($widget3['widget_3_visible'] ?? true);
|
||||
$newsletterVisible = $this->toBool($newsletter['newsletter_visible'] ?? true);
|
||||
|
||||
$widget1Title = esc_html($widget1['widget_1_title'] ?? 'Recursos');
|
||||
$widget2Title = esc_html($widget2['widget_2_title'] ?? 'Soporte');
|
||||
$widget3Title = esc_html($widget3['widget_3_title'] ?? 'Empresa');
|
||||
|
||||
$newsletterTitle = esc_html($newsletter['newsletter_title'] ?? 'Suscribete al Newsletter');
|
||||
$newsletterDesc = esc_html($newsletter['newsletter_description'] ?? 'Recibe las ultimas actualizaciones.');
|
||||
$newsletterPlaceholder = esc_attr($newsletter['newsletter_placeholder'] ?? 'Email');
|
||||
$newsletterBtnText = esc_html($newsletter['newsletter_button_text'] ?? 'Suscribirse');
|
||||
|
||||
$copyrightText = esc_html($footerBottom['copyright_text'] ?? date('Y') . ' Todos los derechos reservados.');
|
||||
|
||||
$nonce = wp_create_nonce(self::NONCE_ACTION);
|
||||
$ajaxUrl = admin_url('admin-ajax.php');
|
||||
|
||||
$html = '<footer class="roi-footer">';
|
||||
$html .= '<div class="container">';
|
||||
$html .= '<div class="footer-grid">';
|
||||
|
||||
// Widget 1
|
||||
if ($widget1Visible) {
|
||||
$html .= '<div class="footer-widget footer-widget-menu">';
|
||||
$html .= '<h5 class="widget-title">' . $widget1Title . '</h5>';
|
||||
$html .= $this->renderMenu('footer_menu_1');
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
// Widget 2
|
||||
if ($widget2Visible) {
|
||||
$html .= '<div class="footer-widget footer-widget-menu">';
|
||||
$html .= '<h5 class="widget-title">' . $widget2Title . '</h5>';
|
||||
$html .= $this->renderMenu('footer_menu_2');
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
// Widget 3
|
||||
if ($widget3Visible) {
|
||||
$html .= '<div class="footer-widget footer-widget-menu">';
|
||||
$html .= '<h5 class="widget-title">' . $widget3Title . '</h5>';
|
||||
$html .= $this->renderMenu('footer_menu_3');
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
// Widget Newsletter
|
||||
if ($newsletterVisible) {
|
||||
$html .= '<div class="footer-widget footer-widget-newsletter">';
|
||||
$html .= '<h5 class="widget-title">' . $newsletterTitle . '</h5>';
|
||||
$html .= '<p class="newsletter-description">' . $newsletterDesc . '</p>';
|
||||
$html .= '<form id="roi-newsletter-form" class="newsletter-form">';
|
||||
$html .= '<input type="hidden" name="action" value="roi_newsletter_subscribe">';
|
||||
$html .= '<input type="hidden" name="nonce" value="' . esc_attr($nonce) . '">';
|
||||
$html .= '<input type="email" name="email" class="newsletter-input" placeholder="' . $newsletterPlaceholder . '" required>';
|
||||
$html .= '<button type="submit" class="newsletter-btn">' . $newsletterBtnText . '</button>';
|
||||
$html .= '<div class="newsletter-message"></div>';
|
||||
$html .= '</form>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</div>'; // .footer-grid
|
||||
|
||||
// Footer bottom
|
||||
$html .= '<div class="footer-bottom">';
|
||||
$html .= '<p class="copyright-text">© ' . $copyrightText . '</p>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '</div>'; // .container
|
||||
$html .= '</footer>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function renderMenu(string $menuLocation): string
|
||||
{
|
||||
if (!has_nav_menu($menuLocation)) {
|
||||
return '<p class="text-muted">Menu no asignado</p>';
|
||||
}
|
||||
|
||||
return wp_nav_menu([
|
||||
'theme_location' => $menuLocation,
|
||||
'container' => false,
|
||||
'menu_class' => 'footer-nav',
|
||||
'fallback_cb' => false,
|
||||
'echo' => false,
|
||||
'depth' => 1,
|
||||
]) ?: '';
|
||||
}
|
||||
|
||||
private function generateJS(array $data): string
|
||||
{
|
||||
$newsletter = $data['newsletter'] ?? [];
|
||||
$successMsg = esc_js($newsletter['newsletter_success_message'] ?? 'Gracias por suscribirte!');
|
||||
$errorMsg = esc_js($newsletter['newsletter_error_message'] ?? 'Error al suscribirse. Intenta de nuevo.');
|
||||
|
||||
$ajaxUrl = admin_url('admin-ajax.php');
|
||||
|
||||
$js = <<<JS
|
||||
<script>
|
||||
(function() {
|
||||
const form = document.getElementById('roi-newsletter-form');
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const btn = form.querySelector('.newsletter-btn');
|
||||
const msgDiv = form.querySelector('.newsletter-message');
|
||||
const emailInput = form.querySelector('input[name="email"]');
|
||||
const originalText = btn.textContent;
|
||||
|
||||
// Reset message
|
||||
msgDiv.style.display = 'none';
|
||||
msgDiv.className = 'newsletter-message';
|
||||
|
||||
// Validate email
|
||||
if (!emailInput.value || !emailInput.validity.valid) {
|
||||
msgDiv.textContent = 'Por favor ingresa un email valido';
|
||||
msgDiv.classList.add('error');
|
||||
msgDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Enviando...';
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
|
||||
const response = await fetch('{$ajaxUrl}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
msgDiv.textContent = '{$successMsg}';
|
||||
msgDiv.classList.add('success');
|
||||
emailInput.value = '';
|
||||
} else {
|
||||
msgDiv.textContent = result.data?.message || '{$errorMsg}';
|
||||
msgDiv.classList.add('error');
|
||||
}
|
||||
} catch (error) {
|
||||
msgDiv.textContent = '{$errorMsg}';
|
||||
msgDiv.classList.add('error');
|
||||
}
|
||||
|
||||
msgDiv.style.display = 'block';
|
||||
btn.disabled = false;
|
||||
btn.textContent = originalText;
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
JS;
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
private function toBool($value): bool
|
||||
{
|
||||
return $value === true || $value === '1' || $value === 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user