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>
This commit is contained in:
@@ -65,7 +65,7 @@ final class ContactFormFormBuilder
|
||||
$html .= ' Seccion de contacto antes del footer con envio a webhook';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="contact_form">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="contact-form">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
|
||||
@@ -63,7 +63,7 @@ final class CtaBoxSidebarFormBuilder
|
||||
$html .= ' Caja de llamada a la accion en el sidebar';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="cta_box_sidebar">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="cta-box-sidebar">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
|
||||
@@ -57,7 +57,7 @@ final class CtaPostFormBuilder
|
||||
$html .= ' CTA promocional debajo del contenido del post';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="cta_post">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="cta-post">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
@@ -252,8 +252,8 @@ final class CtaPostFormBuilder
|
||||
$buttonBg = $this->renderer->getFieldValue($componentId, 'colors', 'button_bg_color', '#ffffff');
|
||||
$html .= $this->buildColorPicker('ctaPostButtonBg', 'Fondo', $buttonBg);
|
||||
|
||||
$buttonText = $this->renderer->getFieldValue($componentId, 'colors', 'button_text_color', '#212529');
|
||||
$html .= $this->buildColorPicker('ctaPostButtonText', 'Texto', $buttonText);
|
||||
$buttonTextColor = $this->renderer->getFieldValue($componentId, 'colors', 'button_text_color', '#212529');
|
||||
$html .= $this->buildColorPicker('ctaPostButtonTextColor', 'Texto', $buttonTextColor);
|
||||
|
||||
$html .= ' </div>';
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ final class FeaturedImageFormBuilder
|
||||
$html .= ' Personaliza la imagen destacada de los posts';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="featured_image">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="featured-image">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
|
||||
@@ -192,14 +192,14 @@ final class FooterFormBuilder
|
||||
$description = $this->renderer->getFieldValue($componentId, 'newsletter', 'newsletter_description', 'Recibe las ultimas actualizaciones.');
|
||||
$html .= $this->buildTextarea('footerNewsletterDescription', 'Descripcion', 'bi-text-paragraph', $description);
|
||||
|
||||
$placeholder = $this->renderer->getFieldValue($componentId, 'newsletter', 'newsletter_placeholder', 'Email');
|
||||
$placeholder = $this->renderer->getFieldValue($componentId, 'newsletter', 'newsletter_email_placeholder', 'Email');
|
||||
$html .= $this->buildTextInput('footerNewsletterPlaceholder', 'Placeholder email', 'bi-input-cursor', $placeholder);
|
||||
|
||||
$buttonText = $this->renderer->getFieldValue($componentId, 'newsletter', 'newsletter_button_text', 'Suscribirse');
|
||||
$html .= $this->buildTextInput('footerNewsletterButtonText', 'Texto boton', 'bi-cursor', $buttonText);
|
||||
|
||||
$webhookUrl = $this->renderer->getFieldValue($componentId, 'newsletter', 'newsletter_webhook_url', '');
|
||||
$html .= $this->buildPasswordInput('footerNewsletterWebhookUrl', 'URL del Webhook', 'bi-link-45deg', $webhookUrl);
|
||||
$html .= $this->buildTextarea('footerNewsletterWebhookUrl', 'URL del Webhook', 'bi-link-45deg', $webhookUrl);
|
||||
|
||||
$successMsg = $this->renderer->getFieldValue($componentId, 'newsletter', 'newsletter_success_message', 'Gracias por suscribirte!');
|
||||
$html .= $this->buildTextInput('footerNewsletterSuccessMessage', 'Mensaje exito', 'bi-check-circle', $successMsg);
|
||||
|
||||
@@ -591,6 +591,109 @@ final class AdminAjaxHandler
|
||||
'contactFormButtonPadding' => ['group' => 'visual_effects', 'attribute' => 'button_padding'],
|
||||
'contactFormTransitionDuration' => ['group' => 'visual_effects', 'attribute' => 'transition_duration'],
|
||||
'contactFormTextareaRows' => ['group' => 'visual_effects', 'attribute' => 'textarea_rows'],
|
||||
|
||||
// =====================================================
|
||||
// CTA BOX SIDEBAR
|
||||
// =====================================================
|
||||
|
||||
// Visibility
|
||||
'ctaEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||
'ctaShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||
'ctaShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||
'ctaShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
||||
|
||||
// Content
|
||||
'ctaTitle' => ['group' => 'content', 'attribute' => 'title'],
|
||||
'ctaDescription' => ['group' => 'content', 'attribute' => 'description'],
|
||||
'ctaButtonText' => ['group' => 'content', 'attribute' => 'button_text'],
|
||||
'ctaButtonIcon' => ['group' => 'content', 'attribute' => 'button_icon'],
|
||||
'ctaButtonAction' => ['group' => 'content', 'attribute' => 'button_action'],
|
||||
'ctaButtonLink' => ['group' => 'content', 'attribute' => 'button_link'],
|
||||
|
||||
// Behavior
|
||||
'ctaTextAlign' => ['group' => 'behavior', 'attribute' => 'text_align'],
|
||||
|
||||
// Typography
|
||||
'ctaTitleFontSize' => ['group' => 'typography', 'attribute' => 'title_font_size'],
|
||||
'ctaTitleFontWeight' => ['group' => 'typography', 'attribute' => 'title_font_weight'],
|
||||
'ctaDescFontSize' => ['group' => 'typography', 'attribute' => 'description_font_size'],
|
||||
'ctaButtonFontSize' => ['group' => 'typography', 'attribute' => 'button_font_size'],
|
||||
'ctaButtonFontWeight' => ['group' => 'typography', 'attribute' => 'button_font_weight'],
|
||||
|
||||
// Colors
|
||||
'ctaBackgroundColor' => ['group' => 'colors', 'attribute' => 'background_color'],
|
||||
'ctaTitleColor' => ['group' => 'colors', 'attribute' => 'title_color'],
|
||||
'ctaDescriptionColor' => ['group' => 'colors', 'attribute' => 'description_color'],
|
||||
'ctaButtonBgColor' => ['group' => 'colors', 'attribute' => 'button_background_color'],
|
||||
'ctaButtonTextColor' => ['group' => 'colors', 'attribute' => 'button_text_color'],
|
||||
'ctaButtonHoverBg' => ['group' => 'colors', 'attribute' => 'button_hover_background'],
|
||||
'ctaButtonHoverText' => ['group' => 'colors', 'attribute' => 'button_hover_text_color'],
|
||||
|
||||
// Spacing
|
||||
'ctaContainerPadding' => ['group' => 'spacing', 'attribute' => 'container_padding'],
|
||||
'ctaTitleMarginBottom' => ['group' => 'spacing', 'attribute' => 'title_margin_bottom'],
|
||||
'ctaDescMarginBottom' => ['group' => 'spacing', 'attribute' => 'description_margin_bottom'],
|
||||
'ctaButtonPadding' => ['group' => 'spacing', 'attribute' => 'button_padding'],
|
||||
'ctaIconMarginRight' => ['group' => 'spacing', 'attribute' => 'icon_margin_right'],
|
||||
|
||||
// Visual Effects
|
||||
'ctaBorderRadius' => ['group' => 'visual_effects', 'attribute' => 'border_radius'],
|
||||
'ctaButtonBorderRadius' => ['group' => 'visual_effects', 'attribute' => 'button_border_radius'],
|
||||
'ctaBoxShadow' => ['group' => 'visual_effects', 'attribute' => 'box_shadow'],
|
||||
'ctaTransitionDuration' => ['group' => 'visual_effects', 'attribute' => 'transition_duration'],
|
||||
|
||||
// =====================================================
|
||||
// FOOTER
|
||||
// =====================================================
|
||||
|
||||
// Visibility
|
||||
'footerEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||
'footerShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||
'footerShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||
|
||||
// Widget 1
|
||||
'footerWidget1Visible' => ['group' => 'widget_1', 'attribute' => 'widget_1_visible'],
|
||||
'footerWidget1Title' => ['group' => 'widget_1', 'attribute' => 'widget_1_title'],
|
||||
|
||||
// Widget 2
|
||||
'footerWidget2Visible' => ['group' => 'widget_2', 'attribute' => 'widget_2_visible'],
|
||||
'footerWidget2Title' => ['group' => 'widget_2', 'attribute' => 'widget_2_title'],
|
||||
|
||||
// Widget 3
|
||||
'footerWidget3Visible' => ['group' => 'widget_3', 'attribute' => 'widget_3_visible'],
|
||||
'footerWidget3Title' => ['group' => 'widget_3', 'attribute' => 'widget_3_title'],
|
||||
|
||||
// Newsletter
|
||||
'footerNewsletterVisible' => ['group' => 'newsletter', 'attribute' => 'newsletter_visible'],
|
||||
'footerNewsletterTitle' => ['group' => 'newsletter', 'attribute' => 'newsletter_title'],
|
||||
'footerNewsletterDescription' => ['group' => 'newsletter', 'attribute' => 'newsletter_description'],
|
||||
'footerNewsletterPlaceholder' => ['group' => 'newsletter', 'attribute' => 'newsletter_email_placeholder'],
|
||||
'footerNewsletterButtonText' => ['group' => 'newsletter', 'attribute' => 'newsletter_button_text'],
|
||||
'footerNewsletterWebhookUrl' => ['group' => 'newsletter', 'attribute' => 'newsletter_webhook_url'],
|
||||
'footerNewsletterSuccessMessage' => ['group' => 'newsletter', 'attribute' => 'newsletter_success_message'],
|
||||
'footerNewsletterErrorMessage' => ['group' => 'newsletter', 'attribute' => 'newsletter_error_message'],
|
||||
|
||||
// Footer Bottom
|
||||
'footerCopyrightText' => ['group' => 'footer_bottom', 'attribute' => 'copyright_text'],
|
||||
|
||||
// Colors
|
||||
'footerBgColor' => ['group' => 'colors', 'attribute' => 'bg_color'],
|
||||
'footerTextColor' => ['group' => 'colors', 'attribute' => 'text_color'],
|
||||
'footerTitleColor' => ['group' => 'colors', 'attribute' => 'title_color'],
|
||||
'footerLinkColor' => ['group' => 'colors', 'attribute' => 'link_color'],
|
||||
'footerLinkHoverColor' => ['group' => 'colors', 'attribute' => 'link_hover_color'],
|
||||
'footerButtonBgColor' => ['group' => 'colors', 'attribute' => 'button_bg_color'],
|
||||
'footerButtonTextColor' => ['group' => 'colors', 'attribute' => 'button_text_color'],
|
||||
'footerButtonHoverBg' => ['group' => 'colors', 'attribute' => 'button_hover_bg'],
|
||||
|
||||
// Spacing
|
||||
'footerPaddingY' => ['group' => 'spacing', 'attribute' => 'padding_y'],
|
||||
'footerMarginTop' => ['group' => 'spacing', 'attribute' => 'margin_top'],
|
||||
|
||||
// Visual Effects
|
||||
'footerInputBorderRadius' => ['group' => 'visual_effects', 'attribute' => 'input_border_radius'],
|
||||
'footerButtonBorderRadius' => ['group' => 'visual_effects', 'attribute' => 'button_border_radius'],
|
||||
'footerTransitionDuration' => ['group' => 'visual_effects', 'attribute' => 'transition_duration'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,62 @@
|
||||
});
|
||||
|
||||
/**
|
||||
* Inicializa el sistema de tabs
|
||||
* Inicializa el sistema de tabs con persistencia en URL
|
||||
*/
|
||||
function initializeTabs() {
|
||||
const tabs = document.querySelectorAll('.nav-tab');
|
||||
const tabButtons = document.querySelectorAll('[data-bs-toggle="tab"]');
|
||||
|
||||
tabs.forEach(function(tab) {
|
||||
tab.addEventListener('click', function(e) {
|
||||
// Prevenir comportamiento por defecto si es necesario
|
||||
// (En este caso dejamos que funcione la navegación normal)
|
||||
// Leer parametro admin-tab de la URL al cargar
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const activeTabParam = urlParams.get('admin-tab');
|
||||
|
||||
if (activeTabParam) {
|
||||
// Buscar el boton del tab correspondiente
|
||||
const targetButton = document.querySelector('[data-bs-target="#' + activeTabParam + 'Tab"]');
|
||||
if (targetButton) {
|
||||
// Activar el tab usando Bootstrap API
|
||||
const tab = new bootstrap.Tab(targetButton);
|
||||
tab.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Escuchar cambios de tab para actualizar URL
|
||||
tabButtons.forEach(function(tabButton) {
|
||||
tabButton.addEventListener('shown.bs.tab', function(e) {
|
||||
// Obtener el ID del componente desde data-bs-target
|
||||
const target = e.target.getAttribute('data-bs-target');
|
||||
const componentId = target.replace('#', '').replace('Tab', '');
|
||||
|
||||
// Actualizar URL sin recargar pagina
|
||||
updateUrlWithTab(componentId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza la URL con el parametro admin-tab sin recargar la pagina
|
||||
*
|
||||
* @param {string} tabId ID del tab activo
|
||||
*/
|
||||
function updateUrlWithTab(tabId) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('admin-tab', tabId);
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el ID del tab activo actualmente
|
||||
*
|
||||
* @returns {string|null} ID del componente activo o null
|
||||
*/
|
||||
function getActiveTabId() {
|
||||
const activeTab = document.querySelector('.tab-pane.active');
|
||||
if (activeTab) {
|
||||
return activeTab.id.replace('Tab', '');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializa validación de formularios
|
||||
*/
|
||||
@@ -227,7 +270,17 @@
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotice('success', data.data.message || 'Valores restaurados correctamente.');
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
// Recargar preservando el tab activo
|
||||
setTimeout(() => {
|
||||
const activeTabId = getActiveTabId();
|
||||
if (activeTabId) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('admin-tab', activeTabId);
|
||||
window.location.href = url.toString();
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}, 1500);
|
||||
} else {
|
||||
showNotice('error', data.data.message || 'Error al restaurar los valores.');
|
||||
}
|
||||
|
||||
@@ -13,7 +13,19 @@ if (!defined('ABSPATH')) {
|
||||
}
|
||||
|
||||
$components = $this->getComponents();
|
||||
$firstComponentId = array_key_first($components);
|
||||
|
||||
// Determinar tab activo: desde URL o primer componente
|
||||
$activeComponentId = array_key_first($components);
|
||||
|
||||
// Leer parametro admin-tab de la URL con sanitizacion
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Solo lectura de parametro para UI
|
||||
if (isset($_GET['admin-tab'])) {
|
||||
$requestedTab = sanitize_text_field(wp_unslash($_GET['admin-tab']));
|
||||
// Validar que el componente exista
|
||||
if (array_key_exists($requestedTab, $components)) {
|
||||
$activeComponentId = $requestedTab;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="wrap roi-admin-panel">
|
||||
@@ -21,13 +33,13 @@ $firstComponentId = array_key_first($components);
|
||||
<ul class="nav nav-tabs nav-tabs-admin mb-0" role="tablist">
|
||||
<?php foreach ($components as $componentId => $component): ?>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link <?php echo $componentId === $firstComponentId ? 'active' : ''; ?>"
|
||||
<button class="nav-link <?php echo $componentId === $activeComponentId ? 'active' : ''; ?>"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#<?php echo esc_attr($componentId); ?>Tab"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="<?php echo esc_attr($componentId); ?>Tab"
|
||||
aria-selected="<?php echo $componentId === $firstComponentId ? 'true' : 'false'; ?>">
|
||||
aria-selected="<?php echo $componentId === $activeComponentId ? 'true' : 'false'; ?>">
|
||||
<i class="bi <?php echo esc_attr($component['icon']); ?> me-1"></i>
|
||||
<?php echo esc_html($component['label']); ?>
|
||||
</button>
|
||||
@@ -38,11 +50,11 @@ $firstComponentId = array_key_first($components);
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content mt-3">
|
||||
<?php foreach ($components as $componentId => $component):
|
||||
$isFirst = ($componentId === $firstComponentId);
|
||||
$isActive = ($componentId === $activeComponentId);
|
||||
$componentSettings = $this->getComponentSettings($componentId);
|
||||
?>
|
||||
<!-- Tab: <?php echo esc_html($component['label']); ?> -->
|
||||
<div class="tab-pane fade <?php echo $isFirst ? 'show active' : ''; ?>"
|
||||
<div class="tab-pane fade <?php echo $isActive ? 'show active' : ''; ?>"
|
||||
id="<?php echo esc_attr($componentId); ?>Tab"
|
||||
role="tabpanel">
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ final class RelatedPostFormBuilder
|
||||
$html .= ' Seccion de posts relacionados con grid de cards';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="related_post">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="related-post">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
|
||||
@@ -63,7 +63,7 @@ final class SocialShareFormBuilder
|
||||
$html .= ' Botones para compartir contenido en redes sociales';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="social_share">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="social-share">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
|
||||
@@ -63,7 +63,7 @@ final class TableOfContentsFormBuilder
|
||||
$html .= ' Navegacion automatica con ScrollSpy';
|
||||
$html .= ' </p>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="table_of_contents">';
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="table-of-contents">';
|
||||
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
||||
$html .= ' Restaurar valores por defecto';
|
||||
$html .= ' </button>';
|
||||
|
||||
@@ -50,6 +50,21 @@ final class ContactFormRenderer implements RendererInterface
|
||||
return sprintf("<style>%s</style>\n%s\n<script>%s</script>", $css, $html, $js);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderiza el modal de contacto para el boton Let's Talk
|
||||
* Usa la misma configuracion y webhook que el formulario de seccion
|
||||
*/
|
||||
public function renderModal(Component $component): string
|
||||
{
|
||||
$data = $component->getData();
|
||||
|
||||
$css = $this->generateModalCSS($data);
|
||||
$html = $this->buildModalHTML($data);
|
||||
$js = $this->buildModalJS($data);
|
||||
|
||||
return sprintf("<style>%s</style>\n%s\n<script>%s</script>", $css, $html, $js);
|
||||
}
|
||||
|
||||
public function supports(string $componentType): bool
|
||||
{
|
||||
return $componentType === 'contact-form';
|
||||
@@ -454,6 +469,307 @@ final class ContactFormRenderer implements RendererInterface
|
||||
});
|
||||
});
|
||||
})();
|
||||
JS;
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar CSS para el modal
|
||||
*/
|
||||
private function generateModalCSS(array $data): string
|
||||
{
|
||||
$colors = $data['colors'] ?? [];
|
||||
$effects = $data['visual_effects'] ?? [];
|
||||
|
||||
$cssRules = [];
|
||||
|
||||
// Modal header con gradiente del tema
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .modal-header', [
|
||||
'background' => 'linear-gradient(135deg, #0E2337 0%, #1e3a5f 100%)',
|
||||
'border-bottom' => 'none',
|
||||
'padding' => '1.5rem',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .modal-title', [
|
||||
'color' => '#ffffff',
|
||||
'font-weight' => '600',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-close', [
|
||||
'filter' => 'brightness(0) invert(1)',
|
||||
]);
|
||||
|
||||
// Modal body
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .modal-body', [
|
||||
'padding' => '2rem',
|
||||
]);
|
||||
|
||||
// Form inputs
|
||||
$inputBorderColor = $colors['input_border_color'] ?? '#dee2e6';
|
||||
$inputFocusBorder = $colors['input_focus_border'] ?? '#FF8600';
|
||||
$inputBorderRadius = $effects['input_border_radius'] ?? '6px';
|
||||
$transitionDuration = $effects['transition_duration'] ?? '0.3s';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .form-control', [
|
||||
'border-color' => $inputBorderColor,
|
||||
'border-radius' => $inputBorderRadius,
|
||||
'transition' => "all {$transitionDuration} ease",
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .form-control:focus', [
|
||||
'border-color' => $inputFocusBorder,
|
||||
'box-shadow' => '0 0 0 0.2rem rgba(255, 134, 0, 0.25)',
|
||||
'outline' => 'none',
|
||||
]);
|
||||
|
||||
// Submit button
|
||||
$buttonBgColor = $colors['button_bg_color'] ?? '#FF8600';
|
||||
$buttonTextColor = $colors['button_text_color'] ?? '#ffffff';
|
||||
$buttonHoverBg = $colors['button_hover_bg'] ?? '#e67a00';
|
||||
$buttonBorderRadius = $effects['button_border_radius'] ?? '6px';
|
||||
$buttonPadding = $effects['button_padding'] ?? '0.75rem 2rem';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-modal-submit', [
|
||||
'background-color' => $buttonBgColor,
|
||||
'color' => $buttonTextColor,
|
||||
'font-weight' => '600',
|
||||
'padding' => $buttonPadding,
|
||||
'border' => 'none',
|
||||
'border-radius' => $buttonBorderRadius,
|
||||
'transition' => "all {$transitionDuration} ease",
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-modal-submit:hover', [
|
||||
'background-color' => $buttonHoverBg,
|
||||
'color' => $buttonTextColor,
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-modal-submit:disabled', [
|
||||
'opacity' => '0.7',
|
||||
'cursor' => 'not-allowed',
|
||||
]);
|
||||
|
||||
// Success/Error messages
|
||||
$successBgColor = $colors['success_bg_color'] ?? '#d1e7dd';
|
||||
$successTextColor = $colors['success_text_color'] ?? '#0f5132';
|
||||
$errorBgColor = $colors['error_bg_color'] ?? '#f8d7da';
|
||||
$errorTextColor = $colors['error_text_color'] ?? '#842029';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .alert-success', [
|
||||
'background-color' => $successBgColor,
|
||||
'color' => $successTextColor,
|
||||
'border-color' => $successBgColor,
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('#contactModal .alert-danger', [
|
||||
'background-color' => $errorBgColor,
|
||||
'color' => $errorTextColor,
|
||||
'border-color' => $errorBgColor,
|
||||
]);
|
||||
|
||||
return implode("\n", $cssRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar HTML del modal
|
||||
*/
|
||||
private function buildModalHTML(array $data): string
|
||||
{
|
||||
$content = $data['content'] ?? [];
|
||||
$formLabels = $data['form_labels'] ?? [];
|
||||
$effects = $data['visual_effects'] ?? [];
|
||||
|
||||
// Content
|
||||
$sectionTitle = $content['section_title'] ?? '¿Tienes alguna pregunta?';
|
||||
$submitText = $content['submit_button_text'] ?? 'Enviar Mensaje';
|
||||
$submitIcon = $content['submit_button_icon'] ?? 'bi-send-fill';
|
||||
|
||||
// Form labels/placeholders
|
||||
$fullnamePlaceholder = $formLabels['fullname_placeholder'] ?? 'Nombre completo *';
|
||||
$companyPlaceholder = $formLabels['company_placeholder'] ?? 'Empresa';
|
||||
$whatsappPlaceholder = $formLabels['whatsapp_placeholder'] ?? 'WhatsApp *';
|
||||
$emailPlaceholder = $formLabels['email_placeholder'] ?? 'Correo electrónico *';
|
||||
$messagePlaceholder = $formLabels['message_placeholder'] ?? '¿En qué podemos ayudarte?';
|
||||
|
||||
$textareaRows = $effects['textarea_rows'] ?? '4';
|
||||
|
||||
// Nonce for AJAX security
|
||||
$nonce = wp_create_nonce('roi_contact_form_nonce');
|
||||
|
||||
$html = '<div class="modal fade" id="contactModal" tabindex="-1" aria-labelledby="contactModalLabel" aria-hidden="true">';
|
||||
$html .= '<div class="modal-dialog modal-dialog-centered modal-lg">';
|
||||
$html .= '<div class="modal-content">';
|
||||
|
||||
// Modal Header
|
||||
$html .= '<div class="modal-header">';
|
||||
$html .= '<h5 class="modal-title" id="contactModalLabel">';
|
||||
$html .= '<i class="bi bi-chat-dots-fill me-2" style="color: #FF8600;"></i>';
|
||||
$html .= esc_html($sectionTitle);
|
||||
$html .= '</h5>';
|
||||
$html .= '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Modal Body
|
||||
$html .= '<div class="modal-body">';
|
||||
$html .= sprintf('<form id="roiContactModalForm" data-nonce="%s">', esc_attr($nonce));
|
||||
$html .= '<div class="row g-3">';
|
||||
|
||||
// Full name field
|
||||
$html .= '<div class="col-md-6">';
|
||||
$html .= sprintf(
|
||||
'<input type="text" class="form-control" id="roiModalFullName" name="fullName" placeholder="%s" required>',
|
||||
esc_attr($fullnamePlaceholder)
|
||||
);
|
||||
$html .= '</div>';
|
||||
|
||||
// Company field
|
||||
$html .= '<div class="col-md-6">';
|
||||
$html .= sprintf(
|
||||
'<input type="text" class="form-control" id="roiModalCompany" name="company" placeholder="%s">',
|
||||
esc_attr($companyPlaceholder)
|
||||
);
|
||||
$html .= '</div>';
|
||||
|
||||
// WhatsApp field
|
||||
$html .= '<div class="col-md-6">';
|
||||
$html .= sprintf(
|
||||
'<input type="tel" class="form-control" id="roiModalWhatsapp" name="whatsapp" placeholder="%s" required>',
|
||||
esc_attr($whatsappPlaceholder)
|
||||
);
|
||||
$html .= '</div>';
|
||||
|
||||
// Email field
|
||||
$html .= '<div class="col-md-6">';
|
||||
$html .= sprintf(
|
||||
'<input type="email" class="form-control" id="roiModalEmail" name="email" placeholder="%s" required>',
|
||||
esc_attr($emailPlaceholder)
|
||||
);
|
||||
$html .= '</div>';
|
||||
|
||||
// Message field
|
||||
$html .= '<div class="col-12">';
|
||||
$html .= sprintf(
|
||||
'<textarea class="form-control" id="roiModalMessage" name="message" rows="%s" placeholder="%s"></textarea>',
|
||||
esc_attr($textareaRows),
|
||||
esc_attr($messagePlaceholder)
|
||||
);
|
||||
$html .= '</div>';
|
||||
|
||||
// Submit button
|
||||
$html .= '<div class="col-12">';
|
||||
$html .= '<button type="submit" class="btn btn-modal-submit w-100">';
|
||||
$html .= sprintf('<i class="%s me-2"></i>', esc_attr($submitIcon));
|
||||
$html .= esc_html($submitText);
|
||||
$html .= '</button>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Message container
|
||||
$html .= '<div id="roiContactModalMessage" class="col-12 mt-2 alert" style="display: none;"></div>';
|
||||
|
||||
$html .= '</div>'; // .row g-3
|
||||
$html .= '</form>';
|
||||
$html .= '</div>'; // .modal-body
|
||||
|
||||
$html .= '</div>'; // .modal-content
|
||||
$html .= '</div>'; // .modal-dialog
|
||||
$html .= '</div>'; // .modal
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar JS para el modal
|
||||
*/
|
||||
private function buildModalJS(array $data): string
|
||||
{
|
||||
$messages = $data['messages'] ?? [];
|
||||
$content = $data['content'] ?? [];
|
||||
|
||||
$successMessage = $messages['success_message'] ?? '¡Gracias por contactarnos! Te responderemos pronto.';
|
||||
$errorMessage = $messages['error_message'] ?? 'Hubo un error al enviar el mensaje. Por favor intenta de nuevo.';
|
||||
$sendingMessage = $messages['sending_message'] ?? 'Enviando...';
|
||||
|
||||
// AJAX URL for WordPress
|
||||
$ajaxUrl = admin_url('admin-ajax.php');
|
||||
|
||||
$js = <<<JS
|
||||
(function() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('roiContactModalForm');
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const messageDiv = document.getElementById('roiContactModalMessage');
|
||||
const originalBtnHtml = submitBtn.innerHTML;
|
||||
const nonce = form.dataset.nonce;
|
||||
|
||||
// Disable button and show sending state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>' + '{$sendingMessage}';
|
||||
messageDiv.style.display = 'none';
|
||||
|
||||
// Collect form data
|
||||
const formData = new FormData(form);
|
||||
formData.append('action', 'roi_contact_form_submit');
|
||||
formData.append('nonce', nonce);
|
||||
formData.append('pageUrl', window.location.href);
|
||||
formData.append('pageTitle', document.title);
|
||||
|
||||
try {
|
||||
const response = await fetch('{$ajaxUrl}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
messageDiv.className = 'col-12 mt-2 alert alert-success';
|
||||
messageDiv.textContent = '{$successMessage}';
|
||||
messageDiv.style.display = 'block';
|
||||
form.reset();
|
||||
|
||||
// Cerrar modal despues de 2 segundos en exito
|
||||
setTimeout(function() {
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('contactModal'));
|
||||
if (modal) {
|
||||
modal.hide();
|
||||
}
|
||||
messageDiv.style.display = 'none';
|
||||
}, 2000);
|
||||
} else {
|
||||
messageDiv.className = 'col-12 mt-2 alert alert-danger';
|
||||
messageDiv.textContent = result.data?.message || '{$errorMessage}';
|
||||
messageDiv.style.display = 'block';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Contact modal form error:', error);
|
||||
messageDiv.className = 'col-12 mt-2 alert alert-danger';
|
||||
messageDiv.textContent = '{$errorMessage}';
|
||||
messageDiv.style.display = 'block';
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalBtnHtml;
|
||||
}
|
||||
});
|
||||
|
||||
// Limpiar formulario cuando se cierra el modal
|
||||
const contactModal = document.getElementById('contactModal');
|
||||
if (contactModal) {
|
||||
contactModal.addEventListener('hidden.bs.modal', function() {
|
||||
form.reset();
|
||||
const messageDiv = document.getElementById('roiContactModalMessage');
|
||||
if (messageDiv) {
|
||||
messageDiv.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
JS;
|
||||
|
||||
return $js;
|
||||
|
||||
@@ -194,17 +194,27 @@ final class CtaBoxSidebarRenderer implements RendererInterface
|
||||
esc_html($description)
|
||||
);
|
||||
|
||||
// Button
|
||||
// Button/Link
|
||||
$iconHtml = !empty($buttonIcon)
|
||||
? sprintf('<i class="%s"></i>', esc_attr($buttonIcon))
|
||||
: '';
|
||||
|
||||
$html .= sprintf(
|
||||
'<button class="btn btn-cta-box" %s>%s%s</button>',
|
||||
$buttonAttributes,
|
||||
$iconHtml,
|
||||
esc_html($buttonText)
|
||||
);
|
||||
// Use <a> for link action, <button> for modal/scroll
|
||||
if ($buttonAction === 'link') {
|
||||
$html .= sprintf(
|
||||
'<a href="%s" class="btn btn-cta-box">%s%s</a>',
|
||||
esc_url($buttonLink),
|
||||
$iconHtml,
|
||||
esc_html($buttonText)
|
||||
);
|
||||
} else {
|
||||
$html .= sprintf(
|
||||
'<button class="btn btn-cta-box" %s>%s%s</button>',
|
||||
$buttonAttributes,
|
||||
$iconHtml,
|
||||
esc_html($buttonText)
|
||||
);
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
|
||||
@@ -75,20 +75,31 @@ final class CtaPostRenderer implements RendererInterface
|
||||
{
|
||||
$colors = $data['colors'] ?? [];
|
||||
$effects = $data['visual_effects'] ?? [];
|
||||
$spacing = $data['spacing'] ?? [];
|
||||
$visibility = $data['visibility'] ?? [];
|
||||
|
||||
$cssRules = [];
|
||||
|
||||
// Container values
|
||||
$gradientStart = $colors['gradient_start'] ?? '#FF8600';
|
||||
$gradientEnd = $colors['gradient_end'] ?? '#FFB800';
|
||||
$gradientAngle = $effects['gradient_angle'] ?? '135deg';
|
||||
$borderRadius = $effects['border_radius'] ?? '12px';
|
||||
$boxShadow = $effects['box_shadow'] ?? '0 8px 24px rgba(255, 133, 0, 0.3)';
|
||||
$containerPadding = $spacing['container_padding'] ?? '2rem';
|
||||
|
||||
// Button values
|
||||
$buttonBgColor = $colors['button_bg_color'] ?? '#FF8600';
|
||||
$buttonTextColor = $colors['button_text_color'] ?? '#ffffff';
|
||||
$buttonHoverBgColor = $colors['button_hover_bg_color'] ?? '#e67a00';
|
||||
$buttonHoverBgColor = $colors['button_hover_bg'] ?? '#e67a00';
|
||||
$buttonBorderRadius = $effects['button_border_radius'] ?? '8px';
|
||||
|
||||
// Container - gradient background
|
||||
// Container - gradient background with box-shadow and border-radius
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-post-container', [
|
||||
'background' => "linear-gradient({$gradientAngle}, {$gradientStart} 0%, {$gradientEnd} 100%)",
|
||||
'box-shadow' => $boxShadow,
|
||||
'border-radius' => $borderRadius,
|
||||
'padding' => $containerPadding,
|
||||
]);
|
||||
|
||||
// Button styles (matching template .cta-button) - Using !important to override Bootstrap btn-light
|
||||
@@ -98,8 +109,8 @@ final class CtaPostRenderer implements RendererInterface
|
||||
font-weight: 600;
|
||||
padding: 0.75rem 2rem;
|
||||
border: none !important;
|
||||
border-radius: 8px;
|
||||
transition: 0.3s;
|
||||
border-radius: {$buttonBorderRadius};
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}";
|
||||
@@ -149,7 +160,7 @@ final class CtaPostRenderer implements RendererInterface
|
||||
$buttonUrl = $content['button_url'] ?? '#';
|
||||
$buttonIcon = $content['button_icon'] ?? 'bi-arrow-right';
|
||||
|
||||
$html = '<div class="my-5 p-4 rounded cta-post-container">';
|
||||
$html = '<div class="my-5 cta-post-container">';
|
||||
$html .= ' <div class="row align-items-center">';
|
||||
|
||||
// Left column - Content
|
||||
@@ -169,7 +180,7 @@ final class CtaPostRenderer implements RendererInterface
|
||||
// Right column - Button
|
||||
$html .= ' <div class="col-md-4 text-md-end mt-3 mt-md-0">';
|
||||
$html .= sprintf(
|
||||
' <a href="%s" class="btn btn-light btn-lg cta-button">%s',
|
||||
' <a href="%s" class="cta-button">%s',
|
||||
esc_url($buttonUrl),
|
||||
esc_html($buttonText)
|
||||
);
|
||||
|
||||
@@ -58,8 +58,11 @@ final class NewsletterAjaxHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Validar email
|
||||
// 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')
|
||||
@@ -94,12 +97,20 @@ final class NewsletterAjaxHandler
|
||||
// 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);
|
||||
|
||||
@@ -120,6 +131,8 @@ final class NewsletterAjaxHandler
|
||||
*/
|
||||
private function sendToWebhook(string $url, array $payload): array
|
||||
{
|
||||
error_log('ROI Theme Newsletter: Webhook URL - ' . $url);
|
||||
|
||||
$response = wp_remote_post($url, [
|
||||
'timeout' => 30,
|
||||
'headers' => [
|
||||
@@ -130,6 +143,7 @@ final class NewsletterAjaxHandler
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
error_log('ROI Theme Newsletter: WP Error - ' . $response->get_error_message());
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $response->get_error_message()
|
||||
@@ -137,6 +151,10 @@ final class NewsletterAjaxHandler
|
||||
}
|
||||
|
||||
$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' => ''];
|
||||
|
||||
@@ -156,6 +156,11 @@ final class FooterRenderer implements RendererInterface
|
||||
'color' => $linkHoverColor,
|
||||
]);
|
||||
|
||||
// Widget 1B spacing
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-widget-1b', [
|
||||
'margin-top' => '1.5rem',
|
||||
]);
|
||||
|
||||
// Newsletter description
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-description', [
|
||||
'color' => $textColor,
|
||||
@@ -248,6 +253,7 @@ final class FooterRenderer implements RendererInterface
|
||||
private function generateHTML(array $data): string
|
||||
{
|
||||
$widget1 = $data['widget_1'] ?? [];
|
||||
$widget1b = $data['widget_1b'] ?? [];
|
||||
$widget2 = $data['widget_2'] ?? [];
|
||||
$widget3 = $data['widget_3'] ?? [];
|
||||
$newsletter = $data['newsletter'] ?? [];
|
||||
@@ -259,12 +265,15 @@ final class FooterRenderer implements RendererInterface
|
||||
$newsletterVisible = $this->toBool($newsletter['newsletter_visible'] ?? true);
|
||||
|
||||
$widget1Title = esc_html($widget1['widget_1_title'] ?? 'Recursos');
|
||||
$widget1bTitle = esc_html($widget1b['widget_1b_title'] ?? 'Bases de datos');
|
||||
$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');
|
||||
$newsletterNamePlaceholder = esc_attr($newsletter['newsletter_name_placeholder'] ?? 'Nombre');
|
||||
$newsletterEmailPlaceholder = esc_attr($newsletter['newsletter_email_placeholder'] ?? 'Email');
|
||||
$newsletterWhatsappPlaceholder = esc_attr($newsletter['newsletter_whatsapp_placeholder'] ?? 'WhatsApp');
|
||||
$newsletterBtnText = esc_html($newsletter['newsletter_button_text'] ?? 'Suscribirse');
|
||||
|
||||
$copyrightText = esc_html($footerBottom['copyright_text'] ?? date('Y') . ' Todos los derechos reservados.');
|
||||
@@ -276,12 +285,25 @@ final class FooterRenderer implements RendererInterface
|
||||
$html .= '<div class="container">';
|
||||
$html .= '<div class="footer-grid">';
|
||||
|
||||
// Widget 1
|
||||
// Columna 1: Widget 1 + Widget 1B
|
||||
if ($widget1Visible) {
|
||||
$html .= '<div class="footer-column footer-column-1">';
|
||||
|
||||
// Widget 1
|
||||
$html .= '<div class="footer-widget footer-widget-menu">';
|
||||
$html .= '<h5 class="widget-title">' . $widget1Title . '</h5>';
|
||||
$html .= $this->renderMenu('footer_menu_1');
|
||||
$html .= '</div>';
|
||||
|
||||
// Widget 1B - Solo si tiene menu asignado
|
||||
if (has_nav_menu('footer_menu_4')) {
|
||||
$html .= '<div class="footer-widget footer-widget-menu footer-widget-1b">';
|
||||
$html .= '<h5 class="widget-title">' . $widget1bTitle . '</h5>';
|
||||
$html .= $this->renderMenu('footer_menu_4');
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
// Widget 2
|
||||
@@ -308,7 +330,9 @@ final class FooterRenderer implements RendererInterface
|
||||
$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 .= '<input type="text" name="name" class="newsletter-input" placeholder="' . $newsletterNamePlaceholder . '">';
|
||||
$html .= '<input type="email" name="email" class="newsletter-input" placeholder="' . $newsletterEmailPlaceholder . '" required>';
|
||||
$html .= '<input type="tel" name="whatsapp" class="newsletter-input" placeholder="' . $newsletterWhatsappPlaceholder . '">';
|
||||
$html .= '<button type="submit" class="newsletter-btn">' . $newsletterBtnText . '</button>';
|
||||
$html .= '<div class="newsletter-message"></div>';
|
||||
$html .= '</form>';
|
||||
@@ -384,6 +408,8 @@ final class FooterRenderer implements RendererInterface
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
formData.append('pageUrl', window.location.href);
|
||||
formData.append('pageTitle', document.title);
|
||||
|
||||
const response = await fetch('{$ajaxUrl}', {
|
||||
method: 'POST',
|
||||
|
||||
16
_temp_check.php
Normal file
16
_temp_check.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once 'D:/_Desarrollo/02AnalisisDePreciosUnitarios/dev.analisisdepreciosunitarios.com/wp-load.php';
|
||||
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'roi_theme_component_settings';
|
||||
|
||||
echo "=== CTA-BOX-SIDEBAR CONTENT (ACTUAL) ===\n";
|
||||
$rows = $wpdb->get_results(
|
||||
"SELECT attribute_name, attribute_value FROM {$table}
|
||||
WHERE component_name = 'cta-box-sidebar'
|
||||
AND group_name = 'content'
|
||||
ORDER BY attribute_name"
|
||||
);
|
||||
foreach ($rows as $row) {
|
||||
echo "{$row->attribute_name}: [{$row->attribute_value}]\n";
|
||||
}
|
||||
@@ -191,6 +191,7 @@ add_action('after_setup_theme', function() {
|
||||
'footer_menu_1' => __('Footer Menu 1 (Widget 1)', 'roi-theme'),
|
||||
'footer_menu_2' => __('Footer Menu 2 (Widget 2)', 'roi-theme'),
|
||||
'footer_menu_3' => __('Footer Menu 3 (Widget 3)', 'roi-theme'),
|
||||
'footer_menu_4' => __('Footer Menu 4 (Widget 1B - Bases de datos)', 'roi-theme'),
|
||||
]);
|
||||
|
||||
// TODO: Agregar más configuraciones según sea necesario
|
||||
@@ -218,7 +219,51 @@ add_action('after_setup_theme', function() {
|
||||
// });
|
||||
|
||||
// =============================================================================
|
||||
// 5. INFORMACIÓN DE DEBUG (Solo en desarrollo)
|
||||
// 5. RENDERIZAR MODAL DE CONTACTO (Let's Talk)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Renderizar modal de contacto en el footer
|
||||
* Usa la misma configuracion que contact-form (mismo webhook)
|
||||
*/
|
||||
add_action('wp_footer', function() use ($container) {
|
||||
if ($container === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Obtener configuracion del contact-form
|
||||
$repository = $container->getComponentSettingsRepository();
|
||||
$settings = $repository->getComponentSettings('contact-form');
|
||||
|
||||
if (empty($settings)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Crear Component entity
|
||||
$componentName = new \ROITheme\Shared\Domain\ValueObjects\ComponentName('contact-form');
|
||||
$component = new \ROITheme\Shared\Domain\Entities\Component(
|
||||
name: $componentName,
|
||||
configuration: \ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration::fromArray($settings),
|
||||
visibility: \ROITheme\Shared\Domain\ValueObjects\ComponentVisibility::allDevices()
|
||||
);
|
||||
|
||||
// Crear renderer y renderizar modal
|
||||
$cssGenerator = new \ROITheme\Shared\Infrastructure\Services\CSSGeneratorService();
|
||||
$renderer = new \ROITheme\Public\ContactForm\Infrastructure\Ui\ContactFormRenderer($cssGenerator);
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $renderer->renderModal($component);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log('ROI Theme: Failed to render contact modal: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}, 99); // Prioridad alta para que se renderice al final del footer
|
||||
|
||||
// =============================================================================
|
||||
// 5.1. INFORMACIÓN DE DEBUG (Solo en desarrollo)
|
||||
// =============================================================================
|
||||
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"section_title": {
|
||||
"type": "text",
|
||||
"label": "Titulo de seccion",
|
||||
"default": "¿Tienes alguna pregunta?",
|
||||
"default": "¿Necesitas Ayuda con tu Presupuesto?",
|
||||
"editable": true
|
||||
},
|
||||
"section_description": {
|
||||
@@ -88,7 +88,7 @@
|
||||
"phone_value": {
|
||||
"type": "text",
|
||||
"label": "Numero telefono",
|
||||
"default": "+52 55 1234 5678",
|
||||
"default": "",
|
||||
"editable": true
|
||||
},
|
||||
"email_label": {
|
||||
@@ -100,7 +100,7 @@
|
||||
"email_value": {
|
||||
"type": "text",
|
||||
"label": "Direccion email",
|
||||
"default": "contacto@apumexico.com",
|
||||
"default": "",
|
||||
"editable": true
|
||||
},
|
||||
"location_label": {
|
||||
@@ -148,7 +148,7 @@
|
||||
"message_placeholder": {
|
||||
"type": "text",
|
||||
"label": "Placeholder mensaje",
|
||||
"default": "¿En qué podemos ayudarte?",
|
||||
"default": "¿En qué tema de precios unitarios podemos ayudarte?",
|
||||
"editable": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,25 +45,25 @@
|
||||
"title": {
|
||||
"type": "text",
|
||||
"label": "Título",
|
||||
"default": "¿Listo para potenciar tus proyectos?",
|
||||
"default": "¿Sigues Armando Presupuestos Desde Cero?",
|
||||
"editable": true
|
||||
},
|
||||
"description": {
|
||||
"type": "textarea",
|
||||
"label": "Descripción",
|
||||
"default": "Accede a nuestra biblioteca completa de APUs y herramientas profesionales.",
|
||||
"default": "Ahorra cientos de horas con la biblioteca de precios unitarios más grande de México.",
|
||||
"editable": true
|
||||
},
|
||||
"button_text": {
|
||||
"type": "text",
|
||||
"label": "Texto del botón",
|
||||
"default": "Solicitar Demo",
|
||||
"default": "Convertirme en V.I.P.",
|
||||
"editable": true
|
||||
},
|
||||
"button_icon": {
|
||||
"type": "text",
|
||||
"label": "Icono del botón",
|
||||
"default": "bi bi-calendar-check",
|
||||
"default": "bi bi-star-fill",
|
||||
"editable": true,
|
||||
"description": "Clase Bootstrap Icons (ej: bi bi-calendar-check)"
|
||||
},
|
||||
@@ -176,7 +176,7 @@
|
||||
"container_padding": {
|
||||
"type": "text",
|
||||
"label": "Padding contenedor",
|
||||
"default": "24px",
|
||||
"default": "15px",
|
||||
"editable": true
|
||||
},
|
||||
"title_margin_bottom": {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"button_text": {
|
||||
"type": "text",
|
||||
"label": "Texto del botón",
|
||||
"default": "Let's Talk",
|
||||
"default": "Contáctanos",
|
||||
"editable": true,
|
||||
"required": true,
|
||||
"maxlength": 30,
|
||||
@@ -62,7 +62,7 @@
|
||||
"icon_class": {
|
||||
"type": "text",
|
||||
"label": "Clase del ícono Bootstrap",
|
||||
"default": "bi-lightning-charge-fill",
|
||||
"default": "bi-chat-dots-fill",
|
||||
"editable": true,
|
||||
"required": true,
|
||||
"placeholder": "Ej: bi-lightning-charge-fill, bi-chat-dots",
|
||||
|
||||
@@ -45,31 +45,31 @@
|
||||
"title": {
|
||||
"type": "text",
|
||||
"label": "Titulo",
|
||||
"default": "Accede a 200,000+ Analisis de Precios Unitarios",
|
||||
"default": "Tu Punto de Partida: +200,000 Precios Unitarios",
|
||||
"editable": true
|
||||
},
|
||||
"description": {
|
||||
"type": "textarea",
|
||||
"label": "Descripcion",
|
||||
"default": "Consulta estructuras completas, insumos y dosificaciones de los APUs mas utilizados en construccion en Mexico.",
|
||||
"default": "Olvídate de la página en blanco. Consulta estructuras, rendimientos y componentes listos para adaptar a tu proyecto.",
|
||||
"editable": true
|
||||
},
|
||||
"button_text": {
|
||||
"type": "text",
|
||||
"label": "Texto del boton",
|
||||
"default": "Ver Catalogo Completo",
|
||||
"default": "Convertirme en V.I.P.",
|
||||
"editable": true
|
||||
},
|
||||
"button_url": {
|
||||
"type": "url",
|
||||
"label": "URL del boton",
|
||||
"default": "/catalogo",
|
||||
"default": "/suscripcion-vip",
|
||||
"editable": true
|
||||
},
|
||||
"button_icon": {
|
||||
"type": "text",
|
||||
"label": "Icono del boton",
|
||||
"default": "bi-arrow-right",
|
||||
"default": "bi-star-fill",
|
||||
"editable": true,
|
||||
"description": "Clase de Bootstrap Icons (ej: bi-arrow-right)"
|
||||
}
|
||||
@@ -141,22 +141,23 @@
|
||||
"button_bg_color": {
|
||||
"type": "color",
|
||||
"label": "Fondo boton",
|
||||
"default": "#ffffff",
|
||||
"default": "#FF8600",
|
||||
"editable": true,
|
||||
"description": "btn-light de Bootstrap"
|
||||
"description": "Color naranja primario"
|
||||
},
|
||||
"button_text_color": {
|
||||
"type": "color",
|
||||
"label": "Texto boton",
|
||||
"default": "#212529",
|
||||
"default": "#ffffff",
|
||||
"editable": true,
|
||||
"description": "Color oscuro para contraste"
|
||||
"description": "Texto blanco para contraste"
|
||||
},
|
||||
"button_hover_bg": {
|
||||
"type": "color",
|
||||
"label": "Fondo boton hover",
|
||||
"default": "#f8f9fa",
|
||||
"editable": true
|
||||
"default": "#e67a00",
|
||||
"editable": true,
|
||||
"description": "Naranja oscuro en hover"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -180,9 +181,9 @@
|
||||
"container_padding": {
|
||||
"type": "text",
|
||||
"label": "Padding interno",
|
||||
"default": "1.5rem",
|
||||
"default": "2rem",
|
||||
"editable": true,
|
||||
"description": "p-4 de Bootstrap"
|
||||
"description": "Padding interno del contenedor"
|
||||
},
|
||||
"title_margin_bottom": {
|
||||
"type": "text",
|
||||
@@ -207,9 +208,9 @@
|
||||
"border_radius": {
|
||||
"type": "text",
|
||||
"label": "Radio de borde",
|
||||
"default": "0.375rem",
|
||||
"default": "12px",
|
||||
"editable": true,
|
||||
"description": "rounded de Bootstrap"
|
||||
"description": "Radio de esquinas del contenedor"
|
||||
},
|
||||
"gradient_angle": {
|
||||
"type": "text",
|
||||
@@ -239,8 +240,9 @@
|
||||
"box_shadow": {
|
||||
"type": "text",
|
||||
"label": "Sombra contenedor",
|
||||
"default": "none",
|
||||
"editable": true
|
||||
"default": "0 8px 24px rgba(255, 133, 0, 0.3)",
|
||||
"editable": true,
|
||||
"description": "Sombra naranja sutil"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"widget_1_title": {
|
||||
"type": "text",
|
||||
"label": "Titulo Widget 1",
|
||||
"default": "Recursos",
|
||||
"default": "Membresías V.I.P.",
|
||||
"editable": true,
|
||||
"description": "El contenido se gestiona desde Apariencia > Menus > Footer Menu 1"
|
||||
},
|
||||
@@ -49,6 +49,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget_1b": {
|
||||
"label": "Widget 1B (Menu secundario columna 1)",
|
||||
"priority": 25,
|
||||
"fields": {
|
||||
"widget_1b_title": {
|
||||
"type": "text",
|
||||
"label": "Titulo Widget 1B",
|
||||
"default": "Bases de datos",
|
||||
"editable": true,
|
||||
"description": "El contenido se gestiona desde Apariencia > Menus > Footer Menu 4. Solo se muestra si tiene menu asignado."
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget_2": {
|
||||
"label": "Widget 2 (Menu)",
|
||||
"priority": 30,
|
||||
@@ -56,7 +69,7 @@
|
||||
"widget_2_title": {
|
||||
"type": "text",
|
||||
"label": "Titulo Widget 2",
|
||||
"default": "Soporte",
|
||||
"default": "Cursos Gratuitos",
|
||||
"editable": true,
|
||||
"description": "El contenido se gestiona desde Apariencia > Menus > Footer Menu 2"
|
||||
},
|
||||
@@ -75,7 +88,7 @@
|
||||
"widget_3_title": {
|
||||
"type": "text",
|
||||
"label": "Titulo Widget 3",
|
||||
"default": "Empresa",
|
||||
"default": "Cursos de Pago",
|
||||
"editable": true,
|
||||
"description": "El contenido se gestiona desde Apariencia > Menus > Footer Menu 3"
|
||||
},
|
||||
@@ -109,12 +122,24 @@
|
||||
"default": "Recibe las ultimas actualizaciones de APUs.",
|
||||
"editable": true
|
||||
},
|
||||
"newsletter_placeholder": {
|
||||
"newsletter_name_placeholder": {
|
||||
"type": "text",
|
||||
"label": "Placeholder nombre",
|
||||
"default": "Nombre",
|
||||
"editable": true
|
||||
},
|
||||
"newsletter_email_placeholder": {
|
||||
"type": "text",
|
||||
"label": "Placeholder email",
|
||||
"default": "Email",
|
||||
"editable": true
|
||||
},
|
||||
"newsletter_whatsapp_placeholder": {
|
||||
"type": "text",
|
||||
"label": "Placeholder WhatsApp",
|
||||
"default": "WhatsApp",
|
||||
"editable": true
|
||||
},
|
||||
"newsletter_button_text": {
|
||||
"type": "text",
|
||||
"label": "Texto boton",
|
||||
@@ -122,11 +147,11 @@
|
||||
"editable": true
|
||||
},
|
||||
"newsletter_webhook_url": {
|
||||
"type": "url",
|
||||
"type": "textarea",
|
||||
"label": "URL del Webhook",
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"description": "URL donde se enviara el email de suscripcion (no visible en frontend)",
|
||||
"description": "URL donde se enviara la suscripcion (no visible en frontend)",
|
||||
"secret": true
|
||||
},
|
||||
"newsletter_success_message": {
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
"brand_text": {
|
||||
"type": "text",
|
||||
"label": "Texto de la marca",
|
||||
"default": "Mi Sitio",
|
||||
"default": "APU's México",
|
||||
"editable": true,
|
||||
"maxlength": 50,
|
||||
"conditional_logic": {
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"label_text": {
|
||||
"type": "text",
|
||||
"label": "Etiqueta del anuncio",
|
||||
"default": "Nuevo:",
|
||||
"default": "Nueva imagen, mejor experiencia:",
|
||||
"editable": true,
|
||||
"required": true,
|
||||
"maxlength": 30,
|
||||
@@ -73,7 +73,7 @@
|
||||
"message_text": {
|
||||
"type": "textarea",
|
||||
"label": "Texto del mensaje",
|
||||
"default": "Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.",
|
||||
"default": "+200,000 APUs disponibles. 10,000 con costos 2025, actualizamos los costos de 300 más cada día.",
|
||||
"editable": true,
|
||||
"required": true,
|
||||
"maxlength": 200,
|
||||
@@ -83,7 +83,7 @@
|
||||
"link_text": {
|
||||
"type": "text",
|
||||
"label": "Texto del enlace",
|
||||
"default": "Ver Catálogo",
|
||||
"default": "Convertirme en V.I.P.",
|
||||
"editable": true,
|
||||
"required": true,
|
||||
"maxlength": 50,
|
||||
|
||||
@@ -88,6 +88,7 @@ final readonly class ComponentConfiguration
|
||||
|
||||
// Grupos específicos para footer
|
||||
'widget_1', // Widget 1 del footer (menú)
|
||||
'widget_1b', // Widget 1B del footer (menú secundario columna 1)
|
||||
'widget_2', // Widget 2 del footer (menú)
|
||||
'widget_3', // Widget 3 del footer (menú)
|
||||
'newsletter', // Sección newsletter del footer
|
||||
|
||||
Reference in New Issue
Block a user