Feat: Implementar Top Bar configurable - Issue #143
Implementación completa del componente Top Bar con 15 campos configurables desde el admin panel, siguiendo el algoritmo universal v2.0. ## Cambios Realizados ### Backend (PHP) - Agregados defaults del Top Bar a class-settings-manager.php - Implementada validación completa en class-validator.php (15 campos) - Implementada sanitización con sanitize_text_field, esc_url_raw y sanitize_hex_color - Modificado header.php con código 100% configurable usando wp_parse_args() ### Frontend (Admin Panel) - Creado tab HTML completo con 3 secciones: Activación, Contenido y Estilos - Implementado JavaScript para renderizado y recolección de datos - 15 campos configurables: enabled, visibility, icon, content, link, custom styles ### Infraestructura - Creado admin-panel/init.php para carga del módulo - Creada class-admin-menu.php con enqueue de Bootstrap 5 y assets - Creada estructura base CSS y JavaScript del admin - Ya cargado en functions.php línea 276 ## Características - Responsive: Control independiente mobile/desktop - Estilos personalizables: 4 colores + tamaño de fuente - Validación robusta: Límites de caracteres, URLs, colores hex - Defaults inteligentes: Valores seguros si no hay configuración 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
169
admin-panel/admin/assets/css/admin-panel.css
Normal file
169
admin-panel/admin/assets/css/admin-panel.css
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Admin Panel Styles
|
||||
*
|
||||
* Estilos base para el panel de administración
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Container
|
||||
======================================== */
|
||||
|
||||
.apus-admin-panel {
|
||||
max-width: 1400px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Header
|
||||
======================================== */
|
||||
|
||||
.apus-admin-panel h1 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.apus-admin-panel .description {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Tabs
|
||||
======================================== */
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
color: #666;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:hover {
|
||||
color: #0073aa;
|
||||
border-bottom-color: #0073aa;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
color: #0073aa;
|
||||
font-weight: 600;
|
||||
border-bottom-color: #0073aa;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Tab Content
|
||||
======================================== */
|
||||
|
||||
.tab-content {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tab-pane h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tab-pane h4 {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Form Sections
|
||||
======================================== */
|
||||
|
||||
.form-section {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.form-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="url"],
|
||||
.form-group input[type="email"],
|
||||
.form-group input[type="number"],
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.form-group .form-text {
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-group .form-text code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Action Buttons
|
||||
======================================== */
|
||||
|
||||
.admin-actions {
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-top: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.admin-actions .button-primary {
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.admin-actions .button-primary i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Responsive
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 782px) {
|
||||
.apus-admin-panel {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="url"],
|
||||
.form-group input[type="email"],
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
276
admin-panel/admin/assets/js/admin-app.js
Normal file
276
admin-panel/admin/assets/js/admin-app.js
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Admin Panel Application
|
||||
*
|
||||
* Gestión de configuraciones de componentes del tema
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
const AdminPanel = {
|
||||
/**
|
||||
* Estado de la aplicación
|
||||
*/
|
||||
STATE: {
|
||||
settings: {},
|
||||
hasChanges: false,
|
||||
isLoading: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Inicializar aplicación
|
||||
*/
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.loadSettings();
|
||||
},
|
||||
|
||||
/**
|
||||
* Vincular eventos
|
||||
*/
|
||||
bindEvents() {
|
||||
// Botón guardar
|
||||
document.getElementById('saveSettings').addEventListener('click', () => {
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
// Detectar cambios en formularios
|
||||
document.querySelectorAll('input, select, textarea').forEach(input => {
|
||||
input.addEventListener('change', () => {
|
||||
this.STATE.hasChanges = true;
|
||||
document.getElementById('saveSettings').disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Tabs
|
||||
const tabs = document.querySelectorAll('.nav-tabs .nav-link');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.switchTab(tab);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Cambiar tab
|
||||
*/
|
||||
switchTab(tab) {
|
||||
// Remover active de todos
|
||||
document.querySelectorAll('.nav-tabs .nav-link').forEach(t => {
|
||||
t.classList.remove('active');
|
||||
});
|
||||
document.querySelectorAll('.tab-pane').forEach(pane => {
|
||||
pane.classList.remove('show', 'active');
|
||||
});
|
||||
|
||||
// Activar seleccionado
|
||||
tab.classList.add('active');
|
||||
const targetId = tab.getAttribute('data-bs-target').substring(1);
|
||||
const targetPane = document.getElementById(targetId);
|
||||
if (targetPane) {
|
||||
targetPane.classList.add('show', 'active');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Cargar configuraciones desde servidor
|
||||
*/
|
||||
async loadSettings() {
|
||||
this.STATE.isLoading = true;
|
||||
this.showSpinner(true);
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'POST',
|
||||
url: apusAdminData.ajaxUrl,
|
||||
data: new URLSearchParams({
|
||||
action: 'apus_get_settings',
|
||||
nonce: apusAdminData.nonce
|
||||
})
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.STATE.settings = response.data.data;
|
||||
this.renderAllComponents();
|
||||
} else {
|
||||
this.showNotice('Error al cargar configuraciones', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading settings:', error);
|
||||
this.showNotice('Error de conexión', 'error');
|
||||
} finally {
|
||||
this.STATE.isLoading = false;
|
||||
this.showSpinner(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Guardar configuraciones al servidor
|
||||
*/
|
||||
async saveSettings() {
|
||||
if (!this.STATE.hasChanges) {
|
||||
this.showNotice('No hay cambios para guardar', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showSpinner(true);
|
||||
|
||||
try {
|
||||
const formData = this.collectFormData();
|
||||
|
||||
const response = await axios({
|
||||
method: 'POST',
|
||||
url: apusAdminData.ajaxUrl,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify({
|
||||
action: 'apus_save_settings',
|
||||
nonce: apusAdminData.nonce,
|
||||
...formData
|
||||
})
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.STATE.hasChanges = false;
|
||||
document.getElementById('saveSettings').disabled = true;
|
||||
this.showNotice('Configuración guardada correctamente', 'success');
|
||||
} else {
|
||||
this.showNotice(response.data.data.message || 'Error al guardar', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
this.showNotice('Error de conexión', 'error');
|
||||
} finally {
|
||||
this.showSpinner(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Recolectar datos del formulario
|
||||
* Cada componente agregará su sección aquí
|
||||
*/
|
||||
collectFormData() {
|
||||
return {
|
||||
components: {
|
||||
top_bar: {
|
||||
enabled: document.getElementById('topBarEnabled').checked,
|
||||
show_on_mobile: document.getElementById('topBarShowOnMobile').checked,
|
||||
show_on_desktop: document.getElementById('topBarShowOnDesktop').checked,
|
||||
icon_class: document.getElementById('topBarIconClass').value.trim(),
|
||||
show_icon: document.getElementById('topBarShowIcon').checked,
|
||||
highlight_text: document.getElementById('topBarHighlightText').value.trim(),
|
||||
message_text: document.getElementById('topBarMessageText').value.trim(),
|
||||
link_text: document.getElementById('topBarLinkText').value.trim(),
|
||||
link_url: document.getElementById('topBarLinkUrl').value.trim(),
|
||||
link_target: document.getElementById('topBarLinkTarget').value,
|
||||
show_link: document.getElementById('topBarShowLink').checked,
|
||||
custom_styles: {
|
||||
background_color: this.getColorValue('topBarBgColor', ''),
|
||||
text_color: this.getColorValue('topBarTextColor', ''),
|
||||
highlight_color: this.getColorValue('topBarHighlightColor', ''),
|
||||
link_hover_color: this.getColorValue('topBarLinkHoverColor', ''),
|
||||
font_size: document.getElementById('topBarFontSize').value
|
||||
}
|
||||
}
|
||||
// Navbar - Pendiente
|
||||
// Hero - Pendiente
|
||||
// Footer - Pendiente
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Renderizar todos los componentes
|
||||
*/
|
||||
renderAllComponents() {
|
||||
const components = this.STATE.settings.components || {};
|
||||
|
||||
// Top Bar
|
||||
if (components.top_bar) {
|
||||
this.renderTopBar(components.top_bar);
|
||||
}
|
||||
// Navbar - Pendiente
|
||||
// Hero - Pendiente
|
||||
// Footer - Pendiente
|
||||
},
|
||||
|
||||
/**
|
||||
* Renderizar Top Bar
|
||||
*/
|
||||
renderTopBar(topBar) {
|
||||
document.getElementById('topBarEnabled').checked = topBar.enabled !== undefined ? topBar.enabled : true;
|
||||
document.getElementById('topBarShowOnMobile').checked = topBar.show_on_mobile !== undefined ? topBar.show_on_mobile : true;
|
||||
document.getElementById('topBarShowOnDesktop').checked = topBar.show_on_desktop !== undefined ? topBar.show_on_desktop : true;
|
||||
document.getElementById('topBarIconClass').value = topBar.icon_class || 'bi bi-megaphone-fill';
|
||||
document.getElementById('topBarShowIcon').checked = topBar.show_icon !== undefined ? topBar.show_icon : true;
|
||||
document.getElementById('topBarHighlightText').value = topBar.highlight_text || 'Nuevo:';
|
||||
document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.';
|
||||
document.getElementById('topBarLinkText').value = topBar.link_text || 'Ver Catálogo';
|
||||
document.getElementById('topBarLinkUrl').value = topBar.link_url || '/catalogo';
|
||||
document.getElementById('topBarLinkTarget').value = topBar.link_target || '_self';
|
||||
document.getElementById('topBarShowLink').checked = topBar.show_link !== undefined ? topBar.show_link : true;
|
||||
|
||||
// Estilos personalizados
|
||||
if (topBar.custom_styles) {
|
||||
if (topBar.custom_styles.background_color) {
|
||||
document.getElementById('topBarBgColor').value = topBar.custom_styles.background_color;
|
||||
}
|
||||
if (topBar.custom_styles.text_color) {
|
||||
document.getElementById('topBarTextColor').value = topBar.custom_styles.text_color;
|
||||
}
|
||||
if (topBar.custom_styles.highlight_color) {
|
||||
document.getElementById('topBarHighlightColor').value = topBar.custom_styles.highlight_color;
|
||||
}
|
||||
if (topBar.custom_styles.link_hover_color) {
|
||||
document.getElementById('topBarLinkHoverColor').value = topBar.custom_styles.link_hover_color;
|
||||
}
|
||||
document.getElementById('topBarFontSize').value = topBar.custom_styles.font_size || 'normal';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utilidad: Obtener valor de color con fallback
|
||||
*/
|
||||
getColorValue(inputId, defaultValue) {
|
||||
const input = document.getElementById(inputId);
|
||||
const value = input ? input.value.trim() : '';
|
||||
return value || defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Utilidad: Mostrar spinner
|
||||
*/
|
||||
showSpinner(show) {
|
||||
const spinner = document.querySelector('.spinner');
|
||||
if (spinner) {
|
||||
spinner.style.display = show ? 'inline-block' : 'none';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utilidad: Mostrar notificación
|
||||
*/
|
||||
showNotice(message, type = 'info') {
|
||||
// WordPress admin notices
|
||||
const noticeDiv = document.createElement('div');
|
||||
noticeDiv.className = `notice notice-${type} is-dismissible`;
|
||||
noticeDiv.innerHTML = `<p>${message}</p>`;
|
||||
|
||||
const container = document.querySelector('.apus-admin-panel');
|
||||
if (container) {
|
||||
container.insertBefore(noticeDiv, container.firstChild);
|
||||
|
||||
// Auto-dismiss después de 5 segundos
|
||||
setTimeout(() => {
|
||||
noticeDiv.remove();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Inicializar cuando el DOM esté listo
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
AdminPanel.init();
|
||||
});
|
||||
119
admin-panel/admin/includes/class-admin-menu.php
Normal file
119
admin-panel/admin/includes/class-admin-menu.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Menu Class
|
||||
*
|
||||
* Registra menú en WordPress admin y carga assets
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_Admin_Menu {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('admin_menu', array($this, 'add_menu_page'));
|
||||
add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registrar página de admin
|
||||
*/
|
||||
public function add_menu_page() {
|
||||
add_theme_page(
|
||||
'APUs Theme Settings', // Page title
|
||||
'Tema APUs', // Menu title
|
||||
'manage_options', // Capability
|
||||
'apus-theme-settings', // Menu slug
|
||||
array($this, 'render_admin_page'), // Callback
|
||||
59 // Position
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderizar página de admin
|
||||
*/
|
||||
public function render_admin_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die(__('No tienes permisos para acceder a esta página.'));
|
||||
}
|
||||
|
||||
require_once APUS_ADMIN_PANEL_PATH . 'admin/pages/main.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encolar assets (CSS/JS)
|
||||
*/
|
||||
public function enqueue_assets($hook) {
|
||||
// Solo cargar en nuestra página
|
||||
if ($hook !== 'appearance_page_apus-theme-settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bootstrap 5.3.2 CSS
|
||||
wp_enqueue_style(
|
||||
'bootstrap',
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css',
|
||||
array(),
|
||||
'5.3.2'
|
||||
);
|
||||
|
||||
// Bootstrap Icons
|
||||
wp_enqueue_style(
|
||||
'bootstrap-icons',
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css',
|
||||
array(),
|
||||
'1.11.1'
|
||||
);
|
||||
|
||||
// Admin Panel CSS
|
||||
wp_enqueue_style(
|
||||
'apus-admin-panel-css',
|
||||
APUS_ADMIN_PANEL_URL . 'admin/assets/css/admin-panel.css',
|
||||
array('bootstrap'),
|
||||
APUS_ADMIN_PANEL_VERSION
|
||||
);
|
||||
|
||||
// Bootstrap 5.3.2 JS
|
||||
wp_enqueue_script(
|
||||
'bootstrap',
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js',
|
||||
array(),
|
||||
'5.3.2',
|
||||
true
|
||||
);
|
||||
|
||||
// Axios (para AJAX)
|
||||
wp_enqueue_script(
|
||||
'axios',
|
||||
'https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js',
|
||||
array(),
|
||||
'1.6.0',
|
||||
true
|
||||
);
|
||||
|
||||
// Admin Panel JS
|
||||
wp_enqueue_script(
|
||||
'apus-admin-panel-js',
|
||||
APUS_ADMIN_PANEL_URL . 'admin/assets/js/admin-app.js',
|
||||
array('jquery', 'axios'),
|
||||
APUS_ADMIN_PANEL_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Pasar datos a JavaScript
|
||||
wp_localize_script('apus-admin-panel-js', 'apusAdminData', array(
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('apus_admin_nonce')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Instanciar clase
|
||||
new APUS_Admin_Menu();
|
||||
177
admin-panel/admin/pages/main.php
Normal file
177
admin-panel/admin/pages/main.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Panel - Main Page
|
||||
*
|
||||
* Interfaz de administración de componentes del tema
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="wrap apus-admin-panel">
|
||||
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
|
||||
<p class="description">Configure los componentes del tema Apus</p>
|
||||
|
||||
<!-- Navigation Tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" data-bs-target="#topBar" href="#topBar">Top Bar</a>
|
||||
</li>
|
||||
<!-- Más tabs aquí: Navbar, Hero, Footer, etc. -->
|
||||
</ul>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content mt-3">
|
||||
<!-- Top Bar Tab -->
|
||||
<div id="topBar" class="tab-pane fade show active">
|
||||
<h3>Configuración Top Bar</h3>
|
||||
<p class="text-muted">Personaliza la barra de notificación superior del sitio.</p>
|
||||
|
||||
<!-- Activación y Visibilidad -->
|
||||
<div class="form-section mb-4">
|
||||
<h4>Activación y Visibilidad</h4>
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" id="topBarEnabled" class="form-check-input">
|
||||
Activar Top Bar
|
||||
</label>
|
||||
<p class="form-text text-muted">Si está desactivado, el Top Bar no se mostrará en el sitio.</p>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" id="topBarShowOnMobile" class="form-check-input">
|
||||
Mostrar en móvil
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" id="topBarShowOnDesktop" class="form-check-input">
|
||||
Mostrar en desktop
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenido -->
|
||||
<div class="form-section mb-4">
|
||||
<h4>Contenido</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarIconClass">Clase del icono</label>
|
||||
<input type="text" id="topBarIconClass" class="form-control" placeholder="bi bi-megaphone-fill">
|
||||
<p class="form-text text-muted">Usa clases de <a href="https://icons.getbootstrap.com/" target="_blank">Bootstrap Icons</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" id="topBarShowIcon" class="form-check-input">
|
||||
Mostrar icono
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarHighlightText">Texto destacado</label>
|
||||
<input type="text" id="topBarHighlightText" class="form-control" placeholder="Nuevo:" maxlength="30">
|
||||
<p class="form-text text-muted">Máximo 30 caracteres. Aparece en negritas.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarMessageText">Mensaje principal *</label>
|
||||
<input type="text" id="topBarMessageText" class="form-control" placeholder="Accede a más de 200,000 APUs..." maxlength="250" required>
|
||||
<p class="form-text text-muted">Máximo 250 caracteres. Campo obligatorio.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarLinkText">Texto del enlace</label>
|
||||
<input type="text" id="topBarLinkText" class="form-control" placeholder="Ver Catálogo" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarLinkUrl">URL del enlace</label>
|
||||
<input type="url" id="topBarLinkUrl" class="form-control" placeholder="/catalogo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarLinkTarget">Target</label>
|
||||
<select id="topBarLinkTarget" class="form-select">
|
||||
<option value="_self">Misma pestaña</option>
|
||||
<option value="_blank">Nueva pestaña</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" id="topBarShowLink" class="form-check-input">
|
||||
Mostrar enlace
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estilos Personalizados -->
|
||||
<div class="form-section">
|
||||
<h4>Estilos Personalizados</h4>
|
||||
<p class="form-text text-muted mb-3">Deja en blanco para usar los colores del tema por defecto.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarBgColor">Color de fondo</label>
|
||||
<input type="color" id="topBarBgColor" class="form-control form-control-color">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarTextColor">Color de texto</label>
|
||||
<input type="color" id="topBarTextColor" class="form-control form-control-color">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarHighlightColor">Color del highlight</label>
|
||||
<input type="color" id="topBarHighlightColor" class="form-control form-control-color">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarLinkHoverColor">Color hover enlace</label>
|
||||
<input type="color" id="topBarLinkHoverColor" class="form-control form-control-color">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="topBarFontSize">Tamaño de fuente</label>
|
||||
<select id="topBarFontSize" class="form-select" style="max-width: 200px;">
|
||||
<option value="small">Pequeña</option>
|
||||
<option value="normal" selected>Normal</option>
|
||||
<option value="large">Grande</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="admin-actions mt-4">
|
||||
<button type="button" id="saveSettings" class="button button-primary" disabled>
|
||||
<i class="bi bi-save me-2"></i>Guardar Cambios
|
||||
</button>
|
||||
<span class="spinner" style="display: none; float: none; margin-left: 10px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
183
admin-panel/includes/class-settings-manager.php
Normal file
183
admin-panel/includes/class-settings-manager.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* Settings Manager Class
|
||||
*
|
||||
* CRUD de configuraciones por componentes
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_Settings_Manager {
|
||||
|
||||
const OPTION_NAME = 'apus_theme_settings';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('wp_ajax_apus_get_settings', array($this, 'ajax_get_settings'));
|
||||
add_action('wp_ajax_apus_save_settings', array($this, 'ajax_save_settings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener configuraciones
|
||||
*/
|
||||
public function get_settings() {
|
||||
$settings = get_option(self::OPTION_NAME, array());
|
||||
$defaults = $this->get_defaults();
|
||||
|
||||
return wp_parse_args($settings, $defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar configuraciones
|
||||
*/
|
||||
public function save_settings($data) {
|
||||
// Validar
|
||||
$validator = new APUS_Validator();
|
||||
$validation = $validator->validate($data);
|
||||
|
||||
if (!$validation['valid']) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => 'Error de validación',
|
||||
'errors' => $validation['errors']
|
||||
);
|
||||
}
|
||||
|
||||
// Sanitizar
|
||||
$sanitized = $this->sanitize_settings($data);
|
||||
|
||||
// Agregar metadata
|
||||
$sanitized['version'] = APUS_ADMIN_PANEL_VERSION;
|
||||
$sanitized['updated_at'] = current_time('mysql');
|
||||
|
||||
// Guardar
|
||||
update_option(self::OPTION_NAME, $sanitized, false);
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'message' => 'Configuración guardada correctamente'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valores por defecto
|
||||
* NOTA: Aquí se agregan los defaults de cada componente
|
||||
*/
|
||||
public function get_defaults() {
|
||||
return array(
|
||||
'version' => APUS_ADMIN_PANEL_VERSION,
|
||||
'components' => array(
|
||||
'top_bar' => array(
|
||||
'enabled' => true,
|
||||
'show_on_mobile' => true,
|
||||
'show_on_desktop' => true,
|
||||
'icon_class' => 'bi bi-megaphone-fill',
|
||||
'show_icon' => true,
|
||||
'highlight_text' => 'Nuevo:',
|
||||
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
|
||||
'link_text' => 'Ver Catálogo',
|
||||
'link_url' => '/catalogo',
|
||||
'link_target' => '_self',
|
||||
'show_link' => true,
|
||||
'custom_styles' => array(
|
||||
'background_color' => '',
|
||||
'text_color' => '',
|
||||
'highlight_color' => '',
|
||||
'link_hover_color' => '',
|
||||
'font_size' => 'normal'
|
||||
)
|
||||
)
|
||||
// Navbar - Pendiente
|
||||
// Hero - Pendiente
|
||||
// Footer - Pendiente
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizar configuraciones
|
||||
* NOTA: Aquí se agrega sanitización de cada componente
|
||||
*/
|
||||
public function sanitize_settings($data) {
|
||||
$sanitized = array(
|
||||
'components' => array()
|
||||
);
|
||||
|
||||
// Sanitizar Top Bar
|
||||
if (isset($data['components']['top_bar'])) {
|
||||
$top_bar = $data['components']['top_bar'];
|
||||
|
||||
$sanitized['components']['top_bar'] = array(
|
||||
'enabled' => !empty($top_bar['enabled']),
|
||||
'show_on_mobile' => !empty($top_bar['show_on_mobile']),
|
||||
'show_on_desktop' => !empty($top_bar['show_on_desktop']),
|
||||
'icon_class' => sanitize_text_field($top_bar['icon_class'] ?? ''),
|
||||
'show_icon' => !empty($top_bar['show_icon']),
|
||||
'highlight_text' => sanitize_text_field($top_bar['highlight_text'] ?? ''),
|
||||
'message_text' => sanitize_text_field($top_bar['message_text'] ?? ''),
|
||||
'link_text' => sanitize_text_field($top_bar['link_text'] ?? ''),
|
||||
'link_url' => esc_url_raw($top_bar['link_url'] ?? ''),
|
||||
'link_target' => in_array($top_bar['link_target'] ?? '', array('_self', '_blank')) ? $top_bar['link_target'] : '_self',
|
||||
'show_link' => !empty($top_bar['show_link']),
|
||||
'custom_styles' => array(
|
||||
'background_color' => sanitize_hex_color($top_bar['custom_styles']['background_color'] ?? ''),
|
||||
'text_color' => sanitize_hex_color($top_bar['custom_styles']['text_color'] ?? ''),
|
||||
'highlight_color' => sanitize_hex_color($top_bar['custom_styles']['highlight_color'] ?? ''),
|
||||
'link_hover_color' => sanitize_hex_color($top_bar['custom_styles']['link_hover_color'] ?? ''),
|
||||
'font_size' => in_array($top_bar['custom_styles']['font_size'] ?? '', array('small', 'normal', 'large')) ? $top_bar['custom_styles']['font_size'] : 'normal'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Obtener configuraciones
|
||||
*/
|
||||
public function ajax_get_settings() {
|
||||
check_ajax_referer('apus_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Permisos insuficientes');
|
||||
}
|
||||
|
||||
$settings = $this->get_settings();
|
||||
wp_send_json_success($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Guardar configuraciones
|
||||
*/
|
||||
public function ajax_save_settings() {
|
||||
check_ajax_referer('apus_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Permisos insuficientes');
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data) {
|
||||
wp_send_json_error('Datos inválidos');
|
||||
}
|
||||
|
||||
$result = $this->save_settings($data);
|
||||
|
||||
if ($result['success']) {
|
||||
wp_send_json_success($result);
|
||||
} else {
|
||||
wp_send_json_error($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instanciar clase
|
||||
new APUS_Settings_Manager();
|
||||
100
admin-panel/includes/class-validator.php
Normal file
100
admin-panel/includes/class-validator.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Validator Class
|
||||
*
|
||||
* Validación de datos por componentes
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_Validator {
|
||||
|
||||
/**
|
||||
* Validar todas las configuraciones
|
||||
*/
|
||||
public function validate($data) {
|
||||
$errors = array();
|
||||
|
||||
// Validar estructura base
|
||||
if (!isset($data['components']) || !is_array($data['components'])) {
|
||||
$errors[] = 'Estructura de datos inválida';
|
||||
return array('valid' => false, 'errors' => $errors);
|
||||
}
|
||||
|
||||
// Validar Top Bar
|
||||
if (isset($data['components']['top_bar'])) {
|
||||
$top_bar_errors = $this->validate_top_bar($data['components']['top_bar']);
|
||||
$errors = array_merge($errors, $top_bar_errors);
|
||||
}
|
||||
|
||||
return array(
|
||||
'valid' => empty($errors),
|
||||
'errors' => $errors
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar Top Bar
|
||||
*/
|
||||
public function validate_top_bar($top_bar) {
|
||||
$errors = array();
|
||||
|
||||
// Validar icon_class
|
||||
if (!empty($top_bar['icon_class']) && strlen($top_bar['icon_class']) > 50) {
|
||||
$errors[] = 'La clase del icono no puede exceder 50 caracteres';
|
||||
}
|
||||
|
||||
// Validar highlight_text
|
||||
if (!empty($top_bar['highlight_text']) && strlen($top_bar['highlight_text']) > 30) {
|
||||
$errors[] = 'El texto destacado no puede exceder 30 caracteres';
|
||||
}
|
||||
|
||||
// Validar message_text
|
||||
if (empty($top_bar['message_text'])) {
|
||||
$errors[] = 'El mensaje principal es obligatorio';
|
||||
} elseif (strlen($top_bar['message_text']) > 250) {
|
||||
$errors[] = 'El mensaje principal no puede exceder 250 caracteres';
|
||||
}
|
||||
|
||||
// Validar link_text
|
||||
if (!empty($top_bar['link_text']) && strlen($top_bar['link_text']) > 50) {
|
||||
$errors[] = 'El texto del enlace no puede exceder 50 caracteres';
|
||||
}
|
||||
|
||||
// Validar link_url
|
||||
if (!empty($top_bar['link_url']) && !filter_var($top_bar['link_url'], FILTER_VALIDATE_URL)) {
|
||||
$errors[] = 'La URL del enlace no es válida';
|
||||
}
|
||||
|
||||
// Validar link_target
|
||||
if (!in_array($top_bar['link_target'] ?? '', array('_self', '_blank'))) {
|
||||
$errors[] = 'El target del enlace debe ser _self o _blank';
|
||||
}
|
||||
|
||||
// Validar colores
|
||||
if (!empty($top_bar['custom_styles']['background_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['background_color'])) {
|
||||
$errors[] = 'El color de fondo debe ser un color hexadecimal válido';
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['text_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['text_color'])) {
|
||||
$errors[] = 'El color de texto debe ser un color hexadecimal válido';
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['highlight_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['highlight_color'])) {
|
||||
$errors[] = 'El color del highlight debe ser un color hexadecimal válido';
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['link_hover_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['link_hover_color'])) {
|
||||
$errors[] = 'El color hover del enlace debe ser un color hexadecimal válido';
|
||||
}
|
||||
|
||||
// Validar font_size
|
||||
if (!in_array($top_bar['custom_styles']['font_size'] ?? '', array('small', 'normal', 'large'))) {
|
||||
$errors[] = 'El tamaño de fuente debe ser small, normal o large';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
25
admin-panel/init.php
Normal file
25
admin-panel/init.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Panel Module - Initialization
|
||||
*
|
||||
* Sistema de configuración por componentes
|
||||
* Cada componente del tema es configurable desde el admin panel
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Module constants
|
||||
define('APUS_ADMIN_PANEL_VERSION', '2.0.0');
|
||||
define('APUS_ADMIN_PANEL_PATH', get_template_directory() . '/admin-panel/');
|
||||
define('APUS_ADMIN_PANEL_URL', get_template_directory_uri() . '/admin-panel/');
|
||||
|
||||
// Load classes
|
||||
require_once APUS_ADMIN_PANEL_PATH . 'admin/includes/class-admin-menu.php';
|
||||
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-settings-manager.php';
|
||||
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-validator.php';
|
||||
92
header.php
92
header.php
@@ -20,16 +20,98 @@
|
||||
<body <?php body_class(); ?> data-bs-spy="scroll" data-bs-target=".toc-container" data-bs-offset="100">
|
||||
<?php wp_body_open(); ?>
|
||||
|
||||
<!-- Top Notification Bar (Template líneas 55-63) -->
|
||||
<div class="top-notification-bar">
|
||||
<!-- Top Notification Bar (Configurable desde Admin Panel) -->
|
||||
<?php
|
||||
// Obtener configuración del Top Bar
|
||||
$settings = get_option('apus_theme_settings', array());
|
||||
$top_bar_defaults = array(
|
||||
'enabled' => true,
|
||||
'show_on_mobile' => true,
|
||||
'show_on_desktop' => true,
|
||||
'icon_class' => 'bi bi-megaphone-fill',
|
||||
'show_icon' => true,
|
||||
'highlight_text' => 'Nuevo:',
|
||||
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
|
||||
'link_text' => 'Ver Catálogo',
|
||||
'link_url' => '/catalogo',
|
||||
'link_target' => '_self',
|
||||
'show_link' => true,
|
||||
'custom_styles' => array(
|
||||
'background_color' => '',
|
||||
'text_color' => '',
|
||||
'highlight_color' => '',
|
||||
'link_hover_color' => '',
|
||||
'font_size' => 'normal'
|
||||
)
|
||||
);
|
||||
|
||||
$top_bar_config = isset($settings['components']['top_bar']) ? $settings['components']['top_bar'] : array();
|
||||
$top_bar = wp_parse_args($top_bar_config, $top_bar_defaults);
|
||||
|
||||
// Verificar si está habilitado
|
||||
if ($top_bar['enabled']):
|
||||
// Construir clases CSS
|
||||
$classes = array('top-notification-bar');
|
||||
if (!$top_bar['show_on_mobile']) {
|
||||
$classes[] = 'd-none';
|
||||
$classes[] = 'd-md-block';
|
||||
}
|
||||
if (!$top_bar['show_on_desktop']) {
|
||||
$classes[] = 'd-md-none';
|
||||
}
|
||||
$class_attr = implode(' ', $classes);
|
||||
|
||||
// Construir estilos inline
|
||||
$inline_styles = array();
|
||||
if (!empty($top_bar['custom_styles']['background_color'])) {
|
||||
$inline_styles[] = 'background-color: ' . esc_attr($top_bar['custom_styles']['background_color']);
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['text_color'])) {
|
||||
$inline_styles[] = 'color: ' . esc_attr($top_bar['custom_styles']['text_color']);
|
||||
}
|
||||
|
||||
// Font size
|
||||
$font_size_map = array(
|
||||
'small' => '0.8rem',
|
||||
'normal' => '0.9rem',
|
||||
'large' => '1rem'
|
||||
);
|
||||
if (isset($font_size_map[$top_bar['custom_styles']['font_size']])) {
|
||||
$inline_styles[] = 'font-size: ' . $font_size_map[$top_bar['custom_styles']['font_size']];
|
||||
}
|
||||
|
||||
$style_attr = !empty($inline_styles) ? ' style="' . implode('; ', $inline_styles) . '"' : '';
|
||||
?>
|
||||
<div class="<?php echo esc_attr($class_attr); ?>"<?php echo $style_attr; ?>>
|
||||
<div class="container">
|
||||
<div class="d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-megaphone-fill me-2"></i>
|
||||
<span><strong>Nuevo:</strong> Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.</span>
|
||||
<a href="<?php echo esc_url(home_url('/catalogo')); ?>" class="ms-2 text-white text-decoration-underline">Ver Catálogo</a>
|
||||
<?php if ($top_bar['show_icon'] && !empty($top_bar['icon_class'])): ?>
|
||||
<i class="<?php echo esc_attr($top_bar['icon_class']); ?> me-2"></i>
|
||||
<?php endif; ?>
|
||||
|
||||
<span>
|
||||
<?php if (!empty($top_bar['highlight_text'])): ?>
|
||||
<strong<?php if (!empty($top_bar['custom_styles']['highlight_color'])): ?> style="color: <?php echo esc_attr($top_bar['custom_styles']['highlight_color']); ?>"<?php endif; ?>>
|
||||
<?php echo esc_html($top_bar['highlight_text']); ?>
|
||||
</strong>
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($top_bar['message_text']); ?>
|
||||
</span>
|
||||
|
||||
<?php if ($top_bar['show_link'] && !empty($top_bar['link_text']) && !empty($top_bar['link_url'])): ?>
|
||||
<a href="<?php echo esc_url(home_url($top_bar['link_url'])); ?>"
|
||||
class="ms-2 text-white text-decoration-underline top-bar-link"
|
||||
target="<?php echo esc_attr($top_bar['link_target']); ?>"
|
||||
<?php if (!empty($top_bar['custom_styles']['link_hover_color'])): ?>
|
||||
data-hover-color="<?php echo esc_attr($top_bar['custom_styles']['link_hover_color']); ?>"
|
||||
<?php endif; ?>>
|
||||
<?php echo esc_html($top_bar['link_text']); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Navbar (Template líneas 264-320) -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark py-3" role="navigation" aria-label="<?php esc_attr_e('Primary Navigation', 'apus-theme'); ?>">
|
||||
|
||||
Reference in New Issue
Block a user