Fase 1: Estructura Base y DI Container - Clean Architecture

COMPLETADO: Fase 1 de la migración a Clean Architecture + POO

## Estructura de Carpetas
- ✓ Estructura completa de 4 capas (Domain, Application, Infrastructure, Presentation)
- ✓ Carpetas de Use Cases (SaveComponent, GetComponent, DeleteComponent, SyncSchema)
- ✓ Estructura de tests (Unit, Integration, E2E)
- ✓ Carpetas de schemas y templates

## Composer y Autoloading
- ✓ PSR-4 autoloading configurado para ROITheme namespace
- ✓ Autoloader optimizado regenerado

## DI Container
- ✓ DIContainer implementado con patrón Singleton
- ✓ Métodos set(), get(), has() para gestión de servicios
- ✓ Getters específicos para ComponentRepository, ValidationService, CacheService
- ✓ Placeholders que serán implementados en Fase 5
- ✓ Prevención de clonación y deserialización

## Interfaces
- ✓ ComponentRepositoryInterface (Domain)
- ✓ ValidationServiceInterface (Application)
- ✓ CacheServiceInterface (Application)
- ✓ Component entity placeholder (Domain)

## Bootstrap
- ✓ functions.php actualizado con carga de Composer autoloader
- ✓ Inicialización del DIContainer
- ✓ Helper function roi_container() disponible globalmente

## Tests
- ✓ 10 tests unitarios para DIContainer (100% cobertura)
- ✓ Total: 13 tests unitarios, 28 assertions
- ✓ Suite de tests pasando correctamente

## Validación
- ✓ Script de validación automatizado (48/48 checks pasados)
- ✓ 100% de validaciones exitosas

La arquitectura base está lista para la Fase 2.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-11-17 13:48:24 -06:00
parent b782ebceee
commit de5fff4f5c
149 changed files with 3187 additions and 9554 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,538 +0,0 @@
# ANÁLISIS: Problema Crítico de Duplicación de Valores por Defecto
**Fecha:** 2025-01-13
**Severidad:** 🔴 ALTA - Problema de diseño arquitectónico
**Tipo:** Violación del principio DRY (Don't Repeat Yourself)
---
## 🔍 PROBLEMA IDENTIFICADO
El texto `"Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025."` está **duplicado en 7 ubicaciones diferentes**, lo que genera:
-**Difícil mantenimiento** - Cambiar requiere editar 7 archivos
-**Alto riesgo de errores** - Fácil olvidar actualizar un archivo
-**Inconsistencias** - Valores pueden desincronizarse
-**No hay fuente única de verdad** - Múltiples definiciones de defaults
---
## 📍 UBICACIONES DE LA DUPLICACIÓN
### 1. **admin/assets/js/admin-app.js** (línea 357)
```javascript
document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.';
```
**Propósito:** Fallback en JavaScript al renderizar el formulario
**Problema:** Duplica el default que ya está en PHP
---
### 2. **admin/includes/sanitizers/class-topbar-sanitizer.php** (línea 37)
```php
public function get_defaults() {
return array(
// ...
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
// ...
);
}
```
**Propósito:** Define defaults del sanitizer
**Problema:** ¿Por qué el sanitizer define defaults? Debería solo sanitizar.
---
### 3. **admin/includes/class-settings-manager.php** (línea 84)
```php
public function get_defaults() {
return array(
// ...
'components' => array(
'top_bar' => array(
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
// ...
)
)
);
}
```
**Propósito:** Define defaults centralizados del Settings Manager
**Problema:** ⚠️ **DUPLICA lo que ya tiene el Sanitizer**
---
### 4. **admin/pages/main.php** (líneas 243-244, 495)
**Línea 243-244:**
```html
<textarea id="topBarMessageText"
placeholder="Ej: Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025."
required>Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.</textarea>
```
**Línea 495 (preview):**
```html
<span>Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.</span>
```
**Propósito:**
- Placeholder del textarea
- Valor inicial del textarea
- Texto de preview
**Problema:****TRIPLE duplicación en un solo archivo**
---
### 5. **admin/components/component-top-bar.php** (línea 190, aparece 2 veces)
```html
<textarea id="topBarMessageText"
placeholder="Ej: Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025."
required="">Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.</textarea>
```
**Propósito:** Similar a main.php (placeholder + valor)
**Problema:** ¿Por qué existe este archivo si main.php ya tiene el formulario?
---
### 6. **header.php** (línea 34)
```php
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
```
**Propósito:** Fallback en el front-end del tema
**Problema:** El front-end NO debería definir defaults, debería leerlos del Settings Manager
---
## 🏗️ ANÁLISIS ARQUITECTÓNICO
### Arquitectura ACTUAL (Problemática)
```
┌─────────────────────────────────────────────────────────────────┐
│ CAPA 1: DEFAULTS DUPLICADOS (7 lugares) │
├─────────────────────────────────────────────────────────────────┤
│ ❌ TopBar Sanitizer::get_defaults() │
│ ❌ Settings Manager::get_defaults() │
│ ❌ admin-app.js (fallbacks en render) │
│ ❌ main.php (placeholder + valor inicial + preview) │
│ ❌ component-top-bar.php (placeholder + valor) │
│ ❌ header.php (fallback front-end) │
└─────────────────────────────────────────────────────────────────┘
🔴 PROBLEMA: No hay fuente única de verdad
```
### Arquitectura CORRECTA (Propuesta)
```
┌─────────────────────────────────────────────────────────────────┐
│ ÚNICA FUENTE DE VERDAD │
├─────────────────────────────────────────────────────────────────┤
│ ✅ Settings Manager::get_defaults() SOLAMENTE │
│ - Define TODOS los defaults de TODOS los componentes │
│ - Usa constantes PHP para valores reutilizables │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ CONSUMIDORES (leen de Settings Manager) │
├─────────────────────────────────────────────────────────────────┤
│ ✅ TopBar Sanitizer → Llama Settings Manager::get_defaults() │
│ ✅ admin-app.js → AJAX lee settings (ya con defaults merged) │
│ ✅ main.php → Usa PHP para obtener defaults dinámicamente │
│ ✅ header.php → Lee Settings Manager (NO define defaults) │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🔬 RAZONES DE LA DUPLICACIÓN
### 1. **Sanitizer vs Settings Manager** (Confusión de responsabilidades)
**Problema:**
- `APUS_TopBar_Sanitizer::get_defaults()` define defaults
- `APUS_Settings_Manager::get_defaults()` TAMBIÉN define defaults
**Pregunta:** ¿Por qué el SANITIZER define defaults?
**Responsabilidades correctas:**
-**Sanitizer:** Solo SANITIZAR datos (validar, limpiar)
-**Settings Manager:** Definir defaults, leer DB, hacer merge
**Solución:**
- Eliminar `get_defaults()` del Sanitizer
- Mantener solo en Settings Manager
---
### 2. **JavaScript con Fallbacks Hardcodeados**
**Código actual (admin-app.js:357):**
```javascript
topBar.message_text || 'Accede a más de 200,000...'
```
**Problema:** JavaScript NO debería tener defaults hardcodeados.
**Solución:**
Cuando JavaScript llama a AJAX para cargar settings, el Settings Manager YA hace merge con defaults:
```php
// Settings Manager ya retorna datos con defaults merged
public function get_settings() {
$db_data = $this->db_manager->get_all_settings();
$defaults = $this->get_defaults();
return wp_parse_args($db_data, $defaults); // ← Merge automático
}
```
Por lo tanto, JavaScript NUNCA recibirá un `message_text` vacío. El fallback `|| 'Accede...'` es **innecesario**.
**Corrección:**
```javascript
// ANTES:
document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede...';
// DESPUÉS:
document.getElementById('topBarMessageText').value = topBar.message_text;
// ↑ Settings Manager YA hizo merge con defaults
```
---
### 3. **HTML con Valores Hardcodeados** (main.php, component-top-bar.php)
**Código actual:**
```html
<textarea placeholder="Ej: Accede...">Accede...</textarea>
```
**Problemas:**
1. Placeholder hardcodeado
2. Valor inicial hardcodeado
3. Preview hardcodeado
**Solución:** Usar PHP para obtener defaults dinámicamente
```php
<?php
$settings_manager = new APUS_Settings_Manager();
$defaults = $settings_manager->get_defaults();
$default_message = $defaults['components']['top_bar']['message_text'];
?>
<textarea
id="topBarMessageText"
placeholder="Ej: <?php echo esc_attr($default_message); ?>"
><?php echo esc_html($default_message); ?></textarea>
```
**Preview:**
```html
<span id="topBarPreview"><?php echo esc_html($default_message); ?></span>
```
---
### 4. **component-top-bar.php vs main.php** (¿Duplicación de archivos?)
**Observación:**
- `admin/pages/main.php` contiene el formulario del Top Bar
- `admin/components/component-top-bar.php` TAMBIÉN contiene el formulario del Top Bar
**Pregunta:** ¿Por qué existen 2 archivos con el mismo formulario?
**Hipótesis:**
1. **component-top-bar.php** es un archivo PHP modular (componente)
2. **main.php** debería INCLUIR el componente, no duplicar el código
**Solución propuesta:**
```php
// main.php - Debería ser así:
<div id="topBarTab" class="tab-pane fade show active">
<?php require_once APUS_ADMIN_PANEL_PATH . 'components/component-top-bar.php'; ?>
</div>
```
Pero si component-top-bar.php es solo HTML sin lógica, entonces:
- Opción 1: Eliminar component-top-bar.php (usar solo main.php)
- Opción 2: Convertir component-top-bar.php en plantilla reutilizable
---
### 5. **header.php con Fallback** (Front-end no debería definir defaults)
**Código actual (header.php:34):**
```php
$top_bar_config = wp_parse_args($config, array(
'message_text' => 'Accede a más de 200,000...',
// ...
));
```
**Problema:** El front-end NO debería definir defaults.
**¿Por qué está esto aquí?**
Probablemente por si Settings Manager falla o no retorna datos.
**Solución correcta:**
```php
// ANTES:
$settings_manager = new APUS_Settings_Manager();
$settings = $settings_manager->get_settings();
$config = isset($settings['components']['top_bar']) ? $settings['components']['top_bar'] : array();
$top_bar_config = wp_parse_args($config, array( /* defaults hardcodeados */ ));
// DESPUÉS:
$settings_manager = new APUS_Settings_Manager();
$settings = $settings_manager->get_settings(); // ← Ya incluye defaults merged
$top_bar_config = $settings['components']['top_bar']; // ← Sin fallback necesario
```
**Razón:** `get_settings()` del Settings Manager YA hace merge con defaults.
---
## 💡 SOLUCIÓN PROPUESTA
### PASO 1: Única Fuente de Verdad (Settings Manager)
**Crear constantes para valores reutilizables:**
```php
// class-settings-manager.php
class APUS_Settings_Manager {
// Constantes de defaults
const DEFAULT_TOPBAR_MESSAGE = 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.';
const DEFAULT_TOPBAR_HIGHLIGHT = 'Nuevo:';
const DEFAULT_TOPBAR_LINK_TEXT = 'Ver Catálogo';
// ...
public function get_defaults() {
return array(
'version' => APUS_ADMIN_PANEL_VERSION,
'components' => array(
'top_bar' => array(
'enabled' => true,
'message_text' => self::DEFAULT_TOPBAR_MESSAGE,
'highlight_text' => self::DEFAULT_TOPBAR_HIGHLIGHT,
'link_text' => self::DEFAULT_TOPBAR_LINK_TEXT,
// ...
)
)
);
}
}
```
**Ventajas:**
- ✅ Constantes documentadas en un solo lugar
- ✅ Fácil de cambiar (1 línea en vez de 7 archivos)
- ✅ PHP autocomplete para IDEs
---
### PASO 2: Eliminar Duplicaciones
#### 2.1. Sanitizer NO debe tener `get_defaults()`
```php
// class-topbar-sanitizer.php
class APUS_TopBar_Sanitizer {
// ❌ ELIMINAR:
// public function get_defaults() { ... }
// ✅ MANTENER SOLO:
public function sanitize($data) {
// Lógica de sanitización
}
}
```
**Si el Sanitizer necesita defaults para validación:**
```php
class APUS_TopBar_Sanitizer {
private $settings_manager;
public function __construct() {
$this->settings_manager = new APUS_Settings_Manager();
}
public function sanitize($data) {
$defaults = $this->settings_manager->get_defaults()['components']['top_bar'];
// Usar $defaults si es necesario para validación
}
}
```
---
#### 2.2. JavaScript SIN Fallbacks Hardcodeados
```javascript
// admin-app.js
renderTopBar(topBar) {
// ANTES:
// document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede...';
// DESPUÉS:
document.getElementById('topBarMessageText').value = topBar.message_text;
document.getElementById('topBarHighlightText').value = topBar.highlight_text;
document.getElementById('topBarLinkText').value = topBar.link_text;
// ↑ Settings Manager YA hizo merge con defaults
}
```
**Razón:** AJAX obtiene settings de `get_settings()` que ya incluye defaults.
---
#### 2.3. HTML Dinámico (usar PHP)
```php
<!-- main.php -->
<?php
$settings_manager = new APUS_Settings_Manager();
$defaults = $settings_manager->get_defaults()['components']['top_bar'];
?>
<!-- Mensaje de texto -->
<div class="mb-3">
<label class="form-label">
<i class="bi bi-chat-text me-2"></i>Mensaje Principal
</label>
<textarea
id="topBarMessageText"
class="form-control"
rows="2"
maxlength="250"
placeholder="Ej: <?php echo esc_attr($defaults['message_text']); ?>"
><?php echo esc_html($defaults['message_text']); ?></textarea>
</div>
<!-- Preview -->
<div id="topBarPreview" class="preview-top-bar">
<span><?php echo esc_html($defaults['message_text']); ?></span>
</div>
```
---
#### 2.4. Front-end SIN Fallbacks
```php
// header.php
<?php
$settings_manager = new APUS_Settings_Manager();
$settings = $settings_manager->get_settings(); // ← Ya incluye defaults merged
$top_bar_config = $settings['components']['top_bar'];
// NO hacer wp_parse_args con defaults hardcodeados
// ❌ $top_bar_config = wp_parse_args($config, array('message_text' => '...'));
?>
<!-- Renderizar Top Bar -->
<?php if ($top_bar_config['enabled']): ?>
<div class="top-notification-bar">
<span><?php echo esc_html($top_bar_config['message_text']); ?></span>
</div>
<?php endif; ?>
```
---
#### 2.5. Eliminar component-top-bar.php (¿Duplicado?)
**Investigar:**
1. ¿Se usa `component-top-bar.php` en algún lugar?
2. Si NO se usa, eliminarlo
3. Si SÍ se usa, refactorizar para que main.php lo incluya
---
## 📊 RESUMEN DE CAMBIOS
| Archivo | Acción | Razón |
|---------|--------|-------|
| `class-settings-manager.php` | ✅ **Usar constantes para defaults** | Única fuente de verdad |
| `class-topbar-sanitizer.php` | ❌ **Eliminar `get_defaults()`** | Sanitizer no debe definir defaults |
| `admin-app.js` | ❌ **Eliminar fallbacks hardcodeados** | AJAX ya retorna defaults merged |
| `main.php` | ✏️ **Usar PHP dinámico para defaults** | Leer de Settings Manager |
| `component-top-bar.php` | 🔍 **Investigar si es duplicado** | Posible eliminación |
| `header.php` | ❌ **Eliminar fallbacks hardcodeados** | get_settings() ya incluye defaults |
---
## 🎯 BENEFICIOS DE LA SOLUCIÓN
### Antes (Actual)
```
Cambiar "Accede a más de 200,000..." requiere:
├── ✏️ Editar admin-app.js
├── ✏️ Editar class-topbar-sanitizer.php
├── ✏️ Editar class-settings-manager.php
├── ✏️ Editar main.php (3 lugares)
├── ✏️ Editar component-top-bar.php (2 lugares)
└── ✏️ Editar header.php
Total: 7 archivos, ~10 líneas a cambiar
Riesgo: 🔴 ALTO (fácil olvidar un archivo)
```
### Después (Propuesto)
```
Cambiar "Accede a más de 200,000..." requiere:
└── ✏️ Editar class-settings-manager.php (1 constante)
Total: 1 archivo, 1 línea
Riesgo: 🟢 BAJO (cambio centralizado)
```
---
## 🚨 IMPACTO EN OTROS COMPONENTES
**⚠️ IMPORTANTE:** Este problema probablemente se repite en los otros 3 componentes:
1. **Navbar** - ¿Tiene duplicación similar?
2. **Let's Talk Button** - ¿Tiene duplicación similar?
3. **Hero Section** - ¿Tiene duplicación similar?
**Recomendación:** Aplicar la misma refactorización a TODOS los componentes.
---
## ✅ CHECKLIST DE IMPLEMENTACIÓN
- [ ] Crear constantes en Settings Manager
- [ ] Eliminar `get_defaults()` de TopBar Sanitizer
- [ ] Eliminar fallbacks de admin-app.js
- [ ] Convertir HTML de main.php a dinámico
- [ ] Investigar si component-top-bar.php es necesario
- [ ] Eliminar fallbacks de header.php
- [ ] Verificar que NO hay regresiones
- [ ] Aplicar solución a Navbar
- [ ] Aplicar solución a Let's Talk Button
- [ ] Aplicar solución a Hero Section
---
## 🔗 REFERENCIAS
- **Principio DRY:** Don't Repeat Yourself
- **Single Source of Truth:** Una única fuente de verdad para datos
- **Separation of Concerns:** Cada clase tiene una responsabilidad clara
---
**Última actualización:** 2025-01-13

View File

@@ -1,354 +0,0 @@
# Arquitectura de Datos - Apus Theme
**Fecha:** 2025-01-14
**Versión:** 2.2.0
---
## 📊 ARQUITECTURA FINAL IMPLEMENTADA
```
┌──────────────────────────────────────────────────────────────┐
│ APUS_DB_MANAGER (Clase Única) │
│ ├─ Maneja AMBAS tablas con misma estructura │
│ ├─ Parámetro: table_type = 'components' | 'defaults' │
│ └─ Métodos: │
│ ├─ get_config($component, $key, $table_type) │
│ ├─ save_config(..., $table_type) │
│ ├─ delete_config(..., $table_type) │
│ └─ list_components($table_type) │
└──────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────┐
│ │
↓ ↓
┌─────────────────────┐ ┌─────────────────────┐
│ TABLA: DEFAULTS │ │ TABLA: COMPONENTS │
├─────────────────────┤ ├─────────────────────┤
│ wp_apus_theme_ │ │ wp_apus_theme_ │
│ components_defaults │ │ components │
├─────────────────────┤ ├─────────────────────┤
│ PROPÓSITO: │ │ PROPÓSITO: │
│ Valores por defecto │ │ Personalizaciones │
│ del tema │ │ del usuario │
├─────────────────────┤ ├─────────────────────┤
│ ESCRITURA: │ │ ESCRITURA: │
│ Algoritmo │ │ Admin Panel │
│ /implementar- │ │ (Settings Manager) │
│ componente-admin │ │ │
├─────────────────────┤ ├─────────────────────┤
│ LECTURA: │ │ LECTURA: │
│ Settings Manager │ │ Settings Manager │
│ Frontend │ │ Frontend │
└─────────────────────┘ └─────────────────────┘
```
---
## 🗄️ ESTRUCTURA DE TABLAS (Idéntica)
Ambas tablas tienen la misma estructura:
```sql
CREATE TABLE wp_apus_theme_components_defaults (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
component_name VARCHAR(50) NOT NULL,
config_key VARCHAR(100) NOT NULL,
config_value TEXT NOT NULL,
data_type ENUM('string', 'boolean', 'integer', 'json') DEFAULT 'string',
version VARCHAR(10) DEFAULT NULL,
updated_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY component_config (component_name, config_key),
INDEX idx_component (component_name),
INDEX idx_updated (updated_at)
);
-- Misma estructura para wp_apus_theme_components
```
---
## 🔄 FLUJO DE DATOS
### 1. ESCRITURA DE DEFAULTS (Algoritmo)
```php
// Algoritmo escribe defaults a tabla defaults
$db_manager = new APUS_DB_Manager();
$db_manager->save_config(
'top_bar', // component_name
'enabled', // config_key
'1', // config_value
'boolean', // data_type
'2.0.0', // version
'defaults' // table_type ← IMPORTANTE
);
```
### 2. LECTURA DE DEFAULTS (Settings Manager)
```php
// Settings Manager lee defaults
public function get_defaults() {
$db_manager = new APUS_DB_Manager();
$component_names = $db_manager->list_components('defaults');
$defaults = array(
'version' => APUS_ADMIN_PANEL_VERSION,
'components' => array()
);
foreach ($component_names as $component_name) {
$defaults['components'][$component_name] =
$db_manager->get_config($component_name, null, 'defaults');
}
return $defaults;
}
```
### 3. ESCRITURA DE PERSONALIZACIONES (Admin Panel)
```php
// Settings Manager guarda personalizaciones en wp_options
public function save_settings($data) {
// Validar y sanitizar...
$sanitized['version'] = APUS_ADMIN_PANEL_VERSION;
$sanitized['updated_at'] = current_time('mysql');
// Guardar en wp_options
update_option(self::OPTION_NAME, $sanitized, false);
return array('success' => true);
}
```
### 4. MERGE DE DATOS (Settings Manager)
```php
// Combinar defaults + personalizaciones
public function get_settings() {
$settings = get_option(self::OPTION_NAME, array());
$defaults = $this->get_defaults();
// wp_parse_args combina: personalizaciones sobrescriben defaults
return wp_parse_args($settings, $defaults);
}
```
### 5. LECTURA EN FRONTEND
```php
// Frontend obtiene datos combinados
$settings_manager = new APUS_Settings_Manager();
$all_settings = $settings_manager->get_settings();
// Ejemplo: Top Bar
$top_bar_config = $all_settings['components']['top_bar'];
echo $top_bar_config['message_text']; // Default o personalizado
```
---
## 🎯 DECISIÓN ARQUITECTÓNICA: OPCIÓN A
**Personalizaciones se guardan en:** `wp_options` (opción `apus_theme_settings`)
### ✅ Ventajas Opción A (Implementada):
- Más simple y directo
- Ya implementado y funcionando
- Compatible con Settings Manager actual
- Fácil de migrar/exportar (una sola opción)
- Respaldado automáticamente con wp_options
### ❌ Opción B Descartada:
- Guardar personalizaciones en `wp_apus_theme_components`
- Más complejo
- Requeriría cambios extensos en Settings Manager
- No aporta beneficios claros sobre Opción A
---
## 📝 CLASES Y RESPONSABILIDADES
### `APUS_DB_Manager`
**Ubicación:** `admin/includes/class-db-manager.php`
**Responsabilidades:**
- ✅ Crear ambas tablas (components y defaults)
- ✅ CRUD en tabla defaults (algoritmo escribe)
- ✅ CRUD en tabla components (si se necesita en futuro)
- ✅ Parsear tipos de datos (boolean, integer, json, string)
**Métodos principales:**
```php
get_table_name($table_type = 'components')
create_tables()
table_exists($table_type = 'components')
save_config($component, $key, $value, $data_type, $version, $table_type = 'components')
get_config($component, $key = null, $table_type = 'components')
delete_config($component, $key = null, $table_type = 'components')
list_components($table_type = 'components')
```
### `APUS_Settings_Manager`
**Ubicación:** `admin/includes/class-settings-manager.php`
**Responsabilidades:**
- ✅ Leer defaults desde tabla defaults (vía DB Manager)
- ✅ Leer personalizaciones desde wp_options
- ✅ Combinar defaults + personalizaciones (merge)
- ✅ Guardar personalizaciones en wp_options
- ✅ Validar datos (vía Validator)
- ✅ Sanitizar datos
- ✅ AJAX endpoints (get/save)
**Métodos principales:**
```php
get_settings() // Retorna: defaults + personalizaciones
get_defaults() // Lee desde tabla defaults
save_settings($data) // Guarda en wp_options
ajax_get_settings() // AJAX endpoint
ajax_save_settings() // AJAX endpoint
```
---
## 🚀 ESTADO ACTUAL
### ✅ COMPLETADO:
1. ✅ APUS_DB_Manager mejorado para soportar ambas tablas
2. ✅ Ambas tablas creadas (vacías)
3. ✅ Settings Manager integrado con DB Manager
4. ✅ get_defaults() lee desde tabla defaults
5. ✅ Menú admin movido a nivel superior en sidebar
6. ✅ Sistema listo para leer/escribir datos
### ⏳ PENDIENTE:
1. ⏳ Poblar tabla defaults con datos de prueba
2. ⏳ Probar lectura de defaults
3. ⏳ Probar guardar personalizaciones
4. ⏳ Probar merge defaults + personalizaciones
5. ⏳ Probar renderizado en frontend
6. ⏳ Modificar algoritmo `/implementar-componente-admin`
---
## 📋 PRÓXIMOS PASOS
### PASO 5.1: Poblar tabla defaults
Insertar datos de prueba del componente Top Bar:
```sql
INSERT INTO wp_apus_theme_components_defaults
(component_name, config_key, config_value, data_type, version, updated_at)
VALUES
('top_bar', 'enabled', '1', 'boolean', '2.0.0', NOW()),
('top_bar', 'message_text', 'Accede a más de 200,000 Análisis...', 'string', '2.0.0', NOW()),
('top_bar', 'show_icon', '1', 'boolean', '2.0.0', NOW()),
('top_bar', 'icon_class', 'bi bi-megaphone-fill', 'string', '2.0.0', NOW()),
('top_bar', 'cta_text', 'Ver Catálogo', 'string', '2.0.0', NOW()),
('top_bar', 'cta_url', '#', 'string', '2.0.0', NOW());
```
### PASO 5.2: Crear script de prueba
Archivo: `admin/test-defaults.php`
```php
<?php
require_once '../../../wp-load.php';
$db_manager = new APUS_DB_Manager();
$settings_manager = new APUS_Settings_Manager();
// Test 1: Leer defaults de DB
echo "=== TEST 1: Leer defaults de tabla ===\n";
$defaults = $settings_manager->get_defaults();
print_r($defaults);
// Test 2: Guardar personalización
echo "\n=== TEST 2: Guardar personalización ===\n";
$custom_data = array(
'components' => array(
'top_bar' => array(
'message_text' => 'Texto personalizado por usuario'
)
)
);
$result = $settings_manager->save_settings($custom_data);
print_r($result);
// Test 3: Leer settings combinados
echo "\n=== TEST 3: Leer settings combinados ===\n";
$all_settings = $settings_manager->get_settings();
print_r($all_settings);
```
---
## 🎓 CONCEPTOS CLAVE
### ¿Por qué UNA clase para ambas tablas?
**Razón:** Ambas tablas tienen estructura idéntica y almacenan el mismo tipo de datos (configuraciones de componentes). La única diferencia es el PROPÓSITO:
- **Defaults:** Valores originales del tema
- **Components:** Personalizaciones del usuario
Usar una sola clase con parámetro `table_type` es más limpio y DRY (Don't Repeat Yourself).
### ¿Dónde se almacenan las personalizaciones?
**wp_options** (opción `apus_theme_settings`)
**¿Por qué no en tabla components?**
- Más simple
- Ya implementado
- Fácil de migrar/exportar
- La tabla `components` queda disponible para uso futuro si se necesita
### ¿Cómo funciona el merge?
```php
wp_parse_args($personalizaciones, $defaults)
```
Resultado:
- Si existe personalización para una clave → usa personalización
- Si NO existe personalización → usa default
- Ejemplo:
- Default: `message_text = "Accede a más de 200,000..."`
- Personalización: `message_text = "Texto personalizado"`
- **Resultado:** `"Texto personalizado"`
---
## 🔍 VERIFICACIÓN DE ARQUITECTURA
### Checklist pre-modificación de algoritmo:
- ✅ Existe `APUS_DB_Manager` con soporte para ambas tablas
- ✅ DB Manager puede leer/escribir en tabla defaults
- ✅ Settings Manager usa DB Manager para get_defaults()
- ✅ Settings Manager guarda personalizaciones en wp_options
- ✅ Ambas tablas existen en la base de datos
- ⏳ Tabla defaults tiene datos de prueba
- ⏳ Tests de lectura funcionan
- ⏳ Tests de escritura funcionan
- ⏳ Frontend renderiza correctamente
---
**Última actualización:** 2025-01-14
**Estado:** ARQUITECTURA IMPLEMENTADA - LISTO PARA TESTING

View File

@@ -1,784 +0,0 @@
# PLAN DE ACCIÓN: CORRECCIÓN DE DEFAULTS HARDCODEADOS
**Fecha inicio:** _[Pendiente]_
**Fecha fin:** _[Pendiente]_
**Estado:** 🔴 NO INICIADO
---
## 📋 OBJETIVO
Eliminar defaults hardcodeados del código y establecer tabla `wp_apus_theme_components_defaults` como única fuente de verdad.
---
## ⏱️ TIEMPO ESTIMADO TOTAL
- **FASE 1:** 2-3 horas (Limpiar código actual)
- **FASE 2:** 1 hora (Crear tabla defaults)
- **FASE 3:** 3-4 horas (Corregir algoritmo)
- **TOTAL:** 6-8 horas
---
## 🔄 ESTADO DEL PLAN
```
FASE 1: Limpiar Código Actual [ ] 0/15 pasos completados
FASE 2: Crear Tabla Defaults [ ] 0/4 pasos completados
FASE 3: Corregir Algoritmo [ ] 0/8 pasos completados
```
**Progreso total:** 0/27 pasos (0%)
---
# FASE 1: LIMPIAR CÓDIGO ACTUAL
**Objetivo:** Eliminar código mal implementado antes de corregir algoritmo
**Duración estimada:** 2-3 horas
---
## PASO 1.1: Backup de Código Actual
**Duración:** 5 min
- [ ] Crear branch de backup: `git checkout -b backup-antes-limpieza`
- [ ] Hacer commit de estado actual: `git commit -am "backup: estado antes de limpieza de defaults"`
- [ ] Push del backup: `git push origin backup-antes-limpieza`
- [ ] Volver a main: `git checkout main`
- [ ] Crear branch de trabajo: `git checkout -b fix/limpiar-defaults-hardcodeados`
**Verificación:** Branch `backup-antes-limpieza` existe en GitHub
---
## PASO 1.2: Listar Archivos a Eliminar del Admin Panel
**Duración:** 10 min
- [ ] Ejecutar: `dir admin\assets\js\component-*.js 2>nul` (listar JS componentes)
- [ ] Ejecutar: `dir admin\assets\css\component-*.css 2>nul` (listar CSS componentes)
- [ ] Ejecutar: `dir admin\components\component-*.php 2>nul` (listar PHP componentes)
- [ ] Ejecutar: `dir admin\includes\sanitizers\class-*-sanitizer.php 2>nul` (listar sanitizers)
- [ ] Documentar lista de archivos encontrados abajo
**Archivos encontrados:**
```
JS:
-
CSS:
-
PHP Componentes:
-
Sanitizers:
-
```
---
## PASO 1.3: Eliminar Archivos JS de Componentes
**Duración:** 5 min
- [ ] Eliminar: `admin/assets/js/component-navbar.js` (si existe)
- [ ] Eliminar: `admin/assets/js/component-topbar.js` (si existe)
- [ ] Eliminar: `admin/assets/js/component-hero.js` (si existe)
- [ ] Eliminar: otros archivos `component-*.js` listados arriba
- [ ] Verificar que NO quedan archivos: `dir admin\assets\js\component-*.js 2>nul`
**Archivos eliminados:** _[Anotar aquí]_
---
## PASO 1.4: Eliminar Archivos CSS de Componentes
**Duración:** 5 min
- [ ] Eliminar: `admin/assets/css/component-navbar.css` (si existe)
- [ ] Eliminar: `admin/assets/css/component-topbar.css` (si existe)
- [ ] Eliminar: `admin/assets/css/component-hero.css` (si existe)
- [ ] Eliminar: otros archivos `component-*.css` listados arriba
- [ ] Verificar que NO quedan archivos: `dir admin\assets\css\component-*.css 2>nul`
**Archivos eliminados:** _[Anotar aquí]_
---
## PASO 1.5: Eliminar Archivos PHP de Componentes
**Duración:** 5 min
- [ ] Eliminar: `admin/components/component-navbar.php` (si existe)
- [ ] Eliminar: `admin/components/component-top-bar.php` (si existe)
- [ ] Eliminar: `admin/components/component-hero.php` (si existe)
- [ ] Eliminar: otros archivos `component-*.php` listados arriba
- [ ] Verificar que NO quedan archivos: `dir admin\components\component-*.php 2>nul`
**Archivos eliminados:** _[Anotar aquí]_
---
## PASO 1.6: Eliminar Sanitizers de Componentes
**Duración:** 5 min
- [ ] Eliminar: `admin/includes/sanitizers/class-topbar-sanitizer.php` (si existe)
- [ ] Eliminar: `admin/includes/sanitizers/class-navbar-sanitizer.php` (si existe)
- [ ] Eliminar: otros archivos `class-*-sanitizer.php` listados arriba
- [ ] Verificar que NO quedan archivos: `dir admin\includes\sanitizers\class-*-sanitizer.php 2>nul`
**Archivos eliminados:** _[Anotar aquí]_
---
## PASO 1.7: Limpiar class-admin-menu.php
**Duración:** 10 min
**Archivo:** `admin/includes/class-admin-menu.php`
- [ ] Leer el archivo completo
- [ ] Identificar líneas que encolaron CSS de componentes (wp_enqueue_style para component-*.css)
- [ ] Identificar líneas que encolaron JS de componentes (wp_enqueue_script para component-*.js)
- [ ] Eliminar todas las líneas encontradas
- [ ] Verificar que método `enqueue_assets()` solo encola archivos del core (admin-panel.css, admin-app.js)
**Líneas eliminadas:** _[Anotar números de línea]_
---
## PASO 1.8: Limpiar admin/pages/main.php (Parte 1: Analizar)
**Duración:** 15 min
**Archivo:** `admin/pages/main.php`
- [ ] Leer el archivo completo
- [ ] Buscar secciones de tabs de navegación (ej: Top Bar, Navbar, etc.)
- [ ] Buscar secciones de tab-pane con formularios de componentes
- [ ] Documentar números de línea a eliminar abajo
**Secciones encontradas:**
```
Tabs navegación:
Líneas: _____ a _____
Tab-pane Top Bar:
Líneas: _____ a _____
Tab-pane Navbar:
Líneas: _____ a _____
Otros:
Líneas: _____ a _____
```
---
## PASO 1.9: Limpiar admin/pages/main.php (Parte 2: Eliminar)
**Duración:** 10 min
**Archivo:** `admin/pages/main.php`
Usando los rangos de líneas identificados en PASO 1.8:
- [ ] Eliminar sección de tab navegación de componentes
- [ ] Eliminar sección tab-pane de Top Bar
- [ ] Eliminar sección tab-pane de Navbar
- [ ] Eliminar otras secciones documentadas arriba
- [ ] Verificar que NO quedan referencias a componentes
- [ ] Dejar SOLO estructura base del admin panel
**Verificación:** Buscar "top_bar", "navbar", "component" en el archivo - NO debe encontrar nada
---
## PASO 1.10: Limpiar admin/assets/js/admin-app.js
**Duración:** 15 min
**Archivo:** `admin/assets/js/admin-app.js`
- [ ] Leer el archivo completo
- [ ] Buscar métodos `renderTopBar()`, `renderNavbar()`, etc.
- [ ] Buscar referencias a componentes en método `collectFormData()`
- [ ] Buscar valores hardcodeados tipo: `'Accede a más de 200,000...'`
- [ ] Eliminar todos los métodos y referencias encontradas
- [ ] Verificar que NO quedan fallbacks hardcodeados (ej: `|| 'default value'`)
**Líneas eliminadas:** _[Anotar aquí]_
---
## PASO 1.11: Limpiar class-settings-manager.php (Parte 1)
**Duración:** 10 min
**Archivo:** `admin/includes/class-settings-manager.php`
- [ ] Leer método `get_defaults()` completo
- [ ] Identificar sección de defaults de componentes (top_bar, navbar, etc.)
- [ ] Documentar líneas a eliminar
**Defaults encontrados:**
```
top_bar: Líneas _____ a _____
navbar: Líneas _____ a _____
otros: Líneas _____ a _____
```
---
## PASO 1.12: Limpiar class-settings-manager.php (Parte 2)
**Duración:** 15 min
**Archivo:** `admin/includes/class-settings-manager.php`
- [ ] Eliminar método `get_defaults()` COMPLETO (se reemplazará después)
- [ ] Leer método `sanitize_settings()`
- [ ] Eliminar secciones de sanitización de componentes
- [ ] Verificar que NO quedan referencias a top_bar, navbar, etc.
**Líneas eliminadas:** _[Anotar aquí]_
---
## PASO 1.13: Limpiar Tema (header.php y otros)
**Duración:** 20 min
- [ ] Leer `header.php` completo
- [ ] Buscar código que lea de Settings Manager para componentes
- [ ] Buscar valores hardcodeados duplicados (ej: "Accede a más de 200,000...")
- [ ] Documentar qué encontraste
**Código encontrado en header.php:**
```
Líneas: _____ a _____
Descripción: _______________
```
- [ ] Revisar otros archivos del tema si es necesario
- [ ] Documentar archivos revisados
**Archivos del tema revisados:**
- [ ] header.php
- [ ] footer.php
- [ ] _______
**Decisión:** ¿Eliminar código configurable del tema o dejarlo?
_[Decidir con usuario antes de eliminar]_
---
## PASO 1.14: Limpiar Base de Datos
**Duración:** 5 min
- [ ] Conectar a base de datos (phpMyAdmin o terminal)
- [ ] Ejecutar: `SELECT * FROM wp_apus_theme_components;`
- [ ] Documentar componentes encontrados:
**Componentes en DB:**
```
component_name: ___________
component_name: ___________
```
- [ ] Ejecutar: `DELETE FROM wp_apus_theme_components;` (vaciar tabla)
- [ ] Verificar: `SELECT COUNT(*) FROM wp_apus_theme_components;` (debe ser 0)
**Registros eliminados:** _____
---
## PASO 1.15: Commit de Limpieza
**Duración:** 5 min
- [ ] Ejecutar: `git status` (ver todos los cambios)
- [ ] Ejecutar: `git add .`
- [ ] Ejecutar commit:
```bash
git commit -m "fix: eliminar implementación incorrecta de componentes
- Eliminar archivos JS/CSS/PHP de componentes mal implementados
- Limpiar class-admin-menu.php de encolamiento de componentes
- Limpiar admin/pages/main.php de secciones de componentes
- Limpiar admin-app.js de métodos y defaults hardcodeados
- Limpiar class-settings-manager.php de get_defaults() y sanitizers
- Vaciar tabla wp_apus_theme_components
Preparación para implementar arquitectura correcta con tabla defaults.
Ref: PROBLEMA-DEFAULTS-HARDCODEADOS-ALGORITMO.md"
```
- [ ] Ejecutar: `git push origin fix/limpiar-defaults-hardcodeados`
---
## ✅ CHECKLIST FASE 1 COMPLETA
- [ ] Backup creado en branch separado
- [ ] Archivos de componentes eliminados (JS, CSS, PHP, Sanitizers)
- [ ] class-admin-menu.php limpiado
- [ ] admin/pages/main.php limpiado
- [ ] admin-app.js limpiado
- [ ] class-settings-manager.php limpiado
- [ ] Tema revisado
- [ ] Base de datos vaciada
- [ ] Commit y push realizados
**Estado FASE 1:** ⬜ Pendiente | 🟡 En progreso | ✅ Completada
---
# FASE 2: CREAR TABLA DE DEFAULTS
**Objetivo:** Implementar tabla `wp_apus_theme_components_defaults` en base de datos
**Duración estimada:** 1 hora
---
## PASO 2.1: Crear Script SQL
**Duración:** 10 min
- [ ] Crear archivo: `admin/includes/migrations/create-defaults-table.sql`
- [ ] Copiar SQL de `PROBLEMA-DEFAULTS-HARDCODEADOS-ALGORITMO.md` (líneas 418-437)
- [ ] Verificar sintaxis SQL
**Contenido del archivo:**
```sql
CREATE TABLE IF NOT EXISTS wp_apus_theme_components_defaults (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
component_name VARCHAR(50) NOT NULL COMMENT 'Nombre del componente',
config_key VARCHAR(100) NOT NULL COMMENT 'Clave de configuración',
config_value TEXT NOT NULL COMMENT 'Valor por defecto extraído del tema',
data_type ENUM('string','integer','boolean','array','json') NOT NULL,
version VARCHAR(20) DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_default_config (component_name, config_key),
INDEX idx_component_name (component_name),
INDEX idx_config_key (config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
---
## PASO 2.2: Ejecutar SQL en Base de Datos
**Duración:** 5 min
**Método 1: phpMyAdmin**
- [ ] Abrir phpMyAdmin
- [ ] Seleccionar base de datos del tema
- [ ] Ir a pestaña SQL
- [ ] Copiar contenido de `create-defaults-table.sql`
- [ ] Ejecutar SQL
**Método 2: Terminal/CMD**
- [ ] Conectar a MySQL/MariaDB
- [ ] Ejecutar: `USE nombre_base_datos;`
- [ ] Copiar y ejecutar SQL
**Verificación:**
- [ ] Ejecutar: `SHOW TABLES LIKE 'wp_apus_theme_components_defaults';`
- [ ] Debe retornar la tabla
---
## PASO 2.3: Verificar Estructura de Tabla
**Duración:** 5 min
- [ ] Ejecutar: `DESCRIBE wp_apus_theme_components_defaults;`
- [ ] Verificar columnas:
- [ ] id (BIGINT)
- [ ] component_name (VARCHAR 50)
- [ ] config_key (VARCHAR 100)
- [ ] config_value (TEXT)
- [ ] data_type (ENUM)
- [ ] version (VARCHAR 20)
- [ ] created_at (DATETIME)
- [ ] updated_at (DATETIME)
- [ ] Verificar índices:
- [ ] PRIMARY KEY (id)
- [ ] UNIQUE (component_name, config_key)
- [ ] INDEX (component_name)
- [ ] INDEX (config_key)
---
## PASO 2.4: Commit de Creación de Tabla
**Duración:** 5 min
- [ ] Ejecutar: `git add admin/includes/migrations/create-defaults-table.sql`
- [ ] Ejecutar commit:
```bash
git commit -m "feat(db): crear tabla wp_apus_theme_components_defaults
- Tabla para almacenar valores por defecto de componentes
- Estructura normalizada (un row por campo)
- Índices para optimizar búsquedas
- Script SQL reutilizable en create-defaults-table.sql
Ref: PROBLEMA-DEFAULTS-HARDCODEADOS-ALGORITMO.md"
```
- [ ] Ejecutar: `git push origin fix/limpiar-defaults-hardcodeados`
---
## ✅ CHECKLIST FASE 2 COMPLETA
- [ ] Script SQL creado en `admin/includes/migrations/create-defaults-table.sql`
- [ ] SQL ejecutado en base de datos
- [ ] Tabla `wp_apus_theme_components_defaults` existe
- [ ] Estructura verificada (8 columnas, 3 índices)
- [ ] Commit y push realizados
**Estado FASE 2:** ⬜ Pendiente | 🟡 En progreso | ✅ Completada
---
# FASE 3: CORREGIR ALGORITMO
**Objetivo:** Modificar archivos del algoritmo para usar tabla defaults en lugar de hardcodear valores
**Duración estimada:** 3-4 horas
---
## PASO 3.1: Modificar PASO 12 del Algoritmo (Parte 1: Analizar)
**Duración:** 15 min
**Archivo:** `_planeacion/apus-theme/admin-panel-theme/100-modularizacion-admin/00-algoritmo/12-F03-IMPLEMENTACION-IMPLEMENTAR-ADMIN-JS.md`
- [ ] Leer archivo completo
- [ ] Identificar líneas con objeto `DEFAULT_CONFIG` (aprox líneas 43-51, 169-177)
- [ ] Identificar líneas con fallbacks en método `render()` (aprox líneas 117-129)
- [ ] Identificar líneas con botón reset (aprox líneas 196-204)
- [ ] Documentar cambios necesarios
**Líneas a modificar:**
```
DEFAULT_CONFIG: Líneas _____ a _____
Fallbacks render(): Líneas _____ a _____
Botón reset: Líneas _____ a _____
```
---
## PASO 3.2: Modificar PASO 12 del Algoritmo (Parte 2: Eliminar DEFAULT_CONFIG)
**Duración:** 20 min
**Archivo:** `12-F03-IMPLEMENTACION-IMPLEMENTAR-ADMIN-JS.md`
- [ ] Eliminar sección que instruye crear objeto `DEFAULT_CONFIG`
- [ ] Eliminar ejemplo de código con `const DEFAULT_CONFIG = {...}`
- [ ] Agregar nota: "❌ NO crear objeto DEFAULT_CONFIG - Los defaults vienen de DB vía AJAX"
**Texto a agregar:**
```markdown
## ❌ IMPORTANTE: NO Crear Objeto DEFAULT_CONFIG
**PROHIBIDO crear objeto con defaults hardcodeados en JavaScript.**
Los valores por defecto vienen de la base de datos vía AJAX.
Settings Manager lee de tabla `wp_apus_theme_components_defaults`.
```
---
## PASO 3.3: Modificar PASO 12 del Algoritmo (Parte 3: Corregir Fallbacks)
**Duración:** 20 min
**Archivo:** `12-F03-IMPLEMENTACION-IMPLEMENTAR-ADMIN-JS.md`
- [ ] Modificar sección del método `render()`
- [ ] Eliminar ejemplos con fallbacks: `config.field || 'default value'`
- [ ] Reemplazar por: `config.field` (sin fallback)
- [ ] Agregar nota explicando que AJAX SIEMPRE retorna datos completos (DB + defaults merged)
**Ejemplo ANTES (INCORRECTO):**
```javascript
bgColorInput.value = config.custom_styles?.bg_color || '#000000';
```
**Ejemplo DESPUÉS (CORRECTO):**
```javascript
bgColorInput.value = config.custom_styles?.bg_color;
// NO fallback necesario - Settings Manager ya hace merge con defaults de DB
```
---
## PASO 3.4: Modificar PASO 12 del Algoritmo (Parte 4: Botón Reset)
**Duración:** 15 min
**Archivo:** `12-F03-IMPLEMENTACION-IMPLEMENTAR-ADMIN-JS.md`
- [ ] Modificar sección del botón "Reset to Defaults"
- [ ] Cambiar de `loadConfig(DEFAULT_CONFIG)` a llamada AJAX
- [ ] Agregar código para llamar endpoint que retorna defaults de DB
**Código a agregar:**
```javascript
// Botón Reset to Defaults
resetBtn.addEventListener('click', function() {
if (confirm('¿Restaurar valores por defecto?')) {
// Llamar AJAX para obtener defaults de DB
axios.get(apusAdminData.ajaxUrl, {
params: {
action: 'get_component_defaults',
component: 'component_name',
nonce: apusAdminData.nonce
}
})
.then(response => {
loadConfig(response.data);
// Guardar defaults como config personalizada
saveForm();
});
}
});
```
---
## PASO 3.5: Crear NUEVO PASO en Algoritmo (Poblar Defaults)
**Duración:** 30 min
- [ ] Crear archivo: `_planeacion/.../00-algoritmo/07B-F02-DISENO-POBLAR-DEFAULTS-DB.md`
- [ ] Ubicación: DESPUÉS de PASO 7, ANTES de PASO 8
**Contenido del archivo:**
```markdown
# PASO 7B: POBLAR TABLA DE DEFAULTS
**Prerequisito:** PASO 7 completado (código configurable documentado)
## Objetivo
Insertar valores por defecto del componente en tabla `wp_apus_theme_components_defaults`.
## 7B.1 Leer Valores Extraídos
- Abrir archivo del PASO 6: `03-DOCUMENTACION-ESTRUCTURA-DATOS.md`
- Identificar TODOS los campos con sus valores por defecto
- Valores de textos/URLs: Del código hardcodeado actual
- Valores de colores/estilos: Del CSS original del componente
## 7B.2 Generar Script SQL
Crear archivo: `[componente]/defaults-insert.sql`
Formato:
INSERT INTO wp_apus_theme_components_defaults
(component_name, config_key, config_value, data_type, version)
VALUES
('[component_name]', 'enabled', '1', 'boolean', '2.1.4'),
('[component_name]', '[field1]', '[valor]', 'string', '2.1.4'),
...
## 7B.3 Ejecutar SQL
- Conectar a base de datos
- Ejecutar script SQL
- Verificar: SELECT * FROM wp_apus_theme_components_defaults WHERE component_name='[nombre]';
## 7B.4 Verificar
- [ ] Todos los campos del PASO 6 tienen row en tabla defaults
- [ ] Valores coinciden con los extraídos del código/CSS actual
- [ ] data_type es correcto para cada campo
```
---
## PASO 3.6: Modificar PASO 14 del Algoritmo (Eliminar get_defaults)
**Duración:** 30 min
**Archivo:** `_planeacion/.../00-algoritmo/14-F04-CIERRE-GIT-COMMITS.md`
- [ ] Leer sección "14.4 Modificar Settings Manager (CRÍTICO)"
- [ ] Leer subsección "Modificación 1: Agregar Defaults (línea ~146)"
- [ ] Eliminar TODO el ejemplo del método `get_defaults()` con array hardcodeado (líneas ~88-123)
- [ ] Reemplazar por instrucciones para leer de tabla defaults
**Texto a eliminar:**
```php
public function get_defaults() {
return array(
'version' => APUS_ADMIN_PANEL_VERSION,
'components' => array(
'component_name' => array(
'enabled' => true,
// ... defaults hardcodeados
)
)
);
}
```
**Texto a agregar:**
```markdown
### Modificación: Settings Manager Lee de Tabla Defaults
**❌ NO crear método get_defaults() con array hardcodeado**
Los defaults ya están en tabla `wp_apus_theme_components_defaults` (insertados en PASO 7B).
Settings Manager debe leer de DB, NO tener defaults hardcodeados.
Ver método `get_component_config()` que hace merge automático:
1. Lee config personalizada de `wp_apus_theme_components`
2. Si no existe → Lee defaults de `wp_apus_theme_components_defaults`
```
---
## PASO 3.7: Modificar DB Manager (Agregar get_component_defaults)
**Duración:** 30 min
**Archivo:** `admin/includes/class-db-manager.php`
- [ ] Leer archivo completo
- [ ] Buscar método `get_component($component_name)`
- [ ] Copiar método y modificar para leer de tabla `_defaults`
- [ ] Agregar nuevo método
**Código a agregar:**
```php
/**
* Get component default values from defaults table
*
* @param string $component_name
* @return array
*/
public function get_component_defaults($component_name) {
global $wpdb;
$table_name = $wpdb->prefix . 'apus_theme_components_defaults';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT config_key, config_value, data_type
FROM $table_name
WHERE component_name = %s",
$component_name
),
ARRAY_A
);
if (empty($results)) {
return array();
}
// Convertir rows a array asociativo
$config = array();
foreach ($results as $row) {
$config[$row['config_key']] = $this->cast_value(
$row['config_value'],
$row['data_type']
);
}
return $config;
}
/**
* Cast value to correct type based on data_type
*
* @param mixed $value
* @param string $type
* @return mixed
*/
private function cast_value($value, $type) {
switch ($type) {
case 'boolean':
return (bool) $value;
case 'integer':
return (int) $value;
case 'array':
case 'json':
return json_decode($value, true);
default:
return $value;
}
}
```
---
## PASO 3.8: Modificar Settings Manager (get_component_config)
**Duración:** 20 min
**Archivo:** `admin/includes/class-settings-manager.php`
- [ ] Buscar método `get_component_config($component_name)`
- [ ] Modificar para leer de tabla defaults si no hay config personalizada
**Código ANTES:**
```php
public function get_component_config($component_name) {
$settings = $this->get_settings();
$defaults = $this->get_defaults(); // ← Método hardcodeado
return wp_parse_args(
$settings['components'][$component_name] ?? array(),
$defaults['components'][$component_name] ?? array()
);
}
```
**Código DESPUÉS:**
```php
public function get_component_config($component_name) {
// 1. Intentar leer config personalizada
$user_config = $this->db_manager->get_component($component_name);
if (!empty($user_config)) {
return $user_config; // Usuario ya personalizó
}
// 2. Si no hay personalización, leer defaults de tabla
$defaults = $this->db_manager->get_component_defaults($component_name);
if (!empty($defaults)) {
return $defaults; // Usar defaults de DB
}
// 3. Error: componente sin defaults
error_log("APUS Theme: No defaults found for component: {$component_name}");
return array();
}
```
---
## ✅ CHECKLIST FASE 3 COMPLETA
- [ ] PASO 12 modificado (eliminado DEFAULT_CONFIG y fallbacks)
- [ ] PASO 7B creado (poblar defaults en DB)
- [ ] PASO 14 modificado (eliminado get_defaults hardcodeado)
- [ ] DB Manager modificado (agregado get_component_defaults)
- [ ] Settings Manager modificado (lee de tabla defaults)
- [ ] Todos los cambios commiteados
**Estado FASE 3:** ⬜ Pendiente | 🟡 En progreso | ✅ Completada
---
## 🎯 RESUMEN FINAL
Una vez completadas las 3 fases:
### ✅ Lo que se logró:
1. Código actual limpiado (sin implementaciones incorrectas)
2. Tabla `wp_apus_theme_components_defaults` creada y funcionando
3. Algoritmo corregido (sin defaults hardcodeados en JS/PHP)
4. DB Manager y Settings Manager leen de tabla defaults
### 🚀 Próximos pasos:
1. Ejecutar algoritmo CORREGIDO para primer componente (ej: Navbar)
2. Pasos 1-13: Generar documentación
3. PASO 7B: Insertar defaults en DB
4. PASO 14: Implementar código real
5. PASO 15-16: Testing y cierre
---
**Última actualización:** _[Fecha]_
**Estado general:** ⬜ Pendiente | 🟡 En progreso | ✅ Completado

View File

@@ -1,449 +0,0 @@
# PLAN: Preparación del Tema para Arquitectura con Base de Datos
**Fecha:** 2025-01-14
**Objetivo:** Preparar el tema Apus para trabajar con la nueva arquitectura de defaults en base de datos
---
## 🎯 CONTEXTO
### Arquitectura actual (PROBLEMÁTICA):
```
┌─────────────────────────────────────────┐
│ Settings Manager │
│ ├─ get_defaults() → VACÍO ❌ │
│ ├─ get_settings() → wp_options ✅ │
│ └─ save_settings() → wp_options ✅ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ DB Manager │
│ ├─ Tabla: wp_apus_theme_components │
│ ├─ get_config() ✅ │
│ └─ save_config() ✅ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Tabla: wp_apus_theme_components_defaults│
│ ├─ Creada ✅ │
│ ├─ Sin clase para leer ❌ │
│ └─ Vacía (sin datos) ❌ │
└─────────────────────────────────────────┘
```
### Arquitectura deseada (OBJETIVO):
```
┌──────────────────────────────────────────────────────────┐
│ DEFAULTS MANAGER (NUEVO) │
│ ├─ Tabla: wp_apus_theme_components_defaults │
│ ├─ get_defaults($component_name) → leer tabla ✅ │
│ └─ Operación: SOLO LECTURA (escritura desde algoritmo) │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ SETTINGS MANAGER (MODIFICADO) │
│ ├─ get_defaults() → usa Defaults Manager ✅ │
│ ├─ get_settings() → merge defaults + personalizaciones │
│ └─ save_settings() → guarda SOLO personalizaciones │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ PERSONALIZACIÓN: ¿Dónde guardar? │
│ Opción A: wp_options (actual) ✅ SIMPLE │
│ Opción B: wp_apus_theme_components ❓ COMPLEJO │
└──────────────────────────────────────────────────────────┘
```
---
## 📋 FASE 1: ANÁLISIS DE ARQUITECTURA ACTUAL
### PASO 1.1: Verificar qué contiene cada tabla
**Duración:** 5 min
**Ejecutar queries:**
```sql
-- Verificar tabla de defaults (debe estar vacía)
SELECT COUNT(*) FROM wp_apus_theme_components_defaults;
SELECT * FROM wp_apus_theme_components_defaults LIMIT 5;
-- Verificar tabla de componentes
SELECT COUNT(*) FROM wp_apus_theme_components;
SELECT component_name, COUNT(*) as configs
FROM wp_apus_theme_components
GROUP BY component_name;
-- Verificar wp_options
SELECT option_value FROM wp_options WHERE option_name = 'apus_theme_settings';
```
**Documentar:**
- ¿Hay datos en `wp_apus_theme_components`?
- ¿Hay datos en `wp_options` (apus_theme_settings)?
- ¿Cuál se está usando actualmente?
---
### PASO 1.2: Analizar flujo actual de datos
**Duración:** 10 min
**Revisar archivos:**
- [ ] `class-settings-manager.php` - ¿De dónde lee? ¿Dónde guarda?
- [ ] `class-db-manager.php` - ¿Qué tabla maneja?
- [ ] Admin Panel JS - ¿Qué endpoints AJAX llama?
- [ ] Frontend (header.php, etc.) - ¿De dónde obtiene datos para renderizar?
**Documentar:**
```
Flujo actual:
1. Usuario abre Admin Panel → AJAX: apus_get_settings → ???
2. Usuario guarda cambios → AJAX: apus_save_settings → ???
3. Frontend renderiza componente → Función: ??? → Datos de: ???
```
---
### PASO 1.3: Identificar conflictos
**Duración:** 5 min
**Preguntas a responder:**
- ¿Por qué existen 2 tablas (`wp_apus_theme_components` y `wp_apus_theme_components_defaults`)?
- ¿Cuál es el propósito de cada una?
- ¿Se están usando ambas o solo una?
- ¿Hay duplicación de datos?
**Decisión arquitectónica:**
```
OPCIÓN A (RECOMENDADA):
├─ wp_apus_theme_components_defaults → DEFAULTS (solo lectura tema)
├─ wp_options (apus_theme_settings) → PERSONALIZACIONES (lectura/escritura)
└─ ELIMINAR: wp_apus_theme_components (no usar)
OPCIÓN B:
├─ wp_apus_theme_components_defaults → DEFAULTS (solo lectura tema)
├─ wp_apus_theme_components → PERSONALIZACIONES (lectura/escritura)
└─ ELIMINAR: wp_options (apus_theme_settings) (no usar)
```
---
## 📋 FASE 2: CORREGIR UBICACIÓN DEL MENÚ ADMIN
### PASO 2.0: Mover menú a nivel superior del sidebar
**Duración:** 10 min
**PROBLEMA ACTUAL:**
- ❌ "APUs Theme Settings" está bajo menú "Apariencia"
- ❌ Se usa `add_theme_page()` en `class-admin-menu.php`
**SOLUCIÓN:**
- ✅ Crear menú propio en sidebar izquierdo (nivel superior)
- ✅ Cambiar a `add_menu_page()` en `class-admin-menu.php`
**Modificar:** `admin/includes/class-admin-menu.php`
**ANTES (línea 28-36):**
```php
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
);
}
```
**DESPUÉS:**
```php
public function add_menu_page() {
add_menu_page(
'Apus Theme Options', // Page title
'Apus Theme', // Menu title
'manage_options', // Capability
'apus-theme-settings', // Menu slug
array($this, 'render_admin_page'), // Callback
'dashicons-admin-generic', // Icon (WordPress Dashicon)
61 // Position (61 = después de Settings que es 80)
);
}
```
**IMPORTANTE - Ubicación esperada en sidebar:**
```
Dashboard
Posts
Media
Pages
Comments
...
Settings
Apus Theme ← AL MISMO NIVEL que Settings (NO dentro)
```
**Posiciones de menús WordPress:**
```
2 - Dashboard
4 - Separator
5 - Posts
10 - Media
15 - Links
20 - Pages
25 - Comments
59 - Separator
60 - Appearance
65 - Plugins
70 - Users
75 - Tools
80 - Settings
100 - Separator
```
**Usar posición 61** para que quede:
- Después de "Appearance" (60)
- Antes de "Plugins" (65)
- **AL MISMO NIVEL** que todos los menús principales
**Verificar:**
- [ ] Menú aparece en sidebar izquierdo
- [ ] Menú está AL MISMO NIVEL que "Settings", "Dashboard", etc.
- [ ] Menú NO está dentro de ningún otro menú
- [ ] Ícono es visible
- [ ] Título es "Apus Theme"
- [ ] Al hacer click abre el panel de configuración
**Nota:** También renombrar el método de `add_menu_page()` a algo más descriptivo si es necesario, ya que ahora usa la función `add_menu_page()` de WordPress.
---
## 📋 FASE 3: CREAR DEFAULTS MANAGER
### PASO 3.1: Crear clase Defaults Manager
**Duración:** 20 min
**Archivo:** `admin/includes/class-defaults-manager.php`
**Métodos necesarios:**
```php
class APUS_Defaults_Manager {
// Leer todos los defaults de un componente
public function get_component_defaults($component_name);
// Leer un default específico
public function get_default($component_name, $config_key);
// Listar componentes con defaults
public function list_components();
// Verificar si existen defaults para componente
public function has_defaults($component_name);
}
```
**Responsabilidades:**
- ✅ LEER de `wp_apus_theme_components_defaults`
- ❌ NO ESCRIBIR (escritura solo desde algoritmo)
- ✅ Parsear tipos de datos (boolean, integer, json, array)
- ✅ Cachear resultados (opcional, para performance)
---
### PASO 3.2: Integrar Defaults Manager en init.php
**Duración:** 5 min
**Modificar:** `admin/init.php`
```php
// Cargar Defaults Manager
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-defaults-manager.php';
// Inicializar
$defaults_manager = new APUS_Defaults_Manager();
```
---
### PASO 3.3: Modificar Settings Manager para usar Defaults Manager
**Duración:** 15 min
**Modificar:** `admin/includes/class-settings-manager.php`
**Antes:**
```php
public function get_defaults() {
return array(
'version' => APUS_ADMIN_PANEL_VERSION,
'components' => array()
);
}
```
**Después:**
```php
public function get_defaults() {
$defaults_manager = new APUS_Defaults_Manager();
$components = $defaults_manager->list_components();
$defaults = array(
'version' => APUS_ADMIN_PANEL_VERSION,
'components' => array()
);
foreach ($components as $component_name) {
$defaults['components'][$component_name] =
$defaults_manager->get_component_defaults($component_name);
}
return $defaults;
}
```
---
## 📋 FASE 4: DECISIÓN SOBRE PERSONALIZACIONES
### PASO 4.1: Evaluar opciones
**Duración:** 10 min
**Opción A: Usar wp_options (RECOMENDADA)**
- ✅ Más simple
- ✅ Ya implementado
- ✅ Funciona con Settings Manager actual
- ❌ Menos estructurado
**Opción B: Usar wp_apus_theme_components**
- ✅ Más estructurado
- ✅ Usa DB Manager
- ❌ Requiere más cambios
- ❌ Más complejo
**Decisión:** [A COMPLETAR POR USUARIO]
---
### PASO 4.2: Implementar según decisión
**Duración:** Variable
**Si Opción A (wp_options):**
- [ ] Mantener Settings Manager como está
- [ ] Solo agregar get_defaults() con Defaults Manager
- [ ] save_settings() sigue guardando en wp_options
**Si Opción B (wp_apus_theme_components):**
- [ ] Modificar Settings Manager para usar DB Manager
- [ ] Cambiar save_settings() para guardar en tabla
- [ ] Cambiar get_settings() para leer de tabla
- [ ] Eliminar uso de wp_options
---
## 📋 FASE 5: TESTING Y VALIDACIÓN
### PASO 5.1: Poblar tabla de defaults con datos de prueba
**Duración:** 10 min
**Insertar defaults de Top Bar:**
```sql
INSERT INTO wp_apus_theme_components_defaults
(component_name, config_key, config_value, data_type, version)
VALUES
('top_bar', 'enabled', '1', 'boolean', '2.0.0'),
('top_bar', 'message_text', 'Accede a más de 200,000...', 'string', '2.0.0'),
-- ... más configs
```
---
### PASO 5.2: Probar lectura de defaults
**Duración:** 10 min
**Crear script de prueba:** `admin/test-defaults.php`
```php
<?php
require_once 'wp-load.php';
$defaults_manager = new APUS_Defaults_Manager();
$settings_manager = new APUS_Settings_Manager();
// Test 1: Leer defaults de Top Bar
$top_bar_defaults = $defaults_manager->get_component_defaults('top_bar');
echo "Top Bar Defaults:\n";
print_r($top_bar_defaults);
// Test 2: Obtener settings completos (defaults + personalizaciones)
$all_settings = $settings_manager->get_settings();
echo "\nAll Settings:\n";
print_r($all_settings);
```
---
### PASO 5.3: Probar guardar personalizaciones
**Duración:** 10 min
**Test manual:**
1. Abrir Admin Panel
2. Modificar configuración de componente
3. Guardar cambios
4. Verificar que se guardó en lugar correcto (wp_options o tabla)
5. Recargar Admin Panel
6. Verificar que muestra personalización + defaults
---
### PASO 5.4: Probar renderizado en frontend
**Duración:** 10 min
**Verificar:**
1. Frontend muestra defaults cuando no hay personalizaciones
2. Frontend muestra personalizaciones cuando las hay
3. Personalización sobrescribe default (merge correcto)
---
## 📋 FASE 6: DOCUMENTACIÓN
### PASO 6.1: Documentar arquitectura final
**Duración:** 15 min
**Crear:** `admin/ARQUITECTURA-DATOS.md`
**Contenido:**
- Diagrama de flujo de datos
- Explicación de cada tabla
- Explicación de cada clase
- Cómo se combinan defaults + personalizaciones
- Ejemplos de uso
---
## ✅ CHECKLIST FINAL
Antes de modificar el algoritmo, verificar:
- [ ] Existe `class-defaults-manager.php`
- [ ] Defaults Manager puede leer de `wp_apus_theme_components_defaults`
- [ ] Settings Manager usa Defaults Manager en `get_defaults()`
- [ ] Se decidió dónde guardar personalizaciones (wp_options vs tabla)
- [ ] Tabla de defaults tiene datos de prueba
- [ ] Tests de lectura funcionan
- [ ] Tests de guardar funcionan
- [ ] Frontend renderiza correctamente
- [ ] Arquitectura está documentada
---
## 🚀 SIGUIENTE PASO
Una vez completado este plan:
- ✅ TEMA LISTO para procesar datos de BD
- ✅ Puede leer defaults
- ✅ Puede combinar con personalizaciones
- ✅ Puede guardar personalizaciones
- ✅ Puede renderizar en frontend
**ENTONCES:**
→ Proceder a modificar el algoritmo `/implementar-componente-admin`

View File

@@ -1,670 +0,0 @@
# PROBLEMA: Defaults Hardcodeados en Algoritmo de Modularización
**Fecha:** 2025-01-13
**Estado:** 🔴 EN INVESTIGACIÓN
**Prioridad:** ALTA
---
## 📋 CONTEXTO
### Situación Actual
El tema WordPress tiene valores hardcodeados en múltiples archivos:
```
wp-content/themes/apus-theme/
├── *.php → Valores hardcodeados
├── *.html → Valores hardcodeados
├── assets/
├── css/ → Valores hardcodeados
└── js/ → Valores hardcodeados
```
### Objetivo del Sistema
El **Admin Panel** debe permitir personalizar la mayoría de valores que actualmente están hardcodeados.
### Sistema de Persistencia Disponible
**✅ Ya existe tabla personalizada:** `wp_apus_theme_components`
**Ubicación:** Base de datos WordPress
**Documentación:** Ver `ANALISIS-ESTRUCTURA-ADMIN.md`
**Estructura:**
```sql
CREATE TABLE wp_apus_theme_components (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
component_name VARCHAR(50) NOT NULL, -- 'topbar', 'navbar', 'hero', etc.
config_key VARCHAR(100) NOT NULL, -- 'message_text', 'bg_color', etc.
config_value TEXT NOT NULL, -- Valor del campo
data_type ENUM('string','integer','boolean','array','json'),
version VARCHAR(20),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_config (component_name, config_key)
)
```
---
## 🔴 PROBLEMA IDENTIFICADO
### Descripción
El algoritmo de modularización ubicado en:
```
_planeacion/apus-theme/admin-panel-theme/100-modularizacion-admin/00-algoritmo/
```
**❌ PROBLEMA CRÍTICO ENCONTRADO EN PASO 12:**
- El algoritmo instruye crear un objeto `DEFAULT_CONFIG` en JavaScript con TODOS los valores por defecto hardcodeados
- Esto viola el principio de Single Source of Truth
- Los defaults deberían venir de PHP (Settings Manager) vía AJAX, NO estar duplicados en JavaScript
### Evidencia del Problema
**Ubicación:** `00-algoritmo/12-F03-IMPLEMENTACION-IMPLEMENTAR-ADMIN-JS.md`
**Líneas 43-51 y 169-177:**
```javascript
const DEFAULT_CONFIG = {
enabled: true,
campo1: 'valor default',
custom_styles: {
background_color: '#0E2337',
// ... todos los campos del PASO 6
}
};
```
**Líneas 117-129 (método render()):**
```javascript
document.getElementById('topBarIconClass').value = config.icon_class || '';
document.getElementById('topBarShowLink').checked = config.show_link || false;
const bgColorInput = document.getElementById('topBarBgColor');
bgColorInput.value = config.custom_styles?.bg_color || '#000000'; // ← Fallback hardcodeado
```
### Por Qué es un Problema
1. **Duplicación de defaults:**
- PHP Settings Manager tiene defaults
- JavaScript TAMBIÉN tiene defaults (duplicado)
- Tabla DB puede tener defaults (triplicado si se hace seed)
2. **Violación de Single Source of Truth:**
- Cambiar un default requiere editar JavaScript Y PHP
- Alto riesgo de inconsistencias
3. **Arquitectura incorrecta:**
- JavaScript NO debería tener fallbacks porque `get_settings()` de PHP ya hace merge con defaults
- AJAX siempre retorna datos completos (DB + defaults merged)
---
## ❓ PREGUNTAS PARA INVESTIGACIÓN
### PREGUNTA 1: Ubicación del Problema ✅ RESPONDIDA
**¿En cuál(es) paso(s) del algoritmo se guardan valores en archivos JS?**
- [ ] PASO 1: Crear issue
- [ ] PASO 2: Análisis con Serena
- [ ] PASO 3: Crear estructura de documentación
- [ ] PASO 4: Documentar código real
- [ ] PASO 5: Documentar campos configurables
- [ ] PASO 6: Estructura JSON
- [ ] PASO 7: Documentar código configurable
- [ ] PASO 8: Referencia AJAX
- [ ] PASO 9: Plantilla estructura HTML
- [ ] PASO 10: Ejemplos componentes
- [ ] PASO 11: Ensamblar admin HTML
- [X] **PASO 12: Implementar admin JS** ← ❌ AQUÍ ESTÁ EL PROBLEMA
- [ ] PASO 13: CSS admin panel
- [ ] PASO 14: Git commits
- [ ] PASO 15: Testing
- [ ] PASO 16: Cerrar issue
**✅ Respuesta encontrada:**
- **PASO 12** instruye crear objeto `DEFAULT_CONFIG` en JavaScript con todos los defaults hardcodeados
- **Líneas problemáticas:** 43-51, 169-177, 117-129, 223-229
- **Archivos afectados:** `component-[nombre].js` (uno por cada componente)
---
### PREGUNTA 2: Archivos JS Afectados ✅ RESPONDIDA
**¿Qué archivos JavaScript están siendo modificados con valores hardcodeados?**
Opciones probables:
- [X] `admin/assets/js/admin-app.js` ← Fallbacks en método `render()`
- [X] `admin/assets/js/component-navbar.js` ← Si se siguió PASO 12
- [X] `admin/assets/js/component-*.js` (otros componentes) ← Si se siguió PASO 12
- [ ] Archivos JS del tema (fuera de admin)
- [ ] Otro: _______________
**✅ Respuesta encontrada:**
- **Patrón del algoritmo:** CADA componente debe tener su propio archivo `component-[nombre].js`
- **Cada archivo debe tener:** Objeto `DEFAULT_CONFIG` con todos los defaults
- **Ubicación:** `admin/assets/js/component-*.js`
- **Comprobación en código actual:** `admin-app.js:357` tiene fallback hardcodeado para Top Bar
---
### PREGUNTA 3: Tipo de Valores ✅ RESPONDIDA
**¿Qué tipo de valores por defecto se están guardando en JS?**
Opciones:
- [X] Textos (ej: "Accede a más de 200,000...")
- [X] URLs (ej: "/catalogo")
- [X] Colores (ej: "#0E2337")
- [X] Iconos (ej: "bi bi-megaphone-fill")
- [X] Configuraciones booleanas (ej: enabled: true)
- [X] **Todos los anteriores** ← CORRECTO
- [ ] Otro: _______________
**✅ Respuesta encontrada:**
- Según PASO 12 líneas 169-177, el objeto `DEFAULT_CONFIG` debe contener **TODOS** los campos del PASO 6
- Esto incluye: strings, booleans, URLs, colores (custom_styles), números, selects
- **Ejemplo real encontrado:** `admin-app.js:357` tiene `'Accede a más de 200,000...'` hardcodeado
---
### PREGUNTA 4: Propósito de los Valores en JS ✅ RESPONDIDA
**¿Para qué se usan esos valores hardcodeados en JavaScript?**
Opciones:
- [X] **Fallbacks cuando AJAX no retorna datos** ← USO PRINCIPAL
- [X] Valores iniciales al renderizar formulario
- [ ] Placeholders de campos de formulario
- [ ] Valores de preview/demo
- [ ] No estoy seguro
- [X] **Botón "Reset to Defaults"** ← USO SECUNDARIO
**✅ Respuesta encontrada:**
- **Uso 1 (líneas 117-129):** Fallbacks en método `render()``config.field || 'default'`
- **Uso 2 (líneas 196-204):** Botón reset llama `loadConfig(DEFAULT_CONFIG)`
- **Problema:** ❌ Los fallbacks son INNECESARIOS porque Settings Manager ya hace merge con defaults
---
### PREGUNTA 5: Comportamiento Esperado ✅ RESPONDIDA
**¿Cómo DEBERÍAN manejarse los valores por defecto?**
Tu visión:
- [X] Guardar en tabla `wp_apus_theme_components_defaults` (NUEVA tabla, NO la misma)
- [X] Formato: Normalizado - un INSERT por campo (Opción A)
- [X] JavaScript NUNCA debe tener defaults hardcodeados
- [X] JavaScript debe leer defaults vía AJAX desde PHP
- [X] PHP lee de tabla de defaults, NO tiene `get_defaults()` hardcodeado
**✅ Respuesta del usuario:** "opocion A, no debe ser en la msima tabla personalizada wp_apus_theme_components, debe ser en wp_apus_theme_components_defaults"
**Arquitectura definida:**
1. Algoritmo extrae valores hardcodeados → Son los defaults
2. Se insertan en tabla `wp_apus_theme_components_defaults` (un row por campo)
3. Settings Manager lee de tabla de defaults
4. JavaScript NO tiene `DEFAULT_CONFIG`
5. JavaScript lee vía AJAX desde PHP
---
### PREGUNTA 6: Comparación con Sistema Actual ✅ RESPONDIDA
**¿El componente Top Bar (que ya está implementado) tiene este problema?**
Verificación necesaria:
```javascript
// ¿Existe esto en admin-app.js?
topBar.message_text || 'Accede a más de 200,000...'
```
- [X] **SÍ - Top Bar tiene defaults hardcodeados en JS** ← CONFIRMADO
- [ ] NO - Top Bar lee defaults correctamente de PHP
- [ ] NO ESTOY SEGURO
**✅ Respuesta encontrada:**
```javascript
// admin-app.js:357
document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede a más de 200,000...';
```
**Archivos con defaults de Top Bar:**
1. `admin/includes/sanitizers/class-topbar-sanitizer.php` (línea 37)
2. `admin/includes/class-settings-manager.php` (línea 84)
3. `admin/assets/js/admin-app.js` (línea 357)
4. `admin/pages/main.php` (líneas 243-244, 495)
5. `admin/components/component-top-bar.php` (línea 190, 2 veces)
6. `header.php` (línea 34)
**Total:** ❌ 7 lugares con el MISMO valor hardcodeado
---
### PREGUNTA 7: Alcance del Problema ✅ RESPONDIDA
**¿Cuántos componentes están afectados?**
- [X] **Todos los componentes futuros que se modularicen** ← PREOCUPACIÓN PRINCIPAL
**✅ Respuesta:**
- **Actual:** Código existente está MAL implementado - se debe eliminar y rehacer
- **Futuro:** TODOS los componentes que se procesen con el algoritmo PASO 12/14 tendrán el mismo problema
- **Crítico:** Si no se corrige el algoritmo PRIMERO, cada nuevo componente duplicará defaults en JS y PHP
**Acción requerida:**
1. ❌ NO usar código actual como referencia (está mal hecho)
2. ✅ Corregir algoritmo PRIMERO
3. ✅ Limpiar panel de administración (eliminar rastros de componentes mal implementados)
4. ✅ Limpiar tema (eliminar código duplicado)
5. ✅ LUEGO ejecutar algoritmo corregido para cada componente
---
### PREGUNTA 8: Dónde Debe Estar la Única Fuente de Verdad ✅ RESPONDIDA
**¿Dónde deben definirse los defaults UNA SOLA VEZ?**
Tu preferencia:
- [X] **Tabla personalizada `wp_apus_theme_components_defaults`** ← ÚNICA FUENTE DE VERDAD
- [X] Formato normalizado: un row por campo
- [X] Se pobla mediante algoritmo al procesar cada componente
- [ ] ❌ NO en `Settings Manager::get_defaults()` (eliminar método hardcodeado)
- [ ] ❌ NO en JavaScript `DEFAULT_CONFIG` (eliminar objeto hardcodeado)
**✅ Respuesta del usuario:** Nueva tabla `wp_apus_theme_components_defaults` con estructura normalizada
**Flujo correcto:**
```
Algoritmo PASO 2-4
Extrae valores hardcodeados del tema
INSERT INTO wp_apus_theme_components_defaults
Settings Manager lee de tabla
JavaScript lee vía AJAX (sin fallbacks)
```
---
## 🎯 OBJETIVO DE LA SOLUCIÓN
Una vez respondidas las preguntas, definiremos:
1. **Modificaciones al algoritmo** - Qué pasos cambiar
2. **Nueva arquitectura de defaults** - Dónde y cómo guardarlos
3. **Plan de migración** - Cómo corregir código existente
4. **Validación** - Cómo verificar que la solución funciona
---
## 📝 CÓMO EL ALGORITMO EXTRAE DEFAULTS (REVISIÓN COMPLETA)
### Flujo Documentado en el Algoritmo
**PASO 2-4: Extraer valores hardcodeados del tema actual**
- Usa Serena MCP para analizar archivos PHP/CSS/JS del tema
- Identifica valores hardcodeados (textos, URLs, colores, iconos)
- Documenta estos valores en `01-DOCUMENTACION-ANALISIS-CODIGO-REAL.md`
**PASO 6: Definir estructura JSON con defaults**
- Toma los valores extraídos en PASO 2-4
- Los define como valores por defecto en estructura JSON
- Colores se extraen del CSS: `background-color: #0E2337``custom_styles.background_color: '#0E2337'`
**PASO 14 (Líneas 88-123): Implementar defaults en Settings Manager**
```php
public function get_defaults() {
return array(
'version' => APUS_ADMIN_PANEL_VERSION,
'components' => array(
'component_name' => array(
'enabled' => true,
'field1' => 'Valor por defecto', // ← Del código hardcodeado actual
'custom_styles' => array(
'background_color' => '#0E2337', // ← Del CSS original
'text_color' => '#ffffff' // ← Del CSS original
)
)
)
);
}
```
### ❌ PROBLEMA: Defaults NO se insertan en tabla
**Lo que el algoritmo NO tiene:**
- ❌ Script de inicialización que inserte defaults en `wp_apus_theme_components`
- ❌ Paso que ejecute INSERT en la tabla al activar tema
- ❌ Migrador que convierta defaults de PHP a DB
**Lo que el algoritmo SÍ tiene:**
- ✅ Defaults en PHP (Settings Manager)
- ✅ Settings Manager hace merge: `wp_parse_args($db_data, $defaults)`
- ✅ Cuando tabla está vacía, usa defaults de PHP como fallback
### ✅ ENTENDIMIENTO CORRECTO DEL FLUJO:
**El algoritmo se ejecuta MANUALMENTE componente por componente:**
1. **Ejecutar algoritmo para "Top Bar":**
- PASO 2-4: Extrae valores hardcodeados actuales de header.php, CSS, JS
- Estos valores SON los defaults del Top Bar
- PASO 14: ❌ Los pone en Settings Manager (PHP hardcodeado)
- PASO 12: ❌ Los pone en JavaScript (DEFAULT_CONFIG hardcodeado)
- ✅ DEBERÍA: Insertarlos en tabla `wp_apus_theme_components`
2. **Ejecutar algoritmo para "Navbar":**
- PASO 2-4: Extrae valores hardcodeados actuales del navbar
- Estos valores SON los defaults del Navbar
- ✅ DEBERÍA: Insertarlos en tabla `wp_apus_theme_components`
3. **Y así con cada componente...**
### ❌ LO QUE ESTÁ MAL EN EL ALGORITMO:
**PASO 12:** Pone defaults en JavaScript
**PASO 14:** Pone defaults en PHP Settings Manager
**✅ LO QUE DEBERÍA HACER:**
Agregar un NUEVO PASO (o modificar PASO 14) que:
1. Tome los valores extraídos en PASO 2-4
2. Los inserte en `wp_apus_theme_components` con INSERT INTO
3. JavaScript NO tiene defaults hardcodeados
4. PHP lee de tabla, NO tiene `get_defaults()` hardcodeado
## 📝 NOTAS ADICIONALES
_[Espacio para el usuario agregar información adicional]_
---
---
## 📊 RESUMEN EJECUTIVO DE HALLAZGOS
### ✅ Preguntas Respondidas (8 de 8) - INVESTIGACIÓN COMPLETA
| # | Pregunta | Respuesta |
|---|----------|-----------|
| 1 | ¿Dónde está el problema? | **PASO 12 y PASO 14** del algoritmo |
| 2 | ¿Qué archivos JS afectados? | `component-*.js` (uno por componente) |
| 3 | ¿Qué tipo de valores? | **TODOS** (strings, booleans, URLs, colores, etc.) |
| 4 | ¿Para qué se usan? | Fallbacks + Botón Reset |
| 5 | ¿Cómo DEBERÍAN manejarse? | Tabla `wp_apus_theme_components_defaults` normalizada |
| 6 | ¿Top Bar tiene el problema? | **SÍ** - 7 lugares con mismo default |
| 7 | ¿Cuántos componentes afectados? | Top Bar actual + TODOS los futuros |
| 8 | ¿Única fuente de verdad? | Nueva tabla `wp_apus_theme_components_defaults` |
### 🎯 Decisión Arquitectónica Final
**ÚNICA FUENTE DE VERDAD:**
- Tabla: `wp_apus_theme_components_defaults` (nueva)
- Formato: Normalizado (un row por campo)
- Poblamiento: Vía algoritmo al procesar cada componente
- ❌ Eliminar: `DEFAULT_CONFIG` en JavaScript
- ❌ Modificar: `get_defaults()` en PHP para leer de DB
---
## 🗄️ ESTRUCTURA DE NUEVA TABLA DE DEFAULTS
### Tabla: `wp_apus_theme_components_defaults`
**Propósito:** Almacenar valores por defecto extraídos del tema mediante el algoritmo de modularización
**Características:**
- ✅ Estructura normalizada (un row por campo)
- ✅ Misma estructura que `wp_apus_theme_components` para consistencia
- ✅ Se pobla automáticamente al ejecutar algoritmo para cada componente
- ✅ Single source of truth para todos los defaults del sistema
### SQL Schema
```sql
CREATE TABLE IF NOT EXISTS wp_apus_theme_components_defaults (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
component_name VARCHAR(50) NOT NULL COMMENT 'Nombre del componente (top_bar, navbar, hero, etc.)',
config_key VARCHAR(100) NOT NULL COMMENT 'Clave de configuración (message_text, bg_color, etc.)',
config_value TEXT NOT NULL COMMENT 'Valor por defecto extraído del tema',
data_type ENUM('string','integer','boolean','array','json') NOT NULL COMMENT 'Tipo de dato del valor',
version VARCHAR(20) DEFAULT NULL COMMENT 'Versión del tema cuando se insertó el default',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'Fecha de creación del registro',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Última actualización',
UNIQUE KEY unique_default_config (component_name, config_key),
INDEX idx_component_name (component_name),
INDEX idx_config_key (config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Valores por defecto de componentes del tema';
```
### Estructura Genérica de Datos
```sql
-- Estructura GENÉRICA para insertar defaults de CUALQUIER componente
-- Se puebla al ejecutar algoritmo para cada componente
INSERT INTO wp_apus_theme_components_defaults
(component_name, config_key, config_value, data_type, version)
VALUES
-- Campos booleanos
('[component_name]', 'enabled', '[1|0]', 'boolean', '[version]'),
('[component_name]', '[boolean_field]', '[1|0]', 'boolean', '[version]'),
-- Campos de texto (extraídos del código hardcodeado)
('[component_name]', '[text_field]', '[valor_extraído_del_código]', 'string', '[version]'),
-- Campos numéricos
('[component_name]', '[number_field]', '[valor_numérico]', 'integer', '[version]'),
-- Custom styles (extraídos del CSS del componente)
('[component_name]', 'custom_styles.[propiedad_css]', '[valor_del_css]', 'string', '[version]');
```
**Notas:**
- `[component_name]`: Nombre del componente (ej: 'navbar', 'hero', 'footer')
- `[config_key]`: Clave del campo según PASO 6 del algoritmo
- `[config_value]`: Valor extraído del código/CSS actual del tema
- `[data_type]`: Tipo según el campo (string, integer, boolean, array, json)
- `[version]`: Versión del tema al momento de extraer defaults
### Propósito de Cada Tabla
**`wp_apus_theme_components`** (configuraciones personalizadas)
- Se guardan cuando el usuario modifica valores en el Admin Panel
- Si existe config personalizada, se usa esta
**`wp_apus_theme_components_defaults`** (valores por defecto)
- Se pueblan al ejecutar el algoritmo para cada componente
- Se usan SOLO cuando NO existe config personalizada
- Son los valores extraídos del tema actual (hardcodeados)
**Ambas tablas tienen la MISMA estructura** - la diferencia es solo su propósito.
---
## 🔄 FLUJO DE LECTURA DE DATOS
### ¿Por qué se necesitan Settings Manager (PHP) Y JavaScript?
**NO están duplicados - tienen propósitos diferentes:**
#### Settings Manager (PHP)
**Propósito:** Para que archivos PHP del tema lean configuraciones
**Uso:**
```php
// En header.php, footer.php, etc.
$settings_manager = new APUS_Settings_Manager();
$navbar_config = $settings_manager->get_component_config('navbar');
// Usar $navbar_config en el HTML del tema
echo $navbar_config['logo_url'];
```
**Lee de:**
1. Tabla `wp_apus_theme_components` (config personalizada) - PRIORIDAD ALTA
2. Si no existe → Tabla `wp_apus_theme_components_defaults` (defaults)
#### JavaScript + AJAX
**Propósito:** Para que el Admin Panel (interfaz de administración) lea/guarde configuraciones
**Uso:**
```javascript
// En admin-app.js
// Leer configuración vía AJAX
axios.get(ajaxUrl + '?action=get_component_config&component=navbar')
.then(response => {
// Renderizar formulario con los datos
renderForm(response.data);
});
// Guardar configuración vía AJAX
axios.post(ajaxUrl, formData)
.then(response => {
// Mostrar mensaje de éxito
});
```
**Lee/Escribe vía AJAX a:**
- Endpoint PHP que usa Settings Manager
- Guarda en tabla `wp_apus_theme_components`
### Flujo Completo
```
FRONTEND (tema):
header.php → Settings Manager (PHP) → Lee de DB → Muestra en tema
ADMIN PANEL:
JavaScript → AJAX → Endpoint PHP → Settings Manager → Lee/Escribe DB → Respuesta JSON
```
**Conclusión:** Se necesitan AMBOS porque sirven a partes diferentes del sistema (frontend vs admin).
---
## 🎯 PRÓXIMOS PASOS
### ✅ INVESTIGACIÓN COMPLETA - 8/8 preguntas respondidas
**ORDEN CORRECTO DE IMPLEMENTACIÓN:**
### FASE 1: LIMPIAR CÓDIGO ACTUAL (PRIMERO)
**El código actual está MAL implementado y debe eliminarse ANTES de corregir el algoritmo**
#### 1.1. Limpiar Panel de Administración
**Eliminar completamente cualquier rastro de componentes mal implementados:**
- Eliminar archivos JS de componentes: `admin/assets/js/component-*.js`
- Eliminar archivos CSS de componentes: `admin/assets/css/component-*.css`
- Eliminar archivos PHP de componentes: `admin/components/component-*.php`
- Eliminar sanitizers de componentes: `admin/includes/sanitizers/class-*-sanitizer.php`
- Limpiar `admin/pages/main.php` de secciones de componentes
- Limpiar `admin/includes/class-admin-menu.php` de encolamiento de componentes
#### 1.2. Limpiar Tema
**Eliminar valores hardcodeados duplicados:**
- Revisar `header.php` y eliminar valores duplicados
- Revisar otros archivos del tema con valores hardcodeados
- Dejar SOLO el código original del tema (antes de modularización)
#### 1.3. Limpiar Base de Datos
**Eliminar datos de componentes mal implementados:**
- Vaciar tabla `wp_apus_theme_components` o eliminar componentes específicos
- Preparar para empezar desde cero
---
### FASE 2: CREAR TABLA DE DEFAULTS
#### 2.1. Crear Tabla `wp_apus_theme_components_defaults`
- Ejecutar SQL CREATE TABLE (ver estructura arriba)
- Verificar que tabla existe y tiene estructura correcta
---
### FASE 3: CORREGIR ALGORITMO (DESPUÉS DE LIMPIAR)
#### 3.1. PASO 12: Implementar Admin JS (CORREGIR)
**Archivo:** `00-algoritmo/12-F03-IMPLEMENTACION-IMPLEMENTAR-ADMIN-JS.md`
**Cambios:**
-**ELIMINAR:** Objeto `DEFAULT_CONFIG` (líneas 43-51, 169-177)
-**ELIMINAR:** Fallbacks en método `render()` (líneas 117-129)
-**MODIFICAR:** Botón reset debe llamar endpoint AJAX para leer defaults de DB
- ✅ JavaScript NUNCA tiene valores hardcodeados
#### 3.2. PASO 14: Settings Manager (CORREGIR)
**Archivo:** `00-algoritmo/14-F04-CIERRE-GIT-COMMITS.md`
**Cambios:**
-**ELIMINAR:** Método `get_defaults()` con array hardcodeado (líneas 88-123)
-**MODIFICAR:** DB Manager para leer de tabla `_defaults`
#### 3.3. NUEVO PASO: Poblar Tabla de Defaults
**Ubicación:** Después de PASO 7
**Contenido:**
1. Leer valores extraídos en PASO 6 (estructura JSON)
2. Generar script SQL con INSERTs para tabla `wp_apus_theme_components_defaults`
3. Ejecutar script SQL
4. Verificar que defaults están en DB
---
### FASE 4: USAR ALGORITMO CORREGIDO PARA DOCUMENTAR COMPONENTES
**El algoritmo NO implementa código - solo DOCUMENTA**
Una vez el algoritmo esté corregido:
#### 4.1. Ejecutar Algoritmo Completo (16 pasos) para UN Componente
**Ejemplo:** Navbar
**Pasos 1-13: DOCUMENTACIÓN (genera 7 archivos MD)**
- PASO 1: Crear issue en GitHub
- PASO 2-4: Analizar código actual del Navbar (Serena MCP)
- PASO 5-8: Diseñar campos configurables y estructura JSON
- PASO 9-13: Documentar cómo implementar (plantillas, ejemplos, HTML, JS, CSS)
**OUTPUT:** Carpeta `navbar/` con 7 archivos MD de documentación
#### 4.2. PASO 14: Implementar Código Real
**AQUÍ es cuando se modifica código PHP/JS/CSS del tema/admin**
⚠️ **NOTA IMPORTANTE:** El PASO 14 actual del algoritmo tiene el problema que identificamos:
- Instruye crear método `get_defaults()` con array hardcodeado en Settings Manager (líneas 88-123)
- Esto es lo que necesitamos CORREGIR en FASE 3
**Con el algoritmo CORREGIDO**, el PASO 14 debe hacer:
Usando la documentación generada en pasos 1-13:
1. Modificar PHP del tema (ej: `header.php`) según `04-IMPLEMENTACION-COMPONENTE-NAVBAR.md`
2. Agregar HTML admin en `admin/pages/main.php` según `05-IMPLEMENTACION-ADMIN-HTML-NAVBAR.md`
3. Agregar JavaScript en `admin/assets/js/admin-app.js` según `07-IMPLEMENTACION-JS-ESPECIFICO.md`
4. **MODIFICAR DB Manager:** Agregar método para insertar/leer defaults de tabla `wp_apus_theme_components_defaults`
5. **MODIFICAR Settings Manager:** Leer de tabla defaults (NO array hardcodeado)
6. **INSERTAR defaults en DB:** Ejecutar script SQL con valores extraídos en PASO 4
7. Commits por cada archivo modificado
#### 4.3. PASO 15-16: Testing y Cierre
- Testing post-implementación
- Cerrar issue en GitHub
#### 4.4. Repetir para Cada Componente
Una vez completado Navbar (pasos 1-16):
1. Ejecutar algoritmo para siguiente componente (ej: Hero)
2. Generar documentación (pasos 1-13)
3. Implementar código real (paso 14)
4. Testing y cierre (pasos 15-16)
**Componentes del tema a procesar:**
- Navbar
- Hero Section
- Footer
- etc.
---
**Última actualización:** 2025-01-13 - INVESTIGACIÓN COMPLETA - 8/8 preguntas respondidas
**Estado:** 🟢 LISTO PARA IMPLEMENTACIÓN

View File

@@ -3,7 +3,7 @@
*
* Estilos base para el panel de administración
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -11,7 +11,7 @@
Container
======================================== */
.apus-admin-panel {
.roi-admin-panel {
max-width: 1400px;
margin: 20px auto;
}
@@ -20,11 +20,11 @@
Header
======================================== */
.apus-admin-panel h1 {
.roi-admin-panel h1 {
margin-bottom: 10px;
}
.apus-admin-panel .description {
.roi-admin-panel .description {
color: #666;
margin-bottom: 20px;
}
@@ -151,7 +151,7 @@
======================================== */
@media (max-width: 782px) {
.apus-admin-panel {
.roi-admin-panel {
margin: 10px;
}

View File

@@ -1,17 +1,17 @@
/**
* Theme Options Admin Styles
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
/* Main Container */
.apus-theme-options {
.roi-theme-options {
margin: 20px 20px 0 0;
}
/* Header */
.apus-options-header {
.roi-options-header {
background: #fff;
border: 1px solid #c3c4c7;
padding: 20px;
@@ -22,14 +22,14 @@
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.apus-options-logo h2 {
.roi-options-logo h2 {
margin: 0;
font-size: 24px;
color: #1d2327;
display: inline-block;
}
.apus-options-logo .version {
.roi-options-logo .version {
background: #2271b1;
color: #fff;
padding: 3px 8px;
@@ -38,53 +38,53 @@
margin-left: 10px;
}
.apus-options-actions {
.roi-options-actions {
display: flex;
gap: 10px;
}
.apus-options-actions .button .dashicons {
.roi-options-actions .button .dashicons {
margin-top: 3px;
margin-right: 3px;
}
/* Form */
.apus-options-form {
.roi-options-form {
background: #fff;
border: 1px solid #c3c4c7;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
/* Tabs Container */
.apus-options-container {
.roi-options-container {
display: flex;
min-height: 600px;
}
/* Tabs Navigation */
.apus-tabs-nav {
.roi-tabs-nav {
width: 200px;
background: #f6f7f7;
border-right: 1px solid #c3c4c7;
}
.apus-tabs-nav ul {
.roi-tabs-nav ul {
margin: 0;
padding: 0;
list-style: none;
}
.apus-tabs-nav li {
.roi-tabs-nav li {
margin: 0;
padding: 0;
border-bottom: 1px solid #c3c4c7;
}
.apus-tabs-nav li:first-child {
.roi-tabs-nav li:first-child {
border-top: 1px solid #c3c4c7;
}
.apus-tabs-nav a {
.roi-tabs-nav a {
display: block;
padding: 15px 20px;
color: #50575e;
@@ -93,21 +93,21 @@
position: relative;
}
.apus-tabs-nav a .dashicons {
.roi-tabs-nav a .dashicons {
margin-right: 8px;
color: #787c82;
}
.apus-tabs-nav a:hover {
.roi-tabs-nav a:hover {
background: #fff;
color: #2271b1;
}
.apus-tabs-nav a:hover .dashicons {
.roi-tabs-nav a:hover .dashicons {
color: #2271b1;
}
.apus-tabs-nav li.active a {
.roi-tabs-nav li.active a {
background: #fff;
color: #2271b1;
font-weight: 600;
@@ -115,37 +115,37 @@
padding-left: 17px;
}
.apus-tabs-nav li.active a .dashicons {
.roi-tabs-nav li.active a .dashicons {
color: #2271b1;
}
/* Tabs Content */
.apus-tabs-content {
.roi-tabs-content {
flex: 1;
padding: 30px;
}
.apus-tab-pane {
.roi-tab-pane {
display: none;
}
.apus-tab-pane.active {
.roi-tab-pane.active {
display: block;
}
.apus-tab-pane h2 {
.roi-tab-pane h2 {
margin: 0 0 10px 0;
font-size: 23px;
font-weight: 400;
line-height: 1.3;
}
.apus-tab-pane > p.description {
.roi-tab-pane > p.description {
margin: 0 0 20px 0;
color: #646970;
}
.apus-tab-pane h3 {
.roi-tab-pane h3 {
margin: 30px 0 0 0;
padding: 15px 0 10px 0;
border-top: 1px solid #dcdcde;
@@ -153,34 +153,34 @@
}
/* Form Table */
.apus-tab-pane .form-table {
.roi-tab-pane .form-table {
margin-top: 20px;
}
.apus-tab-pane .form-table th {
.roi-tab-pane .form-table th {
padding: 20px 10px 20px 0;
width: 200px;
}
.apus-tab-pane .form-table td {
.roi-tab-pane .form-table td {
padding: 15px 10px;
}
/* Toggle Switch */
.apus-switch {
.roi-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.apus-switch input {
.roi-switch input {
opacity: 0;
width: 0;
height: 0;
}
.apus-slider {
.roi-slider {
position: absolute;
cursor: pointer;
top: 0;
@@ -192,7 +192,7 @@
border-radius: 24px;
}
.apus-slider:before {
.roi-slider:before {
position: absolute;
content: "";
height: 18px;
@@ -204,24 +204,24 @@
border-radius: 50%;
}
input:checked + .apus-slider {
input:checked + .roi-slider {
background-color: #2271b1;
}
input:focus + .apus-slider {
input:focus + .roi-slider {
box-shadow: 0 0 1px #2271b1;
}
input:checked + .apus-slider:before {
input:checked + .roi-slider:before {
transform: translateX(26px);
}
/* Image Upload */
.apus-image-upload {
.roi-image-upload {
max-width: 600px;
}
.apus-image-preview {
.roi-image-preview {
margin-bottom: 10px;
border: 1px solid #c3c4c7;
background: #f6f7f7;
@@ -232,23 +232,23 @@ input:checked + .apus-slider:before {
justify-content: center;
}
.apus-image-preview:empty {
.roi-image-preview:empty {
display: none;
}
.apus-preview-image {
.roi-preview-image {
max-width: 100%;
height: auto;
display: block;
}
.apus-upload-image,
.apus-remove-image {
.roi-upload-image,
.roi-remove-image {
margin-right: 10px;
}
/* Submit Button */
.apus-options-form .submit {
.roi-options-form .submit {
margin: 0;
padding: 20px 30px;
border-top: 1px solid #c3c4c7;
@@ -256,7 +256,7 @@ input:checked + .apus-slider:before {
}
/* Modal */
.apus-modal {
.roi-modal {
display: none;
position: fixed;
z-index: 100000;
@@ -268,7 +268,7 @@ input:checked + .apus-slider:before {
background-color: rgba(0,0,0,0.5);
}
.apus-modal-content {
.roi-modal-content {
background-color: #fff;
margin: 10% auto;
padding: 30px;
@@ -279,7 +279,7 @@ input:checked + .apus-slider:before {
border-radius: 4px;
}
.apus-modal-close {
.roi-modal-close {
color: #646970;
float: right;
font-size: 28px;
@@ -288,22 +288,22 @@ input:checked + .apus-slider:before {
cursor: pointer;
}
.apus-modal-close:hover,
.apus-modal-close:focus {
.roi-modal-close:hover,
.roi-modal-close:focus {
color: #1d2327;
}
.apus-modal-content h2 {
.roi-modal-content h2 {
margin-top: 0;
}
.apus-modal-content textarea {
.roi-modal-content textarea {
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
}
/* Notices */
.apus-notice {
.roi-notice {
padding: 12px;
margin: 20px 0;
border-left: 4px solid;
@@ -311,19 +311,19 @@ input:checked + .apus-slider:before {
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.apus-notice.success {
.roi-notice.success {
border-left-color: #00a32a;
}
.apus-notice.error {
.roi-notice.error {
border-left-color: #d63638;
}
.apus-notice.warning {
.roi-notice.warning {
border-left-color: #dba617;
}
.apus-notice.info {
.roi-notice.info {
border-left-color: #2271b1;
}
@@ -336,109 +336,109 @@ textarea.code {
/* Responsive */
@media screen and (max-width: 782px) {
.apus-options-container {
.roi-options-container {
flex-direction: column;
}
.apus-tabs-nav {
.roi-tabs-nav {
width: 100%;
border-right: none;
border-bottom: 1px solid #c3c4c7;
}
.apus-tabs-nav ul {
.roi-tabs-nav ul {
display: flex;
flex-wrap: wrap;
}
.apus-tabs-nav li {
.roi-tabs-nav li {
flex: 1;
min-width: 50%;
border-right: 1px solid #c3c4c7;
border-bottom: none;
}
.apus-tabs-nav li:first-child {
.roi-tabs-nav li:first-child {
border-top: none;
}
.apus-tabs-nav a {
.roi-tabs-nav a {
text-align: center;
padding: 12px 10px;
font-size: 13px;
}
.apus-tabs-nav a .dashicons {
.roi-tabs-nav a .dashicons {
display: block;
margin: 0 auto 5px;
}
.apus-tabs-nav li.active a {
.roi-tabs-nav li.active a {
border-left: none;
border-bottom: 3px solid #2271b1;
padding-left: 10px;
}
.apus-tabs-content {
.roi-tabs-content {
padding: 20px;
}
.apus-options-header {
.roi-options-header {
flex-direction: column;
gap: 15px;
}
.apus-options-actions {
.roi-options-actions {
width: 100%;
flex-direction: column;
}
.apus-options-actions .button {
.roi-options-actions .button {
width: 100%;
text-align: center;
}
.apus-tab-pane .form-table th {
.roi-tab-pane .form-table th {
width: auto;
padding: 15px 10px 5px 0;
display: block;
}
.apus-tab-pane .form-table td {
.roi-tab-pane .form-table td {
display: block;
padding: 5px 10px 15px 0;
}
}
/* Loading Spinner */
.apus-spinner {
.roi-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(0,0,0,.1);
border-radius: 50%;
border-top-color: #2271b1;
animation: apus-spin 1s ease-in-out infinite;
animation: roispin 1s ease-in-out infinite;
}
@keyframes apus-spin {
@keyframes roispin {
to { transform: rotate(360deg); }
}
/* Helper Classes */
.apus-hidden {
.roi-hidden {
display: none !important;
}
.apus-text-center {
.roi-text-center {
text-align: center;
}
.apus-mt-20 {
.roi-mt-20 {
margin-top: 20px;
}
.apus-mb-20 {
.roi-mb-20 {
margin-bottom: 20px;
}
@@ -448,13 +448,13 @@ textarea.code {
}
/* Field Dependencies */
.apus-field-dependency {
.roi-field-dependency {
opacity: 0.5;
pointer-events: none;
}
/* Success Animation */
@keyframes apus-saved {
@keyframes roisaved {
0% {
transform: scale(1);
}
@@ -466,6 +466,6 @@ textarea.code {
}
}
.apus-saved {
animation: apus-saved 0.3s ease-in-out;
.roi-saved {
animation: roisaved 0.3s ease-in-out;
}

View File

@@ -3,7 +3,7 @@
*
* Gestión de configuraciones de componentes del tema
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -92,10 +92,10 @@ const AdminPanel = {
try {
const response = await axios({
method: 'POST',
url: apusAdminData.ajaxUrl,
url: roiAdminData.ajaxUrl,
data: new URLSearchParams({
action: 'apus_get_settings',
nonce: apusAdminData.nonce
action: 'roi_get_settings',
nonce: roiAdminData.nonce
})
});
@@ -130,15 +130,15 @@ const AdminPanel = {
// Crear FormData para WordPress AJAX
const postData = new URLSearchParams();
postData.append('action', 'apus_save_settings');
postData.append('nonce', apusAdminData.nonce);
postData.append('action', 'roi_save_settings');
postData.append('nonce', roiAdminData.nonce);
// Agregar components como JSON string
postData.append('components', JSON.stringify(formData.components));
const response = await axios({
method: 'POST',
url: apusAdminData.ajaxUrl,
url: roiAdminData.ajaxUrl,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
@@ -201,7 +201,7 @@ const AdminPanel = {
noticeDiv.className = `notice notice-${type} is-dismissible`;
noticeDiv.innerHTML = `<p>${message}</p>`;
const container = document.querySelector('.apus-admin-panel');
const container = document.querySelector('.roi-admin-panel');
if (container) {
container.insertBefore(noticeDiv, container.firstChild);

View File

@@ -1,14 +1,14 @@
/**
* Theme Options Admin JavaScript
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
(function($) {
'use strict';
var ApusThemeOptions = {
var ROIThemeOptions = {
/**
* Initialize
@@ -28,17 +28,17 @@
*/
tabs: function() {
// Tab click handler
$('.apus-tabs-nav a').on('click', function(e) {
$('.roi-tabs-nav a').on('click', function(e) {
e.preventDefault();
var tabId = $(this).attr('href');
// Update active states
$('.apus-tabs-nav li').removeClass('active');
$('.roi-tabs-nav li').removeClass('active');
$(this).parent().addClass('active');
// Show/hide tab content
$('.apus-tab-pane').removeClass('active');
$('.roi-tab-pane').removeClass('active');
$(tabId).addClass('active');
// Update URL hash without scrolling
@@ -53,14 +53,14 @@
if (window.location.hash) {
var hash = window.location.hash;
if ($(hash).length) {
$('.apus-tabs-nav a[href="' + hash + '"]').trigger('click');
$('.roi-tabs-nav a[href="' + hash + '"]').trigger('click');
}
}
// Handle browser back/forward buttons
$(window).on('hashchange', function() {
if (window.location.hash) {
$('.apus-tabs-nav a[href="' + window.location.hash + '"]').trigger('click');
$('.roi-tabs-nav a[href="' + window.location.hash + '"]').trigger('click');
}
});
},
@@ -73,14 +73,14 @@
var mediaUploader;
// Upload button click
$(document).on('click', '.apus-upload-image', function(e) {
$(document).on('click', '.roi-upload-image', function(e) {
e.preventDefault();
var button = $(this);
var container = button.closest('.apus-image-upload');
var preview = container.find('.apus-image-preview');
var input = container.find('.apus-image-id');
var removeBtn = container.find('.apus-remove-image');
var container = button.closest('.roi-image-upload');
var preview = container.find('.roi-image-preview');
var input = container.find('.roi-image-id');
var removeBtn = container.find('.roi-remove-image');
// If the media uploader already exists, reopen it
if (mediaUploader) {
@@ -90,9 +90,9 @@
// Create new media uploader
mediaUploader = wp.media({
title: apusAdminOptions.strings.selectImage,
title: roiAdminOptions.strings.selectImage,
button: {
text: apusAdminOptions.strings.useImage
text: roiAdminOptions.strings.useImage
},
multiple: false
});
@@ -107,7 +107,7 @@
// Show preview
var imgUrl = attachment.sizes && attachment.sizes.medium ?
attachment.sizes.medium.url : attachment.url;
preview.html('<img src="' + imgUrl + '" class="apus-preview-image" />');
preview.html('<img src="' + imgUrl + '" class="roi-preview-image" />');
// Show remove button
removeBtn.show();
@@ -118,13 +118,13 @@
});
// Remove button click
$(document).on('click', '.apus-remove-image', function(e) {
$(document).on('click', '.roi-remove-image', function(e) {
e.preventDefault();
var button = $(this);
var container = button.closest('.apus-image-upload');
var preview = container.find('.apus-image-preview');
var input = container.find('.apus-image-id');
var container = button.closest('.roi-image-upload');
var preview = container.find('.roi-image-preview');
var input = container.find('.roi-image-id');
// Clear values
input.val('');
@@ -137,10 +137,10 @@
* Reset Options
*/
resetOptions: function() {
$('#apus-reset-options').on('click', function(e) {
$('#roi-reset-options').on('click', function(e) {
e.preventDefault();
if (!confirm(apusAdminOptions.strings.confirmReset)) {
if (!confirm(roiAdminOptions.strings.confirmReset)) {
return;
}
@@ -148,28 +148,28 @@
button.prop('disabled', true).addClass('updating-message');
$.ajax({
url: apusAdminOptions.ajaxUrl,
url: roiAdminOptions.ajaxUrl,
type: 'POST',
data: {
action: 'apus_reset_options',
nonce: apusAdminOptions.nonce
action: 'roi_reset_options',
nonce: roiAdminOptions.nonce
},
success: function(response) {
if (response.success) {
// Show success message
ApusThemeOptions.showNotice('success', response.data.message);
ROIThemeOptions.showNotice('success', response.data.message);
// Reload page after 1 second
setTimeout(function() {
window.location.reload();
}, 1000);
} else {
ApusThemeOptions.showNotice('error', response.data.message);
ROIThemeOptions.showNotice('error', response.data.message);
button.prop('disabled', false).removeClass('updating-message');
}
},
error: function() {
ApusThemeOptions.showNotice('error', apusAdminOptions.strings.error);
ROIThemeOptions.showNotice('error', roiAdminOptions.strings.error);
button.prop('disabled', false).removeClass('updating-message');
}
});
@@ -180,18 +180,18 @@
* Export Options
*/
exportOptions: function() {
$('#apus-export-options').on('click', function(e) {
$('#roi-export-options').on('click', function(e) {
e.preventDefault();
var button = $(this);
button.prop('disabled', true).addClass('updating-message');
$.ajax({
url: apusAdminOptions.ajaxUrl,
url: roiAdminOptions.ajaxUrl,
type: 'POST',
data: {
action: 'apus_export_options',
nonce: apusAdminOptions.nonce
action: 'roi_export_options',
nonce: roiAdminOptions.nonce
},
success: function(response) {
if (response.success) {
@@ -206,14 +206,14 @@
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
ApusThemeOptions.showNotice('success', 'Options exported successfully!');
ROIThemeOptions.showNotice('success', 'Options exported successfully!');
} else {
ApusThemeOptions.showNotice('error', response.data.message);
ROIThemeOptions.showNotice('error', response.data.message);
}
button.prop('disabled', false).removeClass('updating-message');
},
error: function() {
ApusThemeOptions.showNotice('error', apusAdminOptions.strings.error);
ROIThemeOptions.showNotice('error', roiAdminOptions.strings.error);
button.prop('disabled', false).removeClass('updating-message');
}
});
@@ -224,17 +224,17 @@
* Import Options
*/
importOptions: function() {
var modal = $('#apus-import-modal');
var importData = $('#apus-import-data');
var modal = $('#roi-import-modal');
var importData = $('#roi-import-data');
// Show modal
$('#apus-import-options').on('click', function(e) {
$('#roi-import-options').on('click', function(e) {
e.preventDefault();
modal.show();
});
// Close modal
$('.apus-modal-close, #apus-import-cancel').on('click', function() {
$('.roi-modal-close, #roi-import-cancel').on('click', function() {
modal.hide();
importData.val('');
});
@@ -248,7 +248,7 @@
});
// Submit import
$('#apus-import-submit').on('click', function(e) {
$('#roi-import-submit').on('click', function(e) {
e.preventDefault();
var data = importData.val().trim();
@@ -262,16 +262,16 @@
button.prop('disabled', true).addClass('updating-message');
$.ajax({
url: apusAdminOptions.ajaxUrl,
url: roiAdminOptions.ajaxUrl,
type: 'POST',
data: {
action: 'apus_import_options',
nonce: apusAdminOptions.nonce,
action: 'roi_import_options',
nonce: roiAdminOptions.nonce,
import_data: data
},
success: function(response) {
if (response.success) {
ApusThemeOptions.showNotice('success', response.data.message);
ROIThemeOptions.showNotice('success', response.data.message);
modal.hide();
importData.val('');
@@ -280,12 +280,12 @@
window.location.reload();
}, 1000);
} else {
ApusThemeOptions.showNotice('error', response.data.message);
ROIThemeOptions.showNotice('error', response.data.message);
button.prop('disabled', false).removeClass('updating-message');
}
},
error: function() {
ApusThemeOptions.showNotice('error', apusAdminOptions.strings.error);
ROIThemeOptions.showNotice('error', roiAdminOptions.strings.error);
button.prop('disabled', false).removeClass('updating-message');
}
});
@@ -296,7 +296,7 @@
* Form Validation
*/
formValidation: function() {
$('.apus-options-form').on('submit', function(e) {
$('.roi-options-form').on('submit', function(e) {
var valid = true;
var firstError = null;
@@ -340,7 +340,7 @@
// Validate URL fields
$(this).find('input[type="url"]').each(function() {
var val = $(this).val();
if (val && !ApusThemeOptions.isValidUrl(val)) {
if (val && !ROIThemeOptions.isValidUrl(val)) {
valid = false;
$(this).addClass('error');
if (!firstError) {
@@ -360,7 +360,7 @@
firstError.focus();
}
ApusThemeOptions.showNotice('error', 'Please fix the errors in the form.');
ROIThemeOptions.showNotice('error', 'Please fix the errors in the form.');
return false;
}
@@ -369,7 +369,7 @@
});
// Remove error class on input
$('.apus-options-form input, .apus-options-form select, .apus-options-form textarea').on('change input', function() {
$('.roi-options-form input, .roi-options-form select, .roi-options-form textarea').on('change input', function() {
$(this).removeClass('error');
});
},
@@ -383,7 +383,7 @@
var checked = $(this).is(':checked');
var fields = $('#related_posts_count, #related_posts_taxonomy, #related_posts_title, #related_posts_columns');
fields.closest('tr').toggleClass('apus-field-dependency', !checked);
fields.closest('tr').toggleClass('roi-field-dependency', !checked);
fields.prop('disabled', !checked);
}).trigger('change');
@@ -392,7 +392,7 @@
var checked = $(this).is(':checked');
var field = $('#breadcrumb_separator');
field.closest('tr').toggleClass('apus-field-dependency', !checked);
field.closest('tr').toggleClass('roi-field-dependency', !checked);
field.prop('disabled', !checked);
}).trigger('change');
},
@@ -403,7 +403,7 @@
showNotice: function(type, message) {
var notice = $('<div class="notice notice-' + type + ' is-dismissible"><p>' + message + '</p></div>');
$('.apus-theme-options h1').after(notice);
$('.roi-theme-options h1').after(notice);
// Auto-dismiss after 5 seconds
setTimeout(function() {
@@ -431,10 +431,10 @@
// Initialize on document ready
$(document).ready(function() {
ApusThemeOptions.init();
ROIThemeOptions.init();
});
// Make it globally accessible
window.ApusThemeOptions = ApusThemeOptions;
window.ROIThemeOptions = ROIThemeOptions;
})(jQuery);

View File

@@ -4,7 +4,7 @@
*
* Registra menú en WordPress admin y carga assets
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -12,7 +12,7 @@ if (!defined('ABSPATH')) {
exit;
}
class APUS_Admin_Menu {
class ROI_Admin_Menu {
/**
* Constructor
@@ -29,50 +29,118 @@ class APUS_Admin_Menu {
public function register_menu() {
// Menú principal de nivel superior (sin callback para que sea solo contenedor)
add_menu_page(
'Apus Theme', // Page title
'Apus Theme', // Menu title
'ROI Theme', // Page title
'ROI Theme', // Menu title
'manage_options', // Capability
'apus-theme-menu', // Menu slug (solo identificador, no página real)
'roi-theme-menu', // Menu slug (solo identificador, no página real)
'', // Sin callback = solo contenedor
'dashicons-admin-generic', // Icon (WordPress Dashicon)
61 // Position (61 = después de Appearance que es 60)
);
// Submenú "Theme Options" (primer y principal subitem)
// Submenú 1: "Theme Options" (formulario viejo de apariencia)
add_submenu_page(
'apus-theme-menu', // Parent slug
'roi-theme-menu', // Parent slug
'Theme Options', // Page title
'Theme Options', // Menu title
'manage_options', // Capability
'apus-theme-settings', // Menu slug (página real)
'roi-theme-settings', // Menu slug
array($this, 'render_admin_page') // Callback
);
// Submenú 2: "Componentes" (nuevo sistema de tabs)
add_submenu_page(
'roi-theme-menu', // Parent slug
'Componentes', // Page title
'Componentes', // Menu title
'manage_options', // Capability
'roi-theme-components', // Menu slug
array($this, 'render_components_page') // Callback
);
// Remover el primer submenú duplicado que WordPress crea automáticamente
remove_submenu_page('apus-theme-menu', 'apus-theme-menu');
remove_submenu_page('roi-theme-menu', 'roi-theme-menu');
}
/**
* Renderizar página de admin
* Renderizar página de Theme Options (formulario viejo)
*/
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 . 'pages/main.php';
// Cargar el formulario viejo de theme options
require_once get_template_directory() . '/admin/theme-options/options-page-template.php';
}
/**
* Renderizar página de Componentes (nuevo sistema de tabs)
*/
public function render_components_page() {
if (!current_user_can('manage_options')) {
wp_die(__('No tienes permisos para acceder a esta página.'));
}
// Cargar el nuevo admin panel con tabs de componentes
require_once ROI_ADMIN_PANEL_PATH . 'pages/main.php';
}
/**
* Encolar assets (CSS/JS)
*/
public function enqueue_assets($hook) {
// Solo cargar en nuestra página
if ($hook !== 'apus-theme_page_apus-theme-settings') {
// Solo cargar en nuestras páginas de admin
$allowed_hooks = array(
'roi-theme_page_roi-theme-settings', // Theme Options
'roi-theme_page_roi-theme-components' // Componentes
);
if (!in_array($hook, $allowed_hooks)) {
return;
}
// Bootstrap 5.3.2 CSS
// CSS y JS específico para Theme Options (formulario viejo)
if ($hook === 'roi-theme_page_roi-theme-settings') {
// Enqueue WordPress media uploader
wp_enqueue_media();
// Enqueue admin styles para theme options
wp_enqueue_style(
'roi-admin-options',
get_template_directory_uri() . '/admin/assets/css/theme-options.css',
array(),
ROI_VERSION
);
// Enqueue admin scripts para theme options
wp_enqueue_script(
'roi-admin-options',
get_template_directory_uri() . '/admin/assets/js/theme-options.js',
array('jquery', 'wp-color-picker'),
ROI_VERSION,
true
);
// Localize script
wp_localize_script('roi-admin-options', 'roiAdminOptions', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('roi_admin_nonce'),
'strings' => array(
'selectImage' => __('Select Image', 'roi-theme'),
'useImage' => __('Use Image', 'roi-theme'),
'removeImage' => __('Remove Image', 'roi-theme'),
'confirmReset' => __('Are you sure you want to reset all options to default values? This cannot be undone.', 'roi-theme'),
'saved' => __('Settings saved successfully!', 'roi-theme'),
'error' => __('An error occurred while saving settings.', 'roi-theme'),
),
));
// No cargar Bootstrap ni otros assets del nuevo panel
return;
}
// Bootstrap 5.3.2 CSS (solo para Componentes)
wp_enqueue_style(
'bootstrap',
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css',
@@ -90,10 +158,10 @@ class APUS_Admin_Menu {
// Admin Panel CSS (Core)
wp_enqueue_style(
'apus-admin-panel-css',
APUS_ADMIN_PANEL_URL . 'assets/css/admin-panel.css',
'roi-admin-panel-css',
ROI_ADMIN_PANEL_URL . 'assets/css/admin-panel.css',
array('bootstrap'),
APUS_ADMIN_PANEL_VERSION
ROI_ADMIN_PANEL_VERSION
);
@@ -118,20 +186,20 @@ class APUS_Admin_Menu {
// Admin Panel JS (Core)
wp_enqueue_script(
'apus-admin-panel-js',
APUS_ADMIN_PANEL_URL . 'assets/js/admin-app.js',
'roi-admin-panel-js',
ROI_ADMIN_PANEL_URL . 'assets/js/admin-app.js',
array('jquery', 'axios'),
APUS_ADMIN_PANEL_VERSION,
ROI_ADMIN_PANEL_VERSION,
true
);
// Pasar datos a JavaScript
wp_localize_script('apus-admin-panel-js', 'apusAdminData', array(
wp_localize_script('roi-admin-panel-js', 'roiAdminData', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('apus_admin_nonce')
'nonce' => wp_create_nonce('roi_admin_nonce')
));
}
}
// Instanciar clase
new APUS_Admin_Menu();
new ROI_Admin_Menu();

View File

@@ -1,310 +0,0 @@
<?php
/**
* Data Migrator Class
*
* Migración de datos de wp_options a tabla personalizada
*
* @package Apus_Theme
* @since 2.2.0
*/
if (!defined('ABSPATH')) {
exit;
}
class APUS_Data_Migrator {
/**
* Opción para trackear si la migración se completó
*/
const MIGRATION_FLAG = 'apus_data_migrated';
/**
* Opción antigua en wp_options
*/
const OLD_OPTION_NAME = 'apus_theme_settings';
/**
* DB Manager instance
*/
private $db_manager;
/**
* Constructor
*/
public function __construct() {
$this->db_manager = new APUS_DB_Manager();
}
/**
* Verificar si la migración ya se ejecutó
*/
public function is_migrated() {
return get_option(self::MIGRATION_FLAG) === '1';
}
/**
* Ejecutar migración si es necesaria
*/
public function maybe_migrate() {
if ($this->is_migrated()) {
return array(
'success' => true,
'message' => 'La migración ya fue ejecutada anteriormente'
);
}
if (!$this->db_manager->table_exists()) {
return array(
'success' => false,
'message' => 'La tabla de destino no existe'
);
}
return $this->migrate();
}
/**
* Ejecutar migración completa
*/
public function migrate() {
global $wpdb;
// Comenzar transacción
$wpdb->query('START TRANSACTION');
try {
// Obtener datos de wp_options
$old_data = get_option(self::OLD_OPTION_NAME);
if (empty($old_data)) {
throw new Exception('No hay datos para migrar en wp_options');
}
$total_migrated = 0;
// Verificar estructura de datos
if (!isset($old_data['components']) || !is_array($old_data['components'])) {
throw new Exception('Estructura de datos inválida');
}
// Obtener versión y timestamp
$version = isset($old_data['version']) ? $old_data['version'] : APUS_ADMIN_PANEL_VERSION;
// Migrar cada componente
foreach ($old_data['components'] as $component_name => $component_data) {
if (!is_array($component_data)) {
continue;
}
$migrated = $this->migrate_component($component_name, $component_data, $version);
$total_migrated += $migrated;
}
// Marcar migración como completada
update_option(self::MIGRATION_FLAG, '1', false);
// Commit transacción
$wpdb->query('COMMIT');
error_log("APUS Data Migrator: Migración completada. Total de registros: $total_migrated");
return array(
'success' => true,
'message' => 'Migración completada exitosamente',
'total_migrated' => $total_migrated
);
} catch (Exception $e) {
// Rollback en caso de error
$wpdb->query('ROLLBACK');
error_log("APUS Data Migrator: Error en migración - " . $e->getMessage());
return array(
'success' => false,
'message' => 'Error en migración: ' . $e->getMessage()
);
}
}
/**
* Migrar un componente específico
*
* @param string $component_name Nombre del componente
* @param array $component_data Datos del componente
* @param string $version Versión
* @return int Número de registros migrados
*/
private function migrate_component($component_name, $component_data, $version) {
$count = 0;
foreach ($component_data as $key => $value) {
// Determinar tipo de dato
$data_type = $this->determine_data_type($key, $value);
// Si es un array/objeto anidado (como custom_styles), guardarlo como JSON
if ($data_type === 'json') {
$result = $this->db_manager->save_config(
$component_name,
$key,
$value,
$data_type,
$version
);
} else {
$result = $this->db_manager->save_config(
$component_name,
$key,
$value,
$data_type,
$version
);
}
if ($result !== false) {
$count++;
}
}
return $count;
}
/**
* Determinar el tipo de dato
*
* @param string $key Clave de configuración
* @param mixed $value Valor
* @return string Tipo de dato (string, boolean, integer, json)
*/
private function determine_data_type($key, $value) {
if (is_bool($value)) {
return 'boolean';
}
if (is_int($value)) {
return 'integer';
}
if (is_array($value)) {
return 'json';
}
// Por nombre de clave
if (in_array($key, array('enabled', 'show_on_mobile', 'show_on_desktop', 'show_icon', 'show_link'))) {
return 'boolean';
}
return 'string';
}
/**
* Crear backup de datos antiguos
*
* @return bool Éxito de la operación
*/
public function backup_old_data() {
$old_data = get_option(self::OLD_OPTION_NAME);
if (empty($old_data)) {
return false;
}
$backup_option = self::OLD_OPTION_NAME . '_backup_' . time();
return update_option($backup_option, $old_data, false);
}
/**
* Restaurar desde backup (rollback)
*
* @param string $backup_option Nombre de la opción de backup
* @return bool Éxito de la operación
*/
public function rollback($backup_option) {
$backup_data = get_option($backup_option);
if (empty($backup_data)) {
return false;
}
// Restaurar datos antiguos
update_option(self::OLD_OPTION_NAME, $backup_data, false);
// Limpiar flag de migración
delete_option(self::MIGRATION_FLAG);
// Limpiar tabla personalizada
global $wpdb;
$table_name = $this->db_manager->get_table_name();
$wpdb->query("TRUNCATE TABLE $table_name");
return true;
}
/**
* Comparar datos entre wp_options y tabla personalizada
*
* @return array Resultado de la comparación
*/
public function verify_migration() {
$old_data = get_option(self::OLD_OPTION_NAME);
if (empty($old_data) || !isset($old_data['components'])) {
return array(
'success' => false,
'message' => 'No hay datos en wp_options para comparar'
);
}
$discrepancies = array();
foreach ($old_data['components'] as $component_name => $component_data) {
$new_data = $this->db_manager->get_config($component_name);
foreach ($component_data as $key => $old_value) {
$new_value = isset($new_data[$key]) ? $new_data[$key] : null;
// Comparar valores (teniendo en cuenta conversiones de tipo)
if ($this->normalize_value($old_value) !== $this->normalize_value($new_value)) {
$discrepancies[] = array(
'component' => $component_name,
'key' => $key,
'old_value' => $old_value,
'new_value' => $new_value
);
}
}
}
if (empty($discrepancies)) {
return array(
'success' => true,
'message' => 'Migración verificada: todos los datos coinciden'
);
}
return array(
'success' => false,
'message' => 'Se encontraron discrepancias en la migración',
'discrepancies' => $discrepancies
);
}
/**
* Normalizar valor para comparación
*
* @param mixed $value Valor a normalizar
* @return mixed Valor normalizado
*/
private function normalize_value($value) {
if (is_bool($value)) {
return $value ? 1 : 0;
}
if (is_array($value)) {
return json_encode($value);
}
return $value;
}
}

View File

@@ -4,7 +4,7 @@
*
* Gestión de tablas personalizadas del tema
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.2.0
*/
@@ -12,17 +12,17 @@ if (!defined('ABSPATH')) {
exit;
}
class APUS_DB_Manager {
class ROI_DB_Manager {
/**
* Nombre de la tabla de componentes (sin prefijo)
*/
const TABLE_COMPONENTS = 'apus_theme_components';
const TABLE_COMPONENTS = 'roi_theme_components';
/**
* Nombre de la tabla de defaults (sin prefijo)
*/
const TABLE_DEFAULTS = 'apus_theme_components_defaults';
const TABLE_DEFAULTS = 'roi_theme_components_defaults';
/**
* Versión de la base de datos
@@ -32,7 +32,7 @@ class APUS_DB_Manager {
/**
* Opción para almacenar la versión de la DB
*/
const DB_VERSION_OPTION = 'apus_db_version';
const DB_VERSION_OPTION = 'roi_db_version';
/**
* Constructor
@@ -104,9 +104,9 @@ class APUS_DB_Manager {
dbDelta($sql_components);
if ($wpdb->get_var("SHOW TABLES LIKE '$table_components'") === $table_components) {
error_log("APUS DB Manager: Tabla $table_components creada/actualizada exitosamente");
error_log("ROI DB Manager: Tabla $table_components creada/actualizada exitosamente");
} else {
error_log("APUS DB Manager: Error al crear tabla $table_components");
error_log("ROI DB Manager: Error al crear tabla $table_components");
$success = false;
}
@@ -116,9 +116,9 @@ class APUS_DB_Manager {
dbDelta($sql_defaults);
if ($wpdb->get_var("SHOW TABLES LIKE '$table_defaults'") === $table_defaults) {
error_log("APUS DB Manager: Tabla $table_defaults creada/actualizada exitosamente");
error_log("ROI DB Manager: Tabla $table_defaults creada/actualizada exitosamente");
} else {
error_log("APUS DB Manager: Error al crear tabla $table_defaults");
error_log("ROI DB Manager: Error al crear tabla $table_defaults");
$success = false;
}

View File

@@ -4,7 +4,7 @@
*
* CRUD de configuraciones por componentes
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -12,16 +12,16 @@ if (!defined('ABSPATH')) {
exit;
}
class APUS_Settings_Manager {
class ROI_Settings_Manager {
const OPTION_NAME = 'apus_theme_settings';
const OPTION_NAME = 'roi_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'));
add_action('wp_ajax_roi_get_settings', array($this, 'ajax_get_settings'));
add_action('wp_ajax_roi_save_settings', array($this, 'ajax_save_settings'));
}
/**
@@ -39,7 +39,7 @@ class APUS_Settings_Manager {
*/
public function save_settings($data) {
// Validar
$validator = new APUS_Validator();
$validator = new ROI_Validator();
$validation = $validator->validate($data);
if (!$validation['valid']) {
@@ -54,7 +54,7 @@ class APUS_Settings_Manager {
$sanitized = $this->sanitize_settings($data);
// Agregar metadata
$sanitized['version'] = APUS_ADMIN_PANEL_VERSION;
$sanitized['version'] = ROI_ADMIN_PANEL_VERSION;
$sanitized['updated_at'] = current_time('mysql');
// Guardar
@@ -68,14 +68,14 @@ class APUS_Settings_Manager {
/**
* Valores por defecto
* Lee los defaults desde la tabla wp_apus_theme_components_defaults
* Lee los defaults desde la tabla wp_roi_theme_components_defaults
*/
public function get_defaults() {
$db_manager = new APUS_DB_Manager();
$db_manager = new ROI_DB_Manager();
$component_names = $db_manager->list_components('defaults');
$defaults = array(
'version' => APUS_ADMIN_PANEL_VERSION,
'version' => ROI_ADMIN_PANEL_VERSION,
'components' => array()
);
@@ -106,7 +106,7 @@ class APUS_Settings_Manager {
*/
public function ajax_get_settings() {
// Verificar nonce usando check_ajax_referer (método recomendado para AJAX)
check_ajax_referer('apus_admin_nonce', 'nonce');
check_ajax_referer('roi_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permisos insuficientes');
@@ -121,7 +121,7 @@ class APUS_Settings_Manager {
*/
public function ajax_save_settings() {
// Verificar nonce usando check_ajax_referer (método recomendado para AJAX)
check_ajax_referer('apus_admin_nonce', 'nonce');
check_ajax_referer('roi_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permisos insuficientes');
@@ -153,4 +153,4 @@ class APUS_Settings_Manager {
}
// Instanciar clase
new APUS_Settings_Manager();
new ROI_Settings_Manager();

View File

@@ -1,382 +0,0 @@
<?php
/**
* Theme Options Migrator Class
*
* Migra configuraciones de wp_options a tabla personalizada wp_apus_theme_components
*
* @package Apus_Theme
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
class APUS_Theme_Options_Migrator {
/**
* DB Manager instance
*/
private $db_manager;
/**
* Nombre de la opción en wp_options
*/
const OLD_OPTION_NAME = 'apus_theme_options';
/**
* Nombre del componente en la nueva tabla
*/
const COMPONENT_NAME = 'theme';
/**
* Constructor
*/
public function __construct() {
$this->db_manager = new APUS_DB_Manager();
}
/**
* Mapeo de tipos de datos para cada configuración
*
* @return array Mapeo config_key => data_type
*/
private function get_data_types_map() {
return array(
// Integers (IDs y contadores)
'site_logo' => 'integer',
'site_favicon' => 'integer',
'excerpt_length' => 'integer',
'archive_posts_per_page' => 'integer',
'related_posts_count' => 'integer',
'related_posts_columns' => 'integer',
// Booleans (enable_*, show_*, performance_*)
'enable_breadcrumbs' => 'boolean',
'show_featured_image_single' => 'boolean',
'show_author_box' => 'boolean',
'enable_comments_posts' => 'boolean',
'enable_comments_pages' => 'boolean',
'show_post_meta' => 'boolean',
'show_post_tags' => 'boolean',
'show_post_categories' => 'boolean',
'enable_lazy_loading' => 'boolean',
'performance_remove_emoji' => 'boolean',
'performance_remove_embeds' => 'boolean',
'performance_remove_dashicons' => 'boolean',
'performance_defer_js' => 'boolean',
'performance_minify_html' => 'boolean',
'performance_disable_gutenberg' => 'boolean',
'enable_related_posts' => 'boolean',
// Strings (todo lo demás: URLs, textos cortos, formatos, CSS/JS)
// No es necesario especificarlos, 'string' es el default
);
}
/**
* Determinar tipo de dato para una configuración
*
* @param string $config_key Nombre de la configuración
* @param mixed $config_value Valor de la configuración
* @return string Tipo de dato (string, boolean, integer, json)
*/
private function determine_data_type($config_key, $config_value) {
$types_map = $this->get_data_types_map();
// Si está en el mapa explícito, usar ese tipo
if (isset($types_map[$config_key])) {
return $types_map[$config_key];
}
// Detección automática por valor
if (is_array($config_value)) {
return 'json';
}
if (is_bool($config_value)) {
return 'boolean';
}
if (is_int($config_value)) {
return 'integer';
}
// Default: string (incluye textos largos, URLs, etc.)
return 'string';
}
/**
* Normalizar valor según tipo de dato
*
* @param mixed $value Valor a normalizar
* @param string $data_type Tipo de dato
* @return mixed Valor normalizado
*/
private function normalize_value($value, $data_type) {
switch ($data_type) {
case 'boolean':
// Convertir a booleano real (maneja strings '0', '1', etc.)
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
case 'integer':
return (int) $value;
case 'json':
// Si ya es array, dejarlo así (DB Manager lo codificará)
return is_array($value) ? $value : json_decode($value, true);
case 'string':
default:
return (string) $value;
}
}
/**
* Verificar si ya se realizó la migración
*
* @return bool True si ya está migrado, false si no
*/
public function is_migrated() {
// La migración se considera completa si:
// 1. No existe la opción antigua en wp_options
// 2. Y existen configuraciones en la tabla nueva
$old_options = get_option(self::OLD_OPTION_NAME, false);
$new_config = $this->db_manager->get_config(self::COMPONENT_NAME);
// Si no hay opción antigua Y hay configuraciones nuevas = migrado
return ($old_options === false && !empty($new_config));
}
/**
* Ejecutar migración completa
*
* @return array Resultado de la migración con éxito, mensaje y detalles
*/
public function migrate() {
// 1. Verificar si ya se migró
if ($this->is_migrated()) {
return array(
'success' => false,
'message' => 'La migración ya fue realizada anteriormente',
'already_migrated' => true
);
}
// 2. Obtener configuraciones actuales de wp_options
$old_options = get_option(self::OLD_OPTION_NAME, array());
if (empty($old_options)) {
return array(
'success' => false,
'message' => 'No hay opciones para migrar en wp_options'
);
}
// 3. Crear backup antes de migrar
$backup_result = $this->create_backup($old_options);
if (!$backup_result['success']) {
return $backup_result;
}
$backup_name = $backup_result['backup_name'];
// 4. Migrar cada configuración
$total = count($old_options);
$migrated = 0;
$errors = array();
foreach ($old_options as $config_key => $config_value) {
// Determinar tipo de dato
$data_type = $this->determine_data_type($config_key, $config_value);
// Normalizar valor
$normalized_value = $this->normalize_value($config_value, $data_type);
// Guardar en tabla personalizada
$result = $this->db_manager->save_config(
self::COMPONENT_NAME,
$config_key,
$normalized_value,
$data_type,
APUS_ADMIN_PANEL_VERSION
);
if ($result !== false) {
$migrated++;
} else {
$errors[] = $config_key;
}
}
// 5. Verificar resultado de la migración
if ($migrated === $total) {
// Éxito total
// Eliminar opción antigua de wp_options
delete_option(self::OLD_OPTION_NAME);
return array(
'success' => true,
'message' => sprintf('Migradas %d configuraciones correctamente', $migrated),
'migrated' => $migrated,
'total' => $total,
'backup_name' => $backup_name
);
} else {
// Migración parcial o con errores
return array(
'success' => false,
'message' => sprintf('Solo se migraron %d de %d configuraciones', $migrated, $total),
'migrated' => $migrated,
'total' => $total,
'errors' => $errors,
'backup_name' => $backup_name
);
}
}
/**
* Crear backup de las opciones actuales
*
* @param array $options Opciones a respaldar
* @return array Resultado con success y backup_name
*/
private function create_backup($options) {
$backup_name = self::OLD_OPTION_NAME . '_backup_' . date('Y-m-d_H-i-s');
$result = update_option($backup_name, $options, false); // No autoload
if ($result) {
return array(
'success' => true,
'backup_name' => $backup_name
);
} else {
return array(
'success' => false,
'message' => 'No se pudo crear el backup de seguridad'
);
}
}
/**
* Rollback de migración (revertir a estado anterior)
*
* @param string $backup_name Nombre del backup a restaurar
* @return array Resultado del rollback
*/
public function rollback($backup_name = null) {
// Si no se especifica backup, buscar el más reciente
if ($backup_name === null) {
$backup_name = $this->find_latest_backup();
}
if ($backup_name === null) {
return array(
'success' => false,
'message' => 'No se encontró backup para restaurar'
);
}
// Obtener backup
$backup = get_option($backup_name, false);
if ($backup === false) {
return array(
'success' => false,
'message' => sprintf('Backup "%s" no encontrado', $backup_name)
);
}
// Restaurar en wp_options
$restored = update_option(self::OLD_OPTION_NAME, $backup);
if ($restored) {
// Eliminar configuraciones de la tabla personalizada
$this->db_manager->delete_config(self::COMPONENT_NAME);
return array(
'success' => true,
'message' => 'Rollback completado exitosamente',
'backup_used' => $backup_name
);
} else {
return array(
'success' => false,
'message' => 'No se pudo restaurar el backup'
);
}
}
/**
* Buscar el backup más reciente
*
* @return string|null Nombre del backup más reciente o null
*/
private function find_latest_backup() {
global $wpdb;
// Buscar opciones que empiecen con el patrón de backup
$pattern = self::OLD_OPTION_NAME . '_backup_%';
$backup_name = $wpdb->get_var($wpdb->prepare(
"SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE %s
ORDER BY option_id DESC
LIMIT 1",
$pattern
));
return $backup_name;
}
/**
* Listar todos los backups disponibles
*
* @return array Lista de nombres de backups
*/
public function list_backups() {
global $wpdb;
$pattern = self::OLD_OPTION_NAME . '_backup_%';
$backups = $wpdb->get_col($wpdb->prepare(
"SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE %s
ORDER BY option_id DESC",
$pattern
));
return $backups;
}
/**
* Eliminar un backup específico
*
* @param string $backup_name Nombre del backup a eliminar
* @return bool True si se eliminó, false si no
*/
public function delete_backup($backup_name) {
return delete_option($backup_name);
}
/**
* Obtener estadísticas de la migración
*
* @return array Estadísticas
*/
public function get_migration_stats() {
$old_options = get_option(self::OLD_OPTION_NAME, array());
$new_config = $this->db_manager->get_config(self::COMPONENT_NAME);
$backups = $this->list_backups();
return array(
'is_migrated' => $this->is_migrated(),
'old_options_count' => count($old_options),
'new_config_count' => count($new_config),
'backups_count' => count($backups),
'backups' => $backups
);
}
}

View File

@@ -4,7 +4,7 @@
*
* Validación de datos por componentes
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -12,7 +12,7 @@ if (!defined('ABSPATH')) {
exit;
}
class APUS_Validator {
class ROI_Validator {
/**
* Validar todas las configuraciones

View File

@@ -1,24 +0,0 @@
-- ============================================================================
-- Tabla: wp_apus_theme_components_defaults
-- Descripción: Almacena valores por defecto de componentes del tema
-- Versión: 1.0.0
-- Autor: Apus Theme
-- Fecha: 2025-01-13
-- ============================================================================
CREATE TABLE IF NOT EXISTS wp_apus_theme_components_defaults (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
component_name VARCHAR(50) NOT NULL COMMENT 'Nombre del componente (ej: top_bar, navbar)',
config_key VARCHAR(100) NOT NULL COMMENT 'Clave de configuración (ej: message_text, background_color)',
config_value TEXT NOT NULL COMMENT 'Valor por defecto extraído del tema',
data_type ENUM('string','integer','boolean','array','json') NOT NULL COMMENT 'Tipo de dato del valor',
version VARCHAR(20) DEFAULT NULL COMMENT 'Versión del tema cuando se creó este default',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'Fecha de creación del registro',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Fecha de última actualización',
-- Índices para optimizar búsquedas
UNIQUE KEY unique_default_config (component_name, config_key),
INDEX idx_component_name (component_name),
INDEX idx_config_key (config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Tabla de valores por defecto para componentes del tema Apus';

View File

@@ -4,7 +4,7 @@
*
* Métodos estáticos reutilizables para sanitización de datos
*
* @package Apus_Theme
* @package ROI_Theme
* @subpackage Admin_Panel\Sanitizers
* @since 2.1.0
*/
@@ -14,12 +14,12 @@ if (!defined('ABSPATH')) {
}
/**
* Class APUS_Sanitizer_Helper
* Class ROI_Sanitizer_Helper
*
* Proporciona métodos estáticos para sanitización común,
* eliminando código duplicado en los sanitizadores de componentes
*/
class APUS_Sanitizer_Helper {
class ROI_Sanitizer_Helper {
/**
* Sanitiza un valor booleano

View File

@@ -5,7 +5,7 @@
* Sistema de configuración por componentes
* Cada componente del tema es configurable desde el admin panel
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -15,45 +15,17 @@ if (!defined('ABSPATH')) {
}
// Module constants
define('APUS_ADMIN_PANEL_VERSION', '2.1.4');
define('APUS_ADMIN_PANEL_PATH', get_template_directory() . '/admin/');
define('APUS_ADMIN_PANEL_URL', get_template_directory_uri() . '/admin/');
define('ROI_ADMIN_PANEL_VERSION', '2.1.4');
define('ROI_ADMIN_PANEL_PATH', get_template_directory() . '/admin/');
define('ROI_ADMIN_PANEL_URL', get_template_directory_uri() . '/admin/');
// Load classes
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-admin-menu.php';
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-db-manager.php';
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-data-migrator.php';
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-validator.php';
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-theme-options-migrator.php';
require_once ROI_ADMIN_PANEL_PATH . 'includes/class-admin-menu.php';
require_once ROI_ADMIN_PANEL_PATH . 'includes/class-db-manager.php';
require_once ROI_ADMIN_PANEL_PATH . 'includes/class-validator.php';
// Settings Manager
require_once APUS_ADMIN_PANEL_PATH . 'includes/class-settings-manager.php';
require_once ROI_ADMIN_PANEL_PATH . 'includes/class-settings-manager.php';
// Initialize Database Manager
new APUS_DB_Manager();
// Execute data migration (one-time operation)
add_action('admin_init', function() {
$migrator = new APUS_Data_Migrator();
$result = $migrator->maybe_migrate();
if ($result['success'] && isset($result['total_migrated'])) {
error_log('APUS Theme: Migración completada - ' . $result['total_migrated'] . ' registros migrados');
}
});
// Execute Theme Options migration (one-time operation)
add_action('admin_init', function() {
$theme_options_migrator = new APUS_Theme_Options_Migrator();
// Solo ejecutar si no se ha migrado ya
if (!$theme_options_migrator->is_migrated()) {
$result = $theme_options_migrator->migrate();
if ($result['success']) {
error_log('APUS Theme: Theme Options migradas exitosamente - ' . $result['migrated'] . ' configuraciones');
} else {
error_log('APUS Theme: Error en migración de Theme Options - ' . $result['message']);
}
}
});
new ROI_DB_Manager();

View File

@@ -4,7 +4,7 @@
*
* Interfaz de administración de componentes del tema
*
* @package Apus_Theme
* @package ROI_Theme
* @since 2.0.0
*/
@@ -13,10 +13,7 @@ if (!defined('ABSPATH')) {
}
?>
<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>
<div class="wrap roi-admin-panel">
<!-- Navigation Tabs -->
<ul class="nav nav-tabs" role="tablist">
<!-- Tabs de componentes se generarán aquí cuando se ejecute el algoritmo -->

View File

@@ -1,281 +0,0 @@
<?php
/**
* Admin Panel - Theme Options Migration Page
*
* Interfaz para migrar Theme Options de wp_options a tabla personalizada
*
* @package Apus_Theme
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
// Instanciar migrator
$migrator = new APUS_Theme_Options_Migrator();
// Obtener estadísticas
$stats = $migrator->get_migration_stats();
// Procesar acciones
$message = '';
$message_type = '';
if (isset($_POST['apus_migrate_action'])) {
check_admin_referer('apus_migration_action', 'apus_migration_nonce');
$action = sanitize_text_field($_POST['apus_migrate_action']);
switch ($action) {
case 'migrate':
$result = $migrator->migrate();
$message = $result['message'];
$message_type = $result['success'] ? 'success' : 'error';
// Actualizar estadísticas
$stats = $migrator->get_migration_stats();
break;
case 'rollback':
$backup_name = isset($_POST['backup_name']) ? sanitize_text_field($_POST['backup_name']) : null;
$result = $migrator->rollback($backup_name);
$message = $result['message'];
$message_type = $result['success'] ? 'success' : 'error';
// Actualizar estadísticas
$stats = $migrator->get_migration_stats();
break;
case 'delete_backup':
$backup_name = isset($_POST['backup_name']) ? sanitize_text_field($_POST['backup_name']) : '';
if ($backup_name && $migrator->delete_backup($backup_name)) {
$message = 'Backup eliminado correctamente';
$message_type = 'success';
} else {
$message = 'Error al eliminar backup';
$message_type = 'error';
}
// Actualizar estadísticas
$stats = $migrator->get_migration_stats();
break;
}
}
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<p class="description">Migración de Theme Options desde wp_options a tabla personalizada wp_apus_theme_components</p>
<?php if ($message): ?>
<div class="notice notice-<?php echo esc_attr($message_type); ?> is-dismissible">
<p><?php echo esc_html($message); ?></p>
</div>
<?php endif; ?>
<!-- Migration Status Card -->
<div class="card mt-4" style="max-width: 800px;">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="bi bi-info-circle me-2"></i>
Estado de la Migración
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<strong>Estado:</strong>
<?php if ($stats['is_migrated']): ?>
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i>
Migrado
</span>
<?php else: ?>
<span class="badge bg-warning text-dark">
<i class="bi bi-exclamation-triangle me-1"></i>
Pendiente
</span>
<?php endif; ?>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<strong>Backups disponibles:</strong>
<span class="badge bg-info"><?php echo esc_html($stats['backups_count']); ?></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<strong>Opciones en wp_options:</strong>
<span class="badge bg-secondary"><?php echo esc_html($stats['old_options_count']); ?></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<strong>Configs en tabla nueva:</strong>
<span class="badge bg-primary"><?php echo esc_html($stats['new_config_count']); ?></span>
</div>
</div>
</div>
<!-- Progress Bar (si hay migración parcial) -->
<?php if (!$stats['is_migrated'] && $stats['new_config_count'] > 0): ?>
<div class="progress mt-3" style="height: 25px;">
<?php
$total = max($stats['old_options_count'], $stats['new_config_count']);
$percentage = $total > 0 ? ($stats['new_config_count'] / $total) * 100 : 0;
?>
<div class="progress-bar bg-warning" role="progressbar"
style="width: <?php echo esc_attr($percentage); ?>%;"
aria-valuenow="<?php echo esc_attr($percentage); ?>"
aria-valuemin="0"
aria-valuemax="100">
<?php echo esc_html(round($percentage, 1)); ?>%
</div>
</div>
<small class="text-muted">Migración parcial detectada</small>
<?php endif; ?>
</div>
</div>
<!-- Action Buttons Card -->
<div class="card mt-4" style="max-width: 800px;">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0">
<i class="bi bi-gear me-2"></i>
Acciones
</h5>
</div>
<div class="card-body">
<?php if (!$stats['is_migrated']): ?>
<!-- Migrate Button -->
<form method="post" style="display: inline;">
<?php wp_nonce_field('apus_migration_action', 'apus_migration_nonce'); ?>
<input type="hidden" name="apus_migrate_action" value="migrate">
<button type="submit" class="btn btn-primary" onclick="return confirm('¿Está seguro de ejecutar la migración? Se creará un backup automático.');">
<i class="bi bi-arrow-right-circle me-1"></i>
Ejecutar Migración
</button>
</form>
<p class="text-muted mt-2 mb-0">
<small>
<i class="bi bi-info-circle me-1"></i>
Se creará un backup automático antes de la migración. Total de configuraciones: <?php echo esc_html($stats['old_options_count']); ?>
</small>
</p>
<?php else: ?>
<div class="alert alert-success mb-0">
<i class="bi bi-check-circle me-2"></i>
La migración ya ha sido completada. Las opciones del tema ahora se leen desde la tabla personalizada.
</div>
<?php endif; ?>
</div>
</div>
<!-- Backups Card -->
<?php if ($stats['backups_count'] > 0): ?>
<div class="card mt-4" style="max-width: 800px;">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
<i class="bi bi-archive me-2"></i>
Backups Disponibles (<?php echo esc_html($stats['backups_count']); ?>)
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Nombre del Backup</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($stats['backups'] as $backup): ?>
<tr>
<td>
<code><?php echo esc_html($backup); ?></code>
</td>
<td>
<!-- Rollback -->
<form method="post" style="display: inline;" class="me-2">
<?php wp_nonce_field('apus_migration_action', 'apus_migration_nonce'); ?>
<input type="hidden" name="apus_migrate_action" value="rollback">
<input type="hidden" name="backup_name" value="<?php echo esc_attr($backup); ?>">
<button type="submit" class="btn btn-sm btn-warning" onclick="return confirm('¿Está seguro de restaurar este backup? Esto revertirá la migración.');">
<i class="bi bi-arrow-counterclockwise me-1"></i>
Restaurar
</button>
</form>
<!-- Delete -->
<form method="post" style="display: inline;">
<?php wp_nonce_field('apus_migration_action', 'apus_migration_nonce'); ?>
<input type="hidden" name="apus_migrate_action" value="delete_backup">
<input type="hidden" name="backup_name" value="<?php echo esc_attr($backup); ?>">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('¿Está seguro de eliminar este backup?');">
<i class="bi bi-trash me-1"></i>
Eliminar
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endif; ?>
<!-- Technical Information -->
<div class="card mt-4" style="max-width: 800px;">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-code-square me-2"></i>
Información Técnica
</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4">Componente:</dt>
<dd class="col-sm-8"><code>theme</code></dd>
<dt class="col-sm-4">Tabla antigua:</dt>
<dd class="col-sm-8"><code>wp_options</code> (opción: <code>apus_theme_options</code>)</dd>
<dt class="col-sm-4">Tabla nueva:</dt>
<dd class="col-sm-8"><code>wp_apus_theme_components</code></dd>
<dt class="col-sm-4">Versión Admin Panel:</dt>
<dd class="col-sm-8"><code><?php echo esc_html(APUS_ADMIN_PANEL_VERSION); ?></code></dd>
<dt class="col-sm-4">Archivo Helper:</dt>
<dd class="col-sm-8"><code>inc/theme-settings.php</code></dd>
</dl>
</div>
</div>
</div>
<style>
.card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.card-header {
padding: 0.75rem 1rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
border-radius: calc(0.375rem - 1px) calc(0.375rem - 1px) 0 0;
}
.card-body {
padding: 1rem;
}
</style>

View File

@@ -5,7 +5,7 @@
* This file contains examples of how to use theme options throughout the theme.
* DO NOT include this file in functions.php - it's for reference only.
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
@@ -18,7 +18,7 @@ if (!defined('ABSPATH')) {
* EXAMPLE 1: Using options in header.php
*/
function example_display_logo() {
$logo_url = apus_get_logo_url();
$logo_url = roi_get_logo_url();
if ($logo_url) {
?>
@@ -39,8 +39,8 @@ function example_display_logo() {
* EXAMPLE 2: Displaying breadcrumbs
*/
function example_show_breadcrumbs() {
if (apus_show_breadcrumbs() && !is_front_page()) {
$separator = apus_get_breadcrumb_separator();
if (roi_show_breadcrumbs() && !is_front_page()) {
$separator = roi_get_breadcrumb_separator();
echo '<nav class="breadcrumbs">';
echo '<a href="' . esc_url(home_url('/')) . '">Home</a>';
@@ -62,12 +62,12 @@ function example_show_breadcrumbs() {
* EXAMPLE 3: Customizing excerpt
*/
function example_custom_excerpt_length($length) {
return apus_get_excerpt_length();
return roi_get_excerpt_length();
}
add_filter('excerpt_length', 'example_custom_excerpt_length');
function example_custom_excerpt_more($more) {
return apus_get_excerpt_more();
return roi_get_excerpt_more();
}
add_filter('excerpt_more', 'example_custom_excerpt_more');
@@ -75,10 +75,10 @@ add_filter('excerpt_more', 'example_custom_excerpt_more');
* EXAMPLE 4: Displaying related posts in single.php
*/
function example_display_related_posts() {
if (apus_show_related_posts() && is_single()) {
$count = apus_get_related_posts_count();
$taxonomy = apus_get_related_posts_taxonomy();
$title = apus_get_related_posts_title();
if (roi_show_related_posts() && is_single()) {
$count = roi_get_related_posts_count();
$taxonomy = roi_get_related_posts_taxonomy();
$title = roi_get_related_posts_title();
// Get related posts
$post_id = get_the_ID();
@@ -113,7 +113,7 @@ function example_display_related_posts() {
<article class="related-post-item">
<?php if (has_post_thumbnail()) : ?>
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('apus-thumbnail'); ?>
<?php the_post_thumbnail('roi-thumbnail'); ?>
</a>
<?php endif; ?>
<h4>
@@ -121,7 +121,7 @@ function example_display_related_posts() {
</h4>
<div class="post-meta">
<time datetime="<?php echo get_the_date('c'); ?>">
<?php echo get_the_date(apus_get_date_format()); ?>
<?php echo get_the_date(roi_get_date_format()); ?>
</time>
</div>
</article>
@@ -140,9 +140,9 @@ function example_display_related_posts() {
* EXAMPLE 5: Conditional comments display
*/
function example_maybe_show_comments() {
if (is_single() && apus_comments_enabled_for_posts()) {
if (is_single() && roi_comments_enabled_for_posts()) {
comments_template();
} elseif (is_page() && apus_comments_enabled_for_pages()) {
} elseif (is_page() && roi_comments_enabled_for_pages()) {
comments_template();
}
}
@@ -151,10 +151,10 @@ function example_maybe_show_comments() {
* EXAMPLE 6: Featured image on single posts
*/
function example_display_featured_image() {
if (is_single() && apus_show_featured_image_single() && has_post_thumbnail()) {
if (is_single() && roi_show_featured_image_single() && has_post_thumbnail()) {
?>
<div class="post-thumbnail">
<?php the_post_thumbnail('apus-featured-large'); ?>
<?php the_post_thumbnail('roi-featured-large'); ?>
</div>
<?php
}
@@ -164,7 +164,7 @@ function example_display_featured_image() {
* EXAMPLE 7: Author box on single posts
*/
function example_display_author_box() {
if (is_single() && apus_show_author_box()) {
if (is_single() && roi_show_author_box()) {
$author_id = get_the_author_meta('ID');
?>
<div class="author-box">
@@ -175,7 +175,7 @@ function example_display_author_box() {
<h4 class="author-name"><?php the_author(); ?></h4>
<p class="author-bio"><?php the_author_meta('description'); ?></p>
<a href="<?php echo get_author_posts_url($author_id); ?>" class="author-link">
<?php _e('View all posts', 'apus-theme'); ?>
<?php _e('View all posts', 'roi-theme'); ?>
</a>
</div>
</div>
@@ -187,7 +187,7 @@ function example_display_author_box() {
* EXAMPLE 8: Social media links in footer
*/
function example_display_social_links() {
$social_links = apus_get_social_links();
$social_links = roi_get_social_links();
// Filter out empty links
$social_links = array_filter($social_links);
@@ -213,7 +213,7 @@ function example_display_social_links() {
* EXAMPLE 9: Copyright text in footer
*/
function example_display_copyright() {
$copyright = apus_get_copyright_text();
$copyright = roi_get_copyright_text();
if ($copyright) {
echo '<div class="copyright">' . wp_kses_post($copyright) . '</div>';
@@ -224,7 +224,7 @@ function example_display_copyright() {
* EXAMPLE 10: Custom CSS in header
*/
function example_add_custom_css() {
$custom_css = apus_get_custom_css();
$custom_css = roi_get_custom_css();
if ($custom_css) {
echo '<style type="text/css">' . "\n";
@@ -238,7 +238,7 @@ add_action('wp_head', 'example_add_custom_css', 100);
* EXAMPLE 11: Custom JS in header
*/
function example_add_custom_js_header() {
$custom_js = apus_get_custom_js_header();
$custom_js = roi_get_custom_js_header();
if ($custom_js) {
echo '<script type="text/javascript">' . "\n";
@@ -252,7 +252,7 @@ add_action('wp_head', 'example_add_custom_js_header', 100);
* EXAMPLE 12: Custom JS in footer
*/
function example_add_custom_js_footer() {
$custom_js = apus_get_custom_js_footer();
$custom_js = roi_get_custom_js_footer();
if ($custom_js) {
echo '<script type="text/javascript">' . "\n";
@@ -267,7 +267,7 @@ add_action('wp_footer', 'example_add_custom_js_footer', 100);
*/
function example_set_archive_posts_per_page($query) {
if ($query->is_archive() && !is_admin() && $query->is_main_query()) {
$posts_per_page = apus_get_archive_posts_per_page();
$posts_per_page = roi_get_archive_posts_per_page();
$query->set('posts_per_page', $posts_per_page);
}
}
@@ -278,18 +278,18 @@ add_action('pre_get_posts', 'example_set_archive_posts_per_page');
*/
function example_apply_performance_settings() {
// Remove emoji scripts
if (apus_is_performance_enabled('remove_emoji')) {
if (roi_is_performance_enabled('remove_emoji')) {
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
}
// Remove embeds
if (apus_is_performance_enabled('remove_embeds')) {
if (roi_is_performance_enabled('remove_embeds')) {
wp_deregister_script('wp-embed');
}
// Remove Dashicons for non-logged users
if (apus_is_performance_enabled('remove_dashicons') && !is_user_logged_in()) {
if (roi_is_performance_enabled('remove_dashicons') && !is_user_logged_in()) {
wp_deregister_style('dashicons');
}
}
@@ -299,7 +299,7 @@ add_action('wp_enqueue_scripts', 'example_apply_performance_settings', 100);
* EXAMPLE 15: Lazy loading images
*/
function example_add_lazy_loading($attr, $attachment, $size) {
if (apus_is_lazy_loading_enabled()) {
if (roi_is_lazy_loading_enabled()) {
$attr['loading'] = 'lazy';
}
return $attr;
@@ -313,9 +313,9 @@ function example_get_layout_class() {
$layout = 'right-sidebar'; // default
if (is_single()) {
$layout = apus_get_default_post_layout();
$layout = roi_get_default_post_layout();
} elseif (is_page()) {
$layout = apus_get_default_page_layout();
$layout = roi_get_default_page_layout();
}
return 'layout-' . $layout;
@@ -325,7 +325,7 @@ function example_get_layout_class() {
* EXAMPLE 17: Display post meta conditionally
*/
function example_display_post_meta() {
if (!apus_get_option('show_post_meta', true)) {
if (!roi_get_option('show_post_meta', true)) {
return;
}
@@ -333,13 +333,13 @@ function example_display_post_meta() {
<div class="post-meta">
<span class="post-date">
<time datetime="<?php echo get_the_date('c'); ?>">
<?php echo get_the_date(apus_get_date_format()); ?>
<?php echo get_the_date(roi_get_date_format()); ?>
</time>
</span>
<span class="post-author">
<?php the_author(); ?>
</span>
<?php if (apus_get_option('show_post_categories', true)) : ?>
<?php if (roi_get_option('show_post_categories', true)) : ?>
<span class="post-categories">
<?php the_category(', '); ?>
</span>
@@ -352,7 +352,7 @@ function example_display_post_meta() {
* EXAMPLE 18: Display post tags conditionally
*/
function example_display_post_tags() {
if (is_single() && apus_get_option('show_post_tags', true)) {
if (is_single() && roi_get_option('show_post_tags', true)) {
the_tags('<div class="post-tags">', ', ', '</div>');
}
}
@@ -362,7 +362,7 @@ function example_display_post_tags() {
*/
function example_debug_all_options() {
if (current_user_can('manage_options') && isset($_GET['debug_options'])) {
$all_options = apus_get_all_options();
$all_options = roi_get_all_options();
echo '<pre>';
print_r($all_options);
echo '</pre>';
@@ -377,17 +377,17 @@ function example_check_feature() {
// Multiple ways to check boolean options
// Method 1: Using helper function
if (apus_is_option_enabled('enable_breadcrumbs')) {
if (roi_is_option_enabled('enable_breadcrumbs')) {
// Breadcrumbs are enabled
}
// Method 2: Using get_option with default
if (apus_get_option('enable_related_posts', true)) {
if (roi_get_option('enable_related_posts', true)) {
// Related posts are enabled
}
// Method 3: Direct check
$options = apus_get_all_options();
$options = roi_get_all_options();
if (isset($options['enable_lazy_loading']) && $options['enable_lazy_loading']) {
// Lazy loading is enabled
}

View File

@@ -2,7 +2,7 @@
/**
* Theme Options Settings API
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
@@ -14,65 +14,65 @@ if (!defined('ABSPATH')) {
/**
* Register all theme settings
*/
function apus_register_settings() {
function roi_register_settings() {
// Register main options group
register_setting(
'apus_theme_options_group',
'apus_theme_options',
'roi_theme_options_group',
'roi_theme_options',
array(
'sanitize_callback' => 'apus_sanitize_options',
'default' => apus_get_default_options(),
'sanitize_callback' => 'roi_sanitize_options',
'default' => roi_get_default_options(),
)
);
// General Settings Section
add_settings_section(
'apus_general_section',
__('General Settings', 'apus-theme'),
'apus_general_section_callback',
'apus-theme-options'
'roi_general_section',
__('General Settings', 'roi-theme'),
'roi_general_section_callback',
'roitheme-options'
);
// Content Settings Section
add_settings_section(
'apus_content_section',
__('Content Settings', 'apus-theme'),
'apus_content_section_callback',
'apus-theme-options'
'roi_content_section',
__('Content Settings', 'roi-theme'),
'roi_content_section_callback',
'roitheme-options'
);
// Performance Settings Section
add_settings_section(
'apus_performance_section',
__('Performance Settings', 'apus-theme'),
'apus_performance_section_callback',
'apus-theme-options'
'roi_performance_section',
__('Performance Settings', 'roi-theme'),
'roi_performance_section_callback',
'roitheme-options'
);
// Related Posts Settings Section
add_settings_section(
'apus_related_posts_section',
__('Related Posts Settings', 'apus-theme'),
'apus_related_posts_section_callback',
'apus-theme-options'
'roi_related_posts_section',
__('Related Posts Settings', 'roi-theme'),
'roi_related_posts_section_callback',
'roitheme-options'
);
// Social Share Settings Section
add_settings_section(
'apus_social_share_section',
__('Social Share Buttons', 'apus-theme'),
'apus_social_share_section_callback',
'apus-theme-options'
'roi_social_share_section',
__('Social Share Buttons', 'roi-theme'),
'roi_social_share_section_callback',
'roitheme-options'
);
}
add_action('admin_init', 'apus_register_settings');
add_action('admin_init', 'roi_register_settings');
/**
* Get default options
*
* @return array
*/
function apus_get_default_options() {
function roi_get_default_options() {
return array(
// General
'site_logo' => 0,
@@ -81,7 +81,7 @@ function apus_get_default_options() {
'breadcrumb_separator' => '>',
'date_format' => 'd/m/Y',
'time_format' => 'H:i',
'copyright_text' => sprintf(__('&copy; %s %s. All rights reserved.', 'apus-theme'), date('Y'), get_bloginfo('name')),
'copyright_text' => sprintf(__('&copy; %s %s. All rights reserved.', 'roi-theme'), date('Y'), get_bloginfo('name')),
'social_facebook' => '',
'social_twitter' => '',
'social_instagram' => '',
@@ -115,12 +115,12 @@ function apus_get_default_options() {
'enable_related_posts' => true,
'related_posts_count' => 3,
'related_posts_taxonomy' => 'category',
'related_posts_title' => __('Related Posts', 'apus-theme'),
'related_posts_title' => __('Related Posts', 'roi-theme'),
'related_posts_columns' => 3,
// Social Share Buttons
'apus_enable_share_buttons' => '1',
'apus_share_text' => __('Compartir:', 'apus-theme'),
'roi_enable_share_buttons' => '1',
'roi_share_text' => __('Compartir:', 'roi-theme'),
// Advanced
'custom_css' => '',
@@ -132,24 +132,24 @@ function apus_get_default_options() {
/**
* Section Callbacks
*/
function apus_general_section_callback() {
echo '<p>' . __('Configure general theme settings including logo, branding, and social media.', 'apus-theme') . '</p>';
function roi_general_section_callback() {
echo '<p>' . __('Configure general theme settings including logo, branding, and social media.', 'roi-theme') . '</p>';
}
function apus_content_section_callback() {
echo '<p>' . __('Configure content display settings for posts, pages, and archives.', 'apus-theme') . '</p>';
function roi_content_section_callback() {
echo '<p>' . __('Configure content display settings for posts, pages, and archives.', 'roi-theme') . '</p>';
}
function apus_performance_section_callback() {
echo '<p>' . __('Optimize your site performance with these settings.', 'apus-theme') . '</p>';
function roi_performance_section_callback() {
echo '<p>' . __('Optimize your site performance with these settings.', 'roi-theme') . '</p>';
}
function apus_related_posts_section_callback() {
echo '<p>' . __('Configure related posts display on single post pages.', 'apus-theme') . '</p>';
function roi_related_posts_section_callback() {
echo '<p>' . __('Configure related posts display on single post pages.', 'roi-theme') . '</p>';
}
function apus_social_share_section_callback() {
echo '<p>' . __('Configure social share buttons display on single post pages.', 'apus-theme') . '</p>';
function roi_social_share_section_callback() {
echo '<p>' . __('Configure social share buttons display on single post pages.', 'roi-theme') . '</p>';
}
/**
@@ -158,7 +158,7 @@ function apus_social_share_section_callback() {
* @param array $input The input array
* @return array The sanitized array
*/
function apus_sanitize_options($input) {
function roi_sanitize_options($input) {
$sanitized = array();
if (!is_array($input)) {
@@ -208,17 +208,17 @@ function apus_sanitize_options($input) {
$sanitized['enable_related_posts'] = isset($input['enable_related_posts']) ? (bool) $input['enable_related_posts'] : false;
$sanitized['related_posts_count'] = isset($input['related_posts_count']) ? absint($input['related_posts_count']) : 3;
$sanitized['related_posts_taxonomy'] = isset($input['related_posts_taxonomy']) ? sanitize_text_field($input['related_posts_taxonomy']) : 'category';
$sanitized['related_posts_title'] = isset($input['related_posts_title']) ? sanitize_text_field($input['related_posts_title']) : __('Related Posts', 'apus-theme');
$sanitized['related_posts_title'] = isset($input['related_posts_title']) ? sanitize_text_field($input['related_posts_title']) : __('Related Posts', 'roi-theme');
$sanitized['related_posts_columns'] = isset($input['related_posts_columns']) ? absint($input['related_posts_columns']) : 3;
// Social Share Buttons
$sanitized['apus_enable_share_buttons'] = isset($input['apus_enable_share_buttons']) ? sanitize_text_field($input['apus_enable_share_buttons']) : '1';
$sanitized['apus_share_text'] = isset($input['apus_share_text']) ? sanitize_text_field($input['apus_share_text']) : __('Compartir:', 'apus-theme');
$sanitized['roi_enable_share_buttons'] = isset($input['roi_enable_share_buttons']) ? sanitize_text_field($input['roi_enable_share_buttons']) : '1';
$sanitized['roi_share_text'] = isset($input['roi_share_text']) ? sanitize_text_field($input['roi_share_text']) : __('Compartir:', 'roi-theme');
// Advanced Settings
$sanitized['custom_css'] = isset($input['custom_css']) ? apus_sanitize_css($input['custom_css']) : '';
$sanitized['custom_js_header'] = isset($input['custom_js_header']) ? apus_sanitize_js($input['custom_js_header']) : '';
$sanitized['custom_js_footer'] = isset($input['custom_js_footer']) ? apus_sanitize_js($input['custom_js_footer']) : '';
$sanitized['custom_css'] = isset($input['custom_css']) ? roi_sanitize_css($input['custom_css']) : '';
$sanitized['custom_js_header'] = isset($input['custom_js_header']) ? roi_sanitize_js($input['custom_js_header']) : '';
$sanitized['custom_js_footer'] = isset($input['custom_js_footer']) ? roi_sanitize_js($input['custom_js_footer']) : '';
return $sanitized;
}
@@ -226,12 +226,12 @@ function apus_sanitize_options($input) {
/**
* NOTE: All sanitization functions have been moved to inc/sanitize-functions.php
* to avoid function redeclaration errors. This includes:
* - apus_sanitize_css()
* - apus_sanitize_js()
* - apus_sanitize_integer()
* - apus_sanitize_text()
* - apus_sanitize_url()
* - apus_sanitize_html()
* - apus_sanitize_checkbox()
* - apus_sanitize_select()
* - roi_sanitize_css()
* - roi_sanitize_js()
* - roi_sanitize_integer()
* - roi_sanitize_text()
* - roi_sanitize_url()
* - roi_sanitize_html()
* - roi_sanitize_checkbox()
* - roi_sanitize_select()
*/

View File

@@ -2,7 +2,7 @@
/**
* Theme Options Page Template
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
@@ -12,103 +12,101 @@ if (!defined('ABSPATH')) {
}
// Get current options
$options = get_option('apus_theme_options', apus_get_default_options());
$options = get_option('roi_theme_options', roi_get_default_options());
?>
<div class="wrap apus-theme-options">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<div class="apus-options-header">
<div class="apus-options-logo">
<h2><?php _e('Apus Theme', 'apus-theme'); ?></h2>
<span class="version"><?php echo 'v' . APUS_VERSION; ?></span>
<div class="wrap roi-theme-options">
<div class="roi-options-header">
<div class="roi-options-logo">
<h2><?php _e('ROI Theme', 'roi-theme'); ?></h2>
<span class="version"><?php echo 'v' . ROI_VERSION; ?></span>
</div>
<div class="apus-options-actions">
<button type="button" class="button button-secondary" id="apus-export-options">
<div class="roi-options-actions">
<button type="button" class="button button-secondary" id="roi-export-options">
<span class="dashicons dashicons-download"></span>
<?php _e('Export Options', 'apus-theme'); ?>
<?php _e('Export Options', 'roi-theme'); ?>
</button>
<button type="button" class="button button-secondary" id="apus-import-options">
<button type="button" class="button button-secondary" id="roi-import-options">
<span class="dashicons dashicons-upload"></span>
<?php _e('Import Options', 'apus-theme'); ?>
<?php _e('Import Options', 'roi-theme'); ?>
</button>
<button type="button" class="button button-secondary" id="apus-reset-options">
<button type="button" class="button button-secondary" id="roi-reset-options">
<span class="dashicons dashicons-image-rotate"></span>
<?php _e('Reset to Defaults', 'apus-theme'); ?>
<?php _e('Reset to Defaults', 'roi-theme'); ?>
</button>
</div>
</div>
<form method="post" action="options.php" class="apus-options-form">
<form method="post" action="options.php" class="roi-options-form">
<?php
settings_fields('apus_theme_options_group');
settings_fields('roi_theme_options_group');
?>
<div class="apus-options-container">
<div class="roi-options-container">
<!-- Tabs Navigation -->
<div class="apus-tabs-nav">
<div class="roi-tabs-nav">
<ul>
<li class="active">
<a href="#general" data-tab="general">
<span class="dashicons dashicons-admin-settings"></span>
<?php _e('General', 'apus-theme'); ?>
<?php _e('General', 'roi-theme'); ?>
</a>
</li>
<li>
<a href="#content" data-tab="content">
<span class="dashicons dashicons-edit-page"></span>
<?php _e('Content', 'apus-theme'); ?>
<?php _e('Content', 'roi-theme'); ?>
</a>
</li>
<li>
<a href="#performance" data-tab="performance">
<span class="dashicons dashicons-performance"></span>
<?php _e('Performance', 'apus-theme'); ?>
<?php _e('Performance', 'roi-theme'); ?>
</a>
</li>
<li>
<a href="#related-posts" data-tab="related-posts">
<span class="dashicons dashicons-admin-links"></span>
<?php _e('Related Posts', 'apus-theme'); ?>
<?php _e('Related Posts', 'roi-theme'); ?>
</a>
</li>
<li>
<a href="#advanced" data-tab="advanced">
<span class="dashicons dashicons-admin-tools"></span>
<?php _e('Advanced', 'apus-theme'); ?>
<?php _e('Advanced', 'roi-theme'); ?>
</a>
</li>
</ul>
</div>
<!-- Tabs Content -->
<div class="apus-tabs-content">
<div class="roi-tabs-content">
<!-- General Tab -->
<div id="general" class="apus-tab-pane active">
<h2><?php _e('General Settings', 'apus-theme'); ?></h2>
<p class="description"><?php _e('Configure general theme settings including logo, branding, and social media.', 'apus-theme'); ?></p>
<div id="general" class="roi-tab-pane active">
<h2><?php _e('General Settings', 'roi-theme'); ?></h2>
<p class="description"><?php _e('Configure general theme settings including logo, branding, and social media.', 'roi-theme'); ?></p>
<table class="form-table">
<!-- Site Logo -->
<tr>
<th scope="row">
<label for="site_logo"><?php _e('Site Logo', 'apus-theme'); ?></label>
<label for="site_logo"><?php _e('Site Logo', 'roi-theme'); ?></label>
</th>
<td>
<div class="apus-image-upload">
<input type="hidden" name="apus_theme_options[site_logo]" id="site_logo" value="<?php echo esc_attr($options['site_logo'] ?? 0); ?>" class="apus-image-id" />
<div class="apus-image-preview">
<div class="roi-image-upload">
<input type="hidden" name="roi_theme_options[site_logo]" id="site_logo" value="<?php echo esc_attr($options['site_logo'] ?? 0); ?>" class="roi-image-id" />
<div class="roi-image-preview">
<?php
$logo_id = $options['site_logo'] ?? 0;
if ($logo_id) {
echo wp_get_attachment_image($logo_id, 'medium', false, array('class' => 'apus-preview-image'));
echo wp_get_attachment_image($logo_id, 'medium', false, array('class' => 'roi-preview-image'));
}
?>
</div>
<button type="button" class="button apus-upload-image"><?php _e('Upload Logo', 'apus-theme'); ?></button>
<button type="button" class="button apus-remove-image" <?php echo (!$logo_id ? 'style="display:none;"' : ''); ?>><?php _e('Remove Logo', 'apus-theme'); ?></button>
<p class="description"><?php _e('Upload your site logo. Recommended size: 200x60px', 'apus-theme'); ?></p>
<button type="button" class="button roi-upload-image"><?php _e('Upload Logo', 'roi-theme'); ?></button>
<button type="button" class="button roi-remove-image" <?php echo (!$logo_id ? 'style="display:none;"' : ''); ?>><?php _e('Remove Logo', 'roi-theme'); ?></button>
<p class="description"><?php _e('Upload your site logo. Recommended size: 200x60px', 'roi-theme'); ?></p>
</div>
</td>
</tr>
@@ -116,22 +114,22 @@ $options = get_option('apus_theme_options', apus_get_default_options());
<!-- Site Favicon -->
<tr>
<th scope="row">
<label for="site_favicon"><?php _e('Site Favicon', 'apus-theme'); ?></label>
<label for="site_favicon"><?php _e('Site Favicon', 'roi-theme'); ?></label>
</th>
<td>
<div class="apus-image-upload">
<input type="hidden" name="apus_theme_options[site_favicon]" id="site_favicon" value="<?php echo esc_attr($options['site_favicon'] ?? 0); ?>" class="apus-image-id" />
<div class="apus-image-preview">
<div class="roi-image-upload">
<input type="hidden" name="roi_theme_options[site_favicon]" id="site_favicon" value="<?php echo esc_attr($options['site_favicon'] ?? 0); ?>" class="roi-image-id" />
<div class="roi-image-preview">
<?php
$favicon_id = $options['site_favicon'] ?? 0;
if ($favicon_id) {
echo wp_get_attachment_image($favicon_id, 'thumbnail', false, array('class' => 'apus-preview-image'));
echo wp_get_attachment_image($favicon_id, 'thumbnail', false, array('class' => 'roi-preview-image'));
}
?>
</div>
<button type="button" class="button apus-upload-image"><?php _e('Upload Favicon', 'apus-theme'); ?></button>
<button type="button" class="button apus-remove-image" <?php echo (!$favicon_id ? 'style="display:none;"' : ''); ?>><?php _e('Remove Favicon', 'apus-theme'); ?></button>
<p class="description"><?php _e('Upload your site favicon. Recommended size: 32x32px or 64x64px', 'apus-theme'); ?></p>
<button type="button" class="button roi-upload-image"><?php _e('Upload Favicon', 'roi-theme'); ?></button>
<button type="button" class="button roi-remove-image" <?php echo (!$favicon_id ? 'style="display:none;"' : ''); ?>><?php _e('Remove Favicon', 'roi-theme'); ?></button>
<p class="description"><?php _e('Upload your site favicon. Recommended size: 32x32px or 64x64px', 'roi-theme'); ?></p>
</div>
</td>
</tr>
@@ -139,501 +137,501 @@ $options = get_option('apus_theme_options', apus_get_default_options());
<!-- Enable Breadcrumbs -->
<tr>
<th scope="row">
<label for="enable_breadcrumbs"><?php _e('Enable Breadcrumbs', 'apus-theme'); ?></label>
<label for="enable_breadcrumbs"><?php _e('Enable Breadcrumbs', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[enable_breadcrumbs]" id="enable_breadcrumbs" value="1" <?php checked(isset($options['enable_breadcrumbs']) ? $options['enable_breadcrumbs'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[enable_breadcrumbs]" id="enable_breadcrumbs" value="1" <?php checked(isset($options['enable_breadcrumbs']) ? $options['enable_breadcrumbs'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Show breadcrumbs navigation on pages and posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Show breadcrumbs navigation on pages and posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Breadcrumb Separator -->
<tr>
<th scope="row">
<label for="breadcrumb_separator"><?php _e('Breadcrumb Separator', 'apus-theme'); ?></label>
<label for="breadcrumb_separator"><?php _e('Breadcrumb Separator', 'roi-theme'); ?></label>
</th>
<td>
<input type="text" name="apus_theme_options[breadcrumb_separator]" id="breadcrumb_separator" value="<?php echo esc_attr($options['breadcrumb_separator'] ?? '>'); ?>" class="regular-text" />
<p class="description"><?php _e('Character or symbol to separate breadcrumb items (e.g., >, /, »)', 'apus-theme'); ?></p>
<input type="text" name="roi_theme_options[breadcrumb_separator]" id="breadcrumb_separator" value="<?php echo esc_attr($options['breadcrumb_separator'] ?? '>'); ?>" class="regular-text" />
<p class="description"><?php _e('Character or symbol to separate breadcrumb items (e.g., >, /, »)', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Date Format -->
<tr>
<th scope="row">
<label for="date_format"><?php _e('Date Format', 'apus-theme'); ?></label>
<label for="date_format"><?php _e('Date Format', 'roi-theme'); ?></label>
</th>
<td>
<input type="text" name="apus_theme_options[date_format]" id="date_format" value="<?php echo esc_attr($options['date_format'] ?? 'd/m/Y'); ?>" class="regular-text" />
<p class="description"><?php _e('PHP date format (e.g., d/m/Y, m/d/Y, Y-m-d)', 'apus-theme'); ?></p>
<input type="text" name="roi_theme_options[date_format]" id="date_format" value="<?php echo esc_attr($options['date_format'] ?? 'd/m/Y'); ?>" class="regular-text" />
<p class="description"><?php _e('PHP date format (e.g., d/m/Y, m/d/Y, Y-m-d)', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Time Format -->
<tr>
<th scope="row">
<label for="time_format"><?php _e('Time Format', 'apus-theme'); ?></label>
<label for="time_format"><?php _e('Time Format', 'roi-theme'); ?></label>
</th>
<td>
<input type="text" name="apus_theme_options[time_format]" id="time_format" value="<?php echo esc_attr($options['time_format'] ?? 'H:i'); ?>" class="regular-text" />
<p class="description"><?php _e('PHP time format (e.g., H:i, g:i A)', 'apus-theme'); ?></p>
<input type="text" name="roi_theme_options[time_format]" id="time_format" value="<?php echo esc_attr($options['time_format'] ?? 'H:i'); ?>" class="regular-text" />
<p class="description"><?php _e('PHP time format (e.g., H:i, g:i A)', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Copyright Text -->
<tr>
<th scope="row">
<label for="copyright_text"><?php _e('Copyright Text', 'apus-theme'); ?></label>
<label for="copyright_text"><?php _e('Copyright Text', 'roi-theme'); ?></label>
</th>
<td>
<textarea name="apus_theme_options[copyright_text]" id="copyright_text" rows="3" class="large-text"><?php echo esc_textarea($options['copyright_text'] ?? sprintf(__('&copy; %s %s. All rights reserved.', 'apus-theme'), date('Y'), get_bloginfo('name'))); ?></textarea>
<p class="description"><?php _e('Footer copyright text. HTML allowed.', 'apus-theme'); ?></p>
<textarea name="roi_theme_options[copyright_text]" id="copyright_text" rows="3" class="large-text"><?php echo esc_textarea($options['copyright_text'] ?? sprintf(__('&copy; %s %s. All rights reserved.', 'roi-theme'), date('Y'), get_bloginfo('name'))); ?></textarea>
<p class="description"><?php _e('Footer copyright text. HTML allowed.', 'roi-theme'); ?></p>
</td>
</tr>
</table>
<h3><?php _e('Social Media Links', 'apus-theme'); ?></h3>
<h3><?php _e('Social Media Links', 'roi-theme'); ?></h3>
<table class="form-table">
<!-- Facebook -->
<tr>
<th scope="row">
<label for="social_facebook"><?php _e('Facebook URL', 'apus-theme'); ?></label>
<label for="social_facebook"><?php _e('Facebook URL', 'roi-theme'); ?></label>
</th>
<td>
<input type="url" name="apus_theme_options[social_facebook]" id="social_facebook" value="<?php echo esc_url($options['social_facebook'] ?? ''); ?>" class="regular-text" placeholder="https://facebook.com/yourpage" />
<input type="url" name="roi_theme_options[social_facebook]" id="social_facebook" value="<?php echo esc_url($options['social_facebook'] ?? ''); ?>" class="regular-text" placeholder="https://facebook.com/yourpage" />
</td>
</tr>
<!-- Twitter -->
<tr>
<th scope="row">
<label for="social_twitter"><?php _e('Twitter URL', 'apus-theme'); ?></label>
<label for="social_twitter"><?php _e('Twitter URL', 'roi-theme'); ?></label>
</th>
<td>
<input type="url" name="apus_theme_options[social_twitter]" id="social_twitter" value="<?php echo esc_url($options['social_twitter'] ?? ''); ?>" class="regular-text" placeholder="https://twitter.com/youraccount" />
<input type="url" name="roi_theme_options[social_twitter]" id="social_twitter" value="<?php echo esc_url($options['social_twitter'] ?? ''); ?>" class="regular-text" placeholder="https://twitter.com/youraccount" />
</td>
</tr>
<!-- Instagram -->
<tr>
<th scope="row">
<label for="social_instagram"><?php _e('Instagram URL', 'apus-theme'); ?></label>
<label for="social_instagram"><?php _e('Instagram URL', 'roi-theme'); ?></label>
</th>
<td>
<input type="url" name="apus_theme_options[social_instagram]" id="social_instagram" value="<?php echo esc_url($options['social_instagram'] ?? ''); ?>" class="regular-text" placeholder="https://instagram.com/youraccount" />
<input type="url" name="roi_theme_options[social_instagram]" id="social_instagram" value="<?php echo esc_url($options['social_instagram'] ?? ''); ?>" class="regular-text" placeholder="https://instagram.com/youraccount" />
</td>
</tr>
<!-- LinkedIn -->
<tr>
<th scope="row">
<label for="social_linkedin"><?php _e('LinkedIn URL', 'apus-theme'); ?></label>
<label for="social_linkedin"><?php _e('LinkedIn URL', 'roi-theme'); ?></label>
</th>
<td>
<input type="url" name="apus_theme_options[social_linkedin]" id="social_linkedin" value="<?php echo esc_url($options['social_linkedin'] ?? ''); ?>" class="regular-text" placeholder="https://linkedin.com/company/yourcompany" />
<input type="url" name="roi_theme_options[social_linkedin]" id="social_linkedin" value="<?php echo esc_url($options['social_linkedin'] ?? ''); ?>" class="regular-text" placeholder="https://linkedin.com/company/yourcompany" />
</td>
</tr>
<!-- YouTube -->
<tr>
<th scope="row">
<label for="social_youtube"><?php _e('YouTube URL', 'apus-theme'); ?></label>
<label for="social_youtube"><?php _e('YouTube URL', 'roi-theme'); ?></label>
</th>
<td>
<input type="url" name="apus_theme_options[social_youtube]" id="social_youtube" value="<?php echo esc_url($options['social_youtube'] ?? ''); ?>" class="regular-text" placeholder="https://youtube.com/yourchannel" />
<input type="url" name="roi_theme_options[social_youtube]" id="social_youtube" value="<?php echo esc_url($options['social_youtube'] ?? ''); ?>" class="regular-text" placeholder="https://youtube.com/yourchannel" />
</td>
</tr>
</table>
</div>
<!-- Content Tab -->
<div id="content" class="apus-tab-pane">
<h2><?php _e('Content Settings', 'apus-theme'); ?></h2>
<p class="description"><?php _e('Configure content display settings for posts, pages, and archives.', 'apus-theme'); ?></p>
<div id="content" class="roi-tab-pane">
<h2><?php _e('Content Settings', 'roi-theme'); ?></h2>
<p class="description"><?php _e('Configure content display settings for posts, pages, and archives.', 'roi-theme'); ?></p>
<table class="form-table">
<!-- Excerpt Length -->
<tr>
<th scope="row">
<label for="excerpt_length"><?php _e('Excerpt Length', 'apus-theme'); ?></label>
<label for="excerpt_length"><?php _e('Excerpt Length', 'roi-theme'); ?></label>
</th>
<td>
<input type="number" name="apus_theme_options[excerpt_length]" id="excerpt_length" value="<?php echo esc_attr($options['excerpt_length'] ?? 55); ?>" class="small-text" min="10" max="500" />
<p class="description"><?php _e('Number of words to show in excerpt', 'apus-theme'); ?></p>
<input type="number" name="roi_theme_options[excerpt_length]" id="excerpt_length" value="<?php echo esc_attr($options['excerpt_length'] ?? 55); ?>" class="small-text" min="10" max="500" />
<p class="description"><?php _e('Number of words to show in excerpt', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Excerpt More -->
<tr>
<th scope="row">
<label for="excerpt_more"><?php _e('Excerpt More Text', 'apus-theme'); ?></label>
<label for="excerpt_more"><?php _e('Excerpt More Text', 'roi-theme'); ?></label>
</th>
<td>
<input type="text" name="apus_theme_options[excerpt_more]" id="excerpt_more" value="<?php echo esc_attr($options['excerpt_more'] ?? '...'); ?>" class="regular-text" />
<p class="description"><?php _e('Text to append at the end of excerpts', 'apus-theme'); ?></p>
<input type="text" name="roi_theme_options[excerpt_more]" id="excerpt_more" value="<?php echo esc_attr($options['excerpt_more'] ?? '...'); ?>" class="regular-text" />
<p class="description"><?php _e('Text to append at the end of excerpts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Default Post Layout -->
<tr>
<th scope="row">
<label for="default_post_layout"><?php _e('Default Post Layout', 'apus-theme'); ?></label>
<label for="default_post_layout"><?php _e('Default Post Layout', 'roi-theme'); ?></label>
</th>
<td>
<select name="apus_theme_options[default_post_layout]" id="default_post_layout">
<option value="right-sidebar" <?php selected($options['default_post_layout'] ?? 'right-sidebar', 'right-sidebar'); ?>><?php _e('Right Sidebar', 'apus-theme'); ?></option>
<option value="left-sidebar" <?php selected($options['default_post_layout'] ?? 'right-sidebar', 'left-sidebar'); ?>><?php _e('Left Sidebar', 'apus-theme'); ?></option>
<option value="no-sidebar" <?php selected($options['default_post_layout'] ?? 'right-sidebar', 'no-sidebar'); ?>><?php _e('No Sidebar (Full Width)', 'apus-theme'); ?></option>
<select name="roi_theme_options[default_post_layout]" id="default_post_layout">
<option value="right-sidebar" <?php selected($options['default_post_layout'] ?? 'right-sidebar', 'right-sidebar'); ?>><?php _e('Right Sidebar', 'roi-theme'); ?></option>
<option value="left-sidebar" <?php selected($options['default_post_layout'] ?? 'right-sidebar', 'left-sidebar'); ?>><?php _e('Left Sidebar', 'roi-theme'); ?></option>
<option value="no-sidebar" <?php selected($options['default_post_layout'] ?? 'right-sidebar', 'no-sidebar'); ?>><?php _e('No Sidebar (Full Width)', 'roi-theme'); ?></option>
</select>
<p class="description"><?php _e('Default layout for single posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Default layout for single posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Default Page Layout -->
<tr>
<th scope="row">
<label for="default_page_layout"><?php _e('Default Page Layout', 'apus-theme'); ?></label>
<label for="default_page_layout"><?php _e('Default Page Layout', 'roi-theme'); ?></label>
</th>
<td>
<select name="apus_theme_options[default_page_layout]" id="default_page_layout">
<option value="right-sidebar" <?php selected($options['default_page_layout'] ?? 'right-sidebar', 'right-sidebar'); ?>><?php _e('Right Sidebar', 'apus-theme'); ?></option>
<option value="left-sidebar" <?php selected($options['default_page_layout'] ?? 'right-sidebar', 'left-sidebar'); ?>><?php _e('Left Sidebar', 'apus-theme'); ?></option>
<option value="no-sidebar" <?php selected($options['default_page_layout'] ?? 'right-sidebar', 'no-sidebar'); ?>><?php _e('No Sidebar (Full Width)', 'apus-theme'); ?></option>
<select name="roi_theme_options[default_page_layout]" id="default_page_layout">
<option value="right-sidebar" <?php selected($options['default_page_layout'] ?? 'right-sidebar', 'right-sidebar'); ?>><?php _e('Right Sidebar', 'roi-theme'); ?></option>
<option value="left-sidebar" <?php selected($options['default_page_layout'] ?? 'right-sidebar', 'left-sidebar'); ?>><?php _e('Left Sidebar', 'roi-theme'); ?></option>
<option value="no-sidebar" <?php selected($options['default_page_layout'] ?? 'right-sidebar', 'no-sidebar'); ?>><?php _e('No Sidebar (Full Width)', 'roi-theme'); ?></option>
</select>
<p class="description"><?php _e('Default layout for pages', 'apus-theme'); ?></p>
<p class="description"><?php _e('Default layout for pages', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Archive Posts Per Page -->
<tr>
<th scope="row">
<label for="archive_posts_per_page"><?php _e('Archive Posts Per Page', 'apus-theme'); ?></label>
<label for="archive_posts_per_page"><?php _e('Archive Posts Per Page', 'roi-theme'); ?></label>
</th>
<td>
<input type="number" name="apus_theme_options[archive_posts_per_page]" id="archive_posts_per_page" value="<?php echo esc_attr($options['archive_posts_per_page'] ?? 10); ?>" class="small-text" min="1" max="100" />
<p class="description"><?php _e('Number of posts to show on archive pages. Set to 0 to use WordPress default.', 'apus-theme'); ?></p>
<input type="number" name="roi_theme_options[archive_posts_per_page]" id="archive_posts_per_page" value="<?php echo esc_attr($options['archive_posts_per_page'] ?? 10); ?>" class="small-text" min="1" max="100" />
<p class="description"><?php _e('Number of posts to show on archive pages. Set to 0 to use WordPress default.', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Show Featured Image on Single Posts -->
<tr>
<th scope="row">
<label for="show_featured_image_single"><?php _e('Show Featured Image', 'apus-theme'); ?></label>
<label for="show_featured_image_single"><?php _e('Show Featured Image', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[show_featured_image_single]" id="show_featured_image_single" value="1" <?php checked(isset($options['show_featured_image_single']) ? $options['show_featured_image_single'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[show_featured_image_single]" id="show_featured_image_single" value="1" <?php checked(isset($options['show_featured_image_single']) ? $options['show_featured_image_single'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Display featured image at the top of single posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Display featured image at the top of single posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Show Author Box -->
<tr>
<th scope="row">
<label for="show_author_box"><?php _e('Show Author Box', 'apus-theme'); ?></label>
<label for="show_author_box"><?php _e('Show Author Box', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[show_author_box]" id="show_author_box" value="1" <?php checked(isset($options['show_author_box']) ? $options['show_author_box'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[show_author_box]" id="show_author_box" value="1" <?php checked(isset($options['show_author_box']) ? $options['show_author_box'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Display author information box on single posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Display author information box on single posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Enable Comments on Posts -->
<tr>
<th scope="row">
<label for="enable_comments_posts"><?php _e('Enable Comments on Posts', 'apus-theme'); ?></label>
<label for="enable_comments_posts"><?php _e('Enable Comments on Posts', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[enable_comments_posts]" id="enable_comments_posts" value="1" <?php checked(isset($options['enable_comments_posts']) ? $options['enable_comments_posts'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[enable_comments_posts]" id="enable_comments_posts" value="1" <?php checked(isset($options['enable_comments_posts']) ? $options['enable_comments_posts'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Allow comments on blog posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Allow comments on blog posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Enable Comments on Pages -->
<tr>
<th scope="row">
<label for="enable_comments_pages"><?php _e('Enable Comments on Pages', 'apus-theme'); ?></label>
<label for="enable_comments_pages"><?php _e('Enable Comments on Pages', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[enable_comments_pages]" id="enable_comments_pages" value="1" <?php checked(isset($options['enable_comments_pages']) ? $options['enable_comments_pages'] : false, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[enable_comments_pages]" id="enable_comments_pages" value="1" <?php checked(isset($options['enable_comments_pages']) ? $options['enable_comments_pages'] : false, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Allow comments on pages', 'apus-theme'); ?></p>
<p class="description"><?php _e('Allow comments on pages', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Show Post Meta -->
<tr>
<th scope="row">
<label for="show_post_meta"><?php _e('Show Post Meta', 'apus-theme'); ?></label>
<label for="show_post_meta"><?php _e('Show Post Meta', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[show_post_meta]" id="show_post_meta" value="1" <?php checked(isset($options['show_post_meta']) ? $options['show_post_meta'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[show_post_meta]" id="show_post_meta" value="1" <?php checked(isset($options['show_post_meta']) ? $options['show_post_meta'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Display post meta information (date, author, etc.)', 'apus-theme'); ?></p>
<p class="description"><?php _e('Display post meta information (date, author, etc.)', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Show Post Tags -->
<tr>
<th scope="row">
<label for="show_post_tags"><?php _e('Show Post Tags', 'apus-theme'); ?></label>
<label for="show_post_tags"><?php _e('Show Post Tags', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[show_post_tags]" id="show_post_tags" value="1" <?php checked(isset($options['show_post_tags']) ? $options['show_post_tags'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[show_post_tags]" id="show_post_tags" value="1" <?php checked(isset($options['show_post_tags']) ? $options['show_post_tags'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Display tags on single posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Display tags on single posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Show Post Categories -->
<tr>
<th scope="row">
<label for="show_post_categories"><?php _e('Show Post Categories', 'apus-theme'); ?></label>
<label for="show_post_categories"><?php _e('Show Post Categories', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[show_post_categories]" id="show_post_categories" value="1" <?php checked(isset($options['show_post_categories']) ? $options['show_post_categories'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[show_post_categories]" id="show_post_categories" value="1" <?php checked(isset($options['show_post_categories']) ? $options['show_post_categories'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Display categories on single posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Display categories on single posts', 'roi-theme'); ?></p>
</td>
</tr>
</table>
</div>
<!-- Performance Tab -->
<div id="performance" class="apus-tab-pane">
<h2><?php _e('Performance Settings', 'apus-theme'); ?></h2>
<p class="description"><?php _e('Optimize your site performance with these settings. Be careful when enabling these options.', 'apus-theme'); ?></p>
<div id="performance" class="roi-tab-pane">
<h2><?php _e('Performance Settings', 'roi-theme'); ?></h2>
<p class="description"><?php _e('Optimize your site performance with these settings. Be careful when enabling these options.', 'roi-theme'); ?></p>
<table class="form-table">
<!-- Enable Lazy Loading -->
<tr>
<th scope="row">
<label for="enable_lazy_loading"><?php _e('Enable Lazy Loading', 'apus-theme'); ?></label>
<label for="enable_lazy_loading"><?php _e('Enable Lazy Loading', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[enable_lazy_loading]" id="enable_lazy_loading" value="1" <?php checked(isset($options['enable_lazy_loading']) ? $options['enable_lazy_loading'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[enable_lazy_loading]" id="enable_lazy_loading" value="1" <?php checked(isset($options['enable_lazy_loading']) ? $options['enable_lazy_loading'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Enable lazy loading for images to improve page load times', 'apus-theme'); ?></p>
<p class="description"><?php _e('Enable lazy loading for images to improve page load times', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Remove Emoji Scripts -->
<tr>
<th scope="row">
<label for="performance_remove_emoji"><?php _e('Remove Emoji Scripts', 'apus-theme'); ?></label>
<label for="performance_remove_emoji"><?php _e('Remove Emoji Scripts', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[performance_remove_emoji]" id="performance_remove_emoji" value="1" <?php checked(isset($options['performance_remove_emoji']) ? $options['performance_remove_emoji'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[performance_remove_emoji]" id="performance_remove_emoji" value="1" <?php checked(isset($options['performance_remove_emoji']) ? $options['performance_remove_emoji'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Remove WordPress emoji scripts and styles (reduces HTTP requests)', 'apus-theme'); ?></p>
<p class="description"><?php _e('Remove WordPress emoji scripts and styles (reduces HTTP requests)', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Remove Embeds -->
<tr>
<th scope="row">
<label for="performance_remove_embeds"><?php _e('Remove Embeds', 'apus-theme'); ?></label>
<label for="performance_remove_embeds"><?php _e('Remove Embeds', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[performance_remove_embeds]" id="performance_remove_embeds" value="1" <?php checked(isset($options['performance_remove_embeds']) ? $options['performance_remove_embeds'] : false, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[performance_remove_embeds]" id="performance_remove_embeds" value="1" <?php checked(isset($options['performance_remove_embeds']) ? $options['performance_remove_embeds'] : false, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Remove WordPress embed scripts if you don\'t use oEmbed', 'apus-theme'); ?></p>
<p class="description"><?php _e('Remove WordPress embed scripts if you don\'t use oEmbed', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Remove Dashicons on Frontend -->
<tr>
<th scope="row">
<label for="performance_remove_dashicons"><?php _e('Remove Dashicons', 'apus-theme'); ?></label>
<label for="performance_remove_dashicons"><?php _e('Remove Dashicons', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[performance_remove_dashicons]" id="performance_remove_dashicons" value="1" <?php checked(isset($options['performance_remove_dashicons']) ? $options['performance_remove_dashicons'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[performance_remove_dashicons]" id="performance_remove_dashicons" value="1" <?php checked(isset($options['performance_remove_dashicons']) ? $options['performance_remove_dashicons'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Remove Dashicons from frontend for non-logged in users', 'apus-theme'); ?></p>
<p class="description"><?php _e('Remove Dashicons from frontend for non-logged in users', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Defer JavaScript -->
<tr>
<th scope="row">
<label for="performance_defer_js"><?php _e('Defer JavaScript', 'apus-theme'); ?></label>
<label for="performance_defer_js"><?php _e('Defer JavaScript', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[performance_defer_js]" id="performance_defer_js" value="1" <?php checked(isset($options['performance_defer_js']) ? $options['performance_defer_js'] : false, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[performance_defer_js]" id="performance_defer_js" value="1" <?php checked(isset($options['performance_defer_js']) ? $options['performance_defer_js'] : false, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Add defer attribute to JavaScript files (may break some scripts)', 'apus-theme'); ?></p>
<p class="description"><?php _e('Add defer attribute to JavaScript files (may break some scripts)', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Minify HTML -->
<tr>
<th scope="row">
<label for="performance_minify_html"><?php _e('Minify HTML', 'apus-theme'); ?></label>
<label for="performance_minify_html"><?php _e('Minify HTML', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[performance_minify_html]" id="performance_minify_html" value="1" <?php checked(isset($options['performance_minify_html']) ? $options['performance_minify_html'] : false, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[performance_minify_html]" id="performance_minify_html" value="1" <?php checked(isset($options['performance_minify_html']) ? $options['performance_minify_html'] : false, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Minify HTML output to reduce page size', 'apus-theme'); ?></p>
<p class="description"><?php _e('Minify HTML output to reduce page size', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Disable Gutenberg -->
<tr>
<th scope="row">
<label for="performance_disable_gutenberg"><?php _e('Disable Gutenberg', 'apus-theme'); ?></label>
<label for="performance_disable_gutenberg"><?php _e('Disable Gutenberg', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[performance_disable_gutenberg]" id="performance_disable_gutenberg" value="1" <?php checked(isset($options['performance_disable_gutenberg']) ? $options['performance_disable_gutenberg'] : false, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[performance_disable_gutenberg]" id="performance_disable_gutenberg" value="1" <?php checked(isset($options['performance_disable_gutenberg']) ? $options['performance_disable_gutenberg'] : false, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Disable Gutenberg editor and revert to classic editor', 'apus-theme'); ?></p>
<p class="description"><?php _e('Disable Gutenberg editor and revert to classic editor', 'roi-theme'); ?></p>
</td>
</tr>
</table>
</div>
<!-- Related Posts Tab -->
<div id="related-posts" class="apus-tab-pane">
<h2><?php _e('Related Posts Settings', 'apus-theme'); ?></h2>
<p class="description"><?php _e('Configure related posts display on single post pages.', 'apus-theme'); ?></p>
<div id="related-posts" class="roi-tab-pane">
<h2><?php _e('Related Posts Settings', 'roi-theme'); ?></h2>
<p class="description"><?php _e('Configure related posts display on single post pages.', 'roi-theme'); ?></p>
<table class="form-table">
<!-- Enable Related Posts -->
<tr>
<th scope="row">
<label for="enable_related_posts"><?php _e('Enable Related Posts', 'apus-theme'); ?></label>
<label for="enable_related_posts"><?php _e('Enable Related Posts', 'roi-theme'); ?></label>
</th>
<td>
<label class="apus-switch">
<input type="checkbox" name="apus_theme_options[enable_related_posts]" id="enable_related_posts" value="1" <?php checked(isset($options['enable_related_posts']) ? $options['enable_related_posts'] : true, true); ?> />
<span class="apus-slider"></span>
<label class="roi-switch">
<input type="checkbox" name="roi_theme_options[enable_related_posts]" id="enable_related_posts" value="1" <?php checked(isset($options['enable_related_posts']) ? $options['enable_related_posts'] : true, true); ?> />
<span class="roi-slider"></span>
</label>
<p class="description"><?php _e('Show related posts at the end of single posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Show related posts at the end of single posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Related Posts Count -->
<tr>
<th scope="row">
<label for="related_posts_count"><?php _e('Number of Related Posts', 'apus-theme'); ?></label>
<label for="related_posts_count"><?php _e('Number of Related Posts', 'roi-theme'); ?></label>
</th>
<td>
<input type="number" name="apus_theme_options[related_posts_count]" id="related_posts_count" value="<?php echo esc_attr($options['related_posts_count'] ?? 3); ?>" class="small-text" min="1" max="12" />
<p class="description"><?php _e('How many related posts to display', 'apus-theme'); ?></p>
<input type="number" name="roi_theme_options[related_posts_count]" id="related_posts_count" value="<?php echo esc_attr($options['related_posts_count'] ?? 3); ?>" class="small-text" min="1" max="12" />
<p class="description"><?php _e('How many related posts to display', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Related Posts Taxonomy -->
<tr>
<th scope="row">
<label for="related_posts_taxonomy"><?php _e('Relate Posts By', 'apus-theme'); ?></label>
<label for="related_posts_taxonomy"><?php _e('Relate Posts By', 'roi-theme'); ?></label>
</th>
<td>
<select name="apus_theme_options[related_posts_taxonomy]" id="related_posts_taxonomy">
<option value="category" <?php selected($options['related_posts_taxonomy'] ?? 'category', 'category'); ?>><?php _e('Category', 'apus-theme'); ?></option>
<option value="tag" <?php selected($options['related_posts_taxonomy'] ?? 'category', 'tag'); ?>><?php _e('Tag', 'apus-theme'); ?></option>
<option value="both" <?php selected($options['related_posts_taxonomy'] ?? 'category', 'both'); ?>><?php _e('Category and Tag', 'apus-theme'); ?></option>
<select name="roi_theme_options[related_posts_taxonomy]" id="related_posts_taxonomy">
<option value="category" <?php selected($options['related_posts_taxonomy'] ?? 'category', 'category'); ?>><?php _e('Category', 'roi-theme'); ?></option>
<option value="tag" <?php selected($options['related_posts_taxonomy'] ?? 'category', 'tag'); ?>><?php _e('Tag', 'roi-theme'); ?></option>
<option value="both" <?php selected($options['related_posts_taxonomy'] ?? 'category', 'both'); ?>><?php _e('Category and Tag', 'roi-theme'); ?></option>
</select>
<p class="description"><?php _e('How to determine related posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('How to determine related posts', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Related Posts Title -->
<tr>
<th scope="row">
<label for="related_posts_title"><?php _e('Related Posts Title', 'apus-theme'); ?></label>
<label for="related_posts_title"><?php _e('Related Posts Title', 'roi-theme'); ?></label>
</th>
<td>
<input type="text" name="apus_theme_options[related_posts_title]" id="related_posts_title" value="<?php echo esc_attr($options['related_posts_title'] ?? __('Related Posts', 'apus-theme')); ?>" class="regular-text" />
<p class="description"><?php _e('Title to display above related posts section', 'apus-theme'); ?></p>
<input type="text" name="roi_theme_options[related_posts_title]" id="related_posts_title" value="<?php echo esc_attr($options['related_posts_title'] ?? __('Related Posts', 'roi-theme')); ?>" class="regular-text" />
<p class="description"><?php _e('Title to display above related posts section', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Related Posts Columns -->
<tr>
<th scope="row">
<label for="related_posts_columns"><?php _e('Columns', 'apus-theme'); ?></label>
<label for="related_posts_columns"><?php _e('Columns', 'roi-theme'); ?></label>
</th>
<td>
<select name="apus_theme_options[related_posts_columns]" id="related_posts_columns">
<option value="2" <?php selected($options['related_posts_columns'] ?? 3, 2); ?>><?php _e('2 Columns', 'apus-theme'); ?></option>
<option value="3" <?php selected($options['related_posts_columns'] ?? 3, 3); ?>><?php _e('3 Columns', 'apus-theme'); ?></option>
<option value="4" <?php selected($options['related_posts_columns'] ?? 3, 4); ?>><?php _e('4 Columns', 'apus-theme'); ?></option>
<select name="roi_theme_options[related_posts_columns]" id="related_posts_columns">
<option value="2" <?php selected($options['related_posts_columns'] ?? 3, 2); ?>><?php _e('2 Columns', 'roi-theme'); ?></option>
<option value="3" <?php selected($options['related_posts_columns'] ?? 3, 3); ?>><?php _e('3 Columns', 'roi-theme'); ?></option>
<option value="4" <?php selected($options['related_posts_columns'] ?? 3, 4); ?>><?php _e('4 Columns', 'roi-theme'); ?></option>
</select>
<p class="description"><?php _e('Number of columns to display related posts', 'apus-theme'); ?></p>
<p class="description"><?php _e('Number of columns to display related posts', 'roi-theme'); ?></p>
</td>
</tr>
</table>
</div>
<!-- Advanced Tab -->
<div id="advanced" class="apus-tab-pane">
<h2><?php _e('Advanced Settings', 'apus-theme'); ?></h2>
<p class="description"><?php _e('Advanced customization options. Use with caution.', 'apus-theme'); ?></p>
<div id="advanced" class="roi-tab-pane">
<h2><?php _e('Advanced Settings', 'roi-theme'); ?></h2>
<p class="description"><?php _e('Advanced customization options. Use with caution.', 'roi-theme'); ?></p>
<table class="form-table">
<!-- Custom CSS -->
<tr>
<th scope="row">
<label for="custom_css"><?php _e('Custom CSS', 'apus-theme'); ?></label>
<label for="custom_css"><?php _e('Custom CSS', 'roi-theme'); ?></label>
</th>
<td>
<textarea name="apus_theme_options[custom_css]" id="custom_css" rows="10" class="large-text code"><?php echo esc_textarea($options['custom_css'] ?? ''); ?></textarea>
<p class="description"><?php _e('Add custom CSS code. This will be added to the &lt;head&gt; section.', 'apus-theme'); ?></p>
<textarea name="roi_theme_options[custom_css]" id="custom_css" rows="10" class="large-text code"><?php echo esc_textarea($options['custom_css'] ?? ''); ?></textarea>
<p class="description"><?php _e('Add custom CSS code. This will be added to the &lt;head&gt; section.', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Custom JS Header -->
<tr>
<th scope="row">
<label for="custom_js_header"><?php _e('Custom JavaScript (Header)', 'apus-theme'); ?></label>
<label for="custom_js_header"><?php _e('Custom JavaScript (Header)', 'roi-theme'); ?></label>
</th>
<td>
<textarea name="apus_theme_options[custom_js_header]" id="custom_js_header" rows="10" class="large-text code"><?php echo esc_textarea($options['custom_js_header'] ?? ''); ?></textarea>
<p class="description"><?php _e('Add custom JavaScript code. This will be added to the &lt;head&gt; section. Do not include &lt;script&gt; tags.', 'apus-theme'); ?></p>
<textarea name="roi_theme_options[custom_js_header]" id="custom_js_header" rows="10" class="large-text code"><?php echo esc_textarea($options['custom_js_header'] ?? ''); ?></textarea>
<p class="description"><?php _e('Add custom JavaScript code. This will be added to the &lt;head&gt; section. Do not include &lt;script&gt; tags.', 'roi-theme'); ?></p>
</td>
</tr>
<!-- Custom JS Footer -->
<tr>
<th scope="row">
<label for="custom_js_footer"><?php _e('Custom JavaScript (Footer)', 'apus-theme'); ?></label>
<label for="custom_js_footer"><?php _e('Custom JavaScript (Footer)', 'roi-theme'); ?></label>
</th>
<td>
<textarea name="apus_theme_options[custom_js_footer]" id="custom_js_footer" rows="10" class="large-text code"><?php echo esc_textarea($options['custom_js_footer'] ?? ''); ?></textarea>
<p class="description"><?php _e('Add custom JavaScript code. This will be added before the closing &lt;/body&gt; tag. Do not include &lt;script&gt; tags.', 'apus-theme'); ?></p>
<textarea name="roi_theme_options[custom_js_footer]" id="custom_js_footer" rows="10" class="large-text code"><?php echo esc_textarea($options['custom_js_footer'] ?? ''); ?></textarea>
<p class="description"><?php _e('Add custom JavaScript code. This will be added before the closing &lt;/body&gt; tag. Do not include &lt;script&gt; tags.', 'roi-theme'); ?></p>
</td>
</tr>
</table>
@@ -642,20 +640,20 @@ $options = get_option('apus_theme_options', apus_get_default_options());
</div>
</div>
<?php submit_button(__('Save All Settings', 'apus-theme'), 'primary large', 'submit', true); ?>
<?php submit_button(__('Save All Settings', 'roi-theme'), 'primary large', 'submit', true); ?>
</form>
</div>
<!-- Import Modal -->
<div id="apus-import-modal" class="apus-modal" style="display:none;">
<div class="apus-modal-content">
<span class="apus-modal-close">&times;</span>
<h2><?php _e('Import Options', 'apus-theme'); ?></h2>
<p><?php _e('Paste your exported options JSON here:', 'apus-theme'); ?></p>
<textarea id="apus-import-data" rows="10" class="large-text code"></textarea>
<div id="roi-import-modal" class="roi-modal" style="display:none;">
<div class="roi-modal-content">
<span class="roi-modal-close">&times;</span>
<h2><?php _e('Import Options', 'roi-theme'); ?></h2>
<p><?php _e('Paste your exported options JSON here:', 'roi-theme'); ?></p>
<textarea id="roi-import-data" rows="10" class="large-text code"></textarea>
<p>
<button type="button" class="button button-primary" id="apus-import-submit"><?php _e('Import', 'apus-theme'); ?></button>
<button type="button" class="button" id="apus-import-cancel"><?php _e('Cancel', 'apus-theme'); ?></button>
<button type="button" class="button button-primary" id="roi-import-submit"><?php _e('Import', 'roi-theme'); ?></button>
<button type="button" class="button" id="roi-import-cancel"><?php _e('Cancel', 'roi-theme'); ?></button>
</p>
</div>
</div>

View File

@@ -5,7 +5,7 @@
* This file provides helper functions and documentation for configuring
* related posts functionality via WordPress options.
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
@@ -19,85 +19,85 @@ if (!defined('ABSPATH')) {
*
* @return array Array of options with their values
*/
function apus_get_related_posts_options() {
function roi_get_related_posts_options() {
return array(
'enabled' => array(
'key' => 'apus_related_posts_enabled',
'value' => get_option('apus_related_posts_enabled', true),
'key' => 'roi_related_posts_enabled',
'value' => get_option('roi_related_posts_enabled', true),
'type' => 'boolean',
'default' => true,
'label' => __('Enable Related Posts', 'apus-theme'),
'description' => __('Show related posts section at the end of single posts', 'apus-theme'),
'label' => __('Enable Related Posts', 'roi-theme'),
'description' => __('Show related posts section at the end of single posts', 'roi-theme'),
),
'title' => array(
'key' => 'apus_related_posts_title',
'value' => get_option('apus_related_posts_title', __('Related Posts', 'apus-theme')),
'key' => 'roi_related_posts_title',
'value' => get_option('roi_related_posts_title', __('Related Posts', 'roi-theme')),
'type' => 'text',
'default' => __('Related Posts', 'apus-theme'),
'label' => __('Section Title', 'apus-theme'),
'description' => __('Title displayed above related posts', 'apus-theme'),
'default' => __('Related Posts', 'roi-theme'),
'label' => __('Section Title', 'roi-theme'),
'description' => __('Title displayed above related posts', 'roi-theme'),
),
'count' => array(
'key' => 'apus_related_posts_count',
'value' => get_option('apus_related_posts_count', 3),
'key' => 'roi_related_posts_count',
'value' => get_option('roi_related_posts_count', 3),
'type' => 'number',
'default' => 3,
'min' => 1,
'max' => 12,
'label' => __('Number of Posts', 'apus-theme'),
'description' => __('Maximum number of related posts to display', 'apus-theme'),
'label' => __('Number of Posts', 'roi-theme'),
'description' => __('Maximum number of related posts to display', 'roi-theme'),
),
'columns' => array(
'key' => 'apus_related_posts_columns',
'value' => get_option('apus_related_posts_columns', 3),
'key' => 'roi_related_posts_columns',
'value' => get_option('roi_related_posts_columns', 3),
'type' => 'select',
'default' => 3,
'options' => array(
1 => __('1 Column', 'apus-theme'),
2 => __('2 Columns', 'apus-theme'),
3 => __('3 Columns', 'apus-theme'),
4 => __('4 Columns', 'apus-theme'),
1 => __('1 Column', 'roi-theme'),
2 => __('2 Columns', 'roi-theme'),
3 => __('3 Columns', 'roi-theme'),
4 => __('4 Columns', 'roi-theme'),
),
'label' => __('Grid Columns', 'apus-theme'),
'description' => __('Number of columns in the grid layout (responsive)', 'apus-theme'),
'label' => __('Grid Columns', 'roi-theme'),
'description' => __('Number of columns in the grid layout (responsive)', 'roi-theme'),
),
'show_excerpt' => array(
'key' => 'apus_related_posts_show_excerpt',
'value' => get_option('apus_related_posts_show_excerpt', true),
'key' => 'roi_related_posts_show_excerpt',
'value' => get_option('roi_related_posts_show_excerpt', true),
'type' => 'boolean',
'default' => true,
'label' => __('Show Excerpt', 'apus-theme'),
'description' => __('Display post excerpt in related posts cards', 'apus-theme'),
'label' => __('Show Excerpt', 'roi-theme'),
'description' => __('Display post excerpt in related posts cards', 'roi-theme'),
),
'excerpt_length' => array(
'key' => 'apus_related_posts_excerpt_length',
'value' => get_option('apus_related_posts_excerpt_length', 20),
'key' => 'roi_related_posts_excerpt_length',
'value' => get_option('roi_related_posts_excerpt_length', 20),
'type' => 'number',
'default' => 20,
'min' => 5,
'max' => 100,
'label' => __('Excerpt Length', 'apus-theme'),
'description' => __('Number of words in the excerpt', 'apus-theme'),
'label' => __('Excerpt Length', 'roi-theme'),
'description' => __('Number of words in the excerpt', 'roi-theme'),
),
'show_date' => array(
'key' => 'apus_related_posts_show_date',
'value' => get_option('apus_related_posts_show_date', true),
'key' => 'roi_related_posts_show_date',
'value' => get_option('roi_related_posts_show_date', true),
'type' => 'boolean',
'default' => true,
'label' => __('Show Date', 'apus-theme'),
'description' => __('Display publication date in related posts', 'apus-theme'),
'label' => __('Show Date', 'roi-theme'),
'description' => __('Display publication date in related posts', 'roi-theme'),
),
'show_category' => array(
'key' => 'apus_related_posts_show_category',
'value' => get_option('apus_related_posts_show_category', true),
'key' => 'roi_related_posts_show_category',
'value' => get_option('roi_related_posts_show_category', true),
'type' => 'boolean',
'default' => true,
'label' => __('Show Category', 'apus-theme'),
'description' => __('Display category badge on related posts', 'apus-theme'),
'label' => __('Show Category', 'roi-theme'),
'description' => __('Display category badge on related posts', 'roi-theme'),
),
'bg_colors' => array(
'key' => 'apus_related_posts_bg_colors',
'value' => get_option('apus_related_posts_bg_colors', array(
'key' => 'roi_related_posts_bg_colors',
'value' => get_option('roi_related_posts_bg_colors', array(
'#1a73e8', '#e91e63', '#4caf50', '#ff9800', '#9c27b0', '#00bcd4',
)),
'type' => 'color_array',
@@ -109,8 +109,8 @@ function apus_get_related_posts_options() {
'#9c27b0', // Purple
'#00bcd4', // Cyan
),
'label' => __('Background Colors', 'apus-theme'),
'description' => __('Colors used for posts without featured images', 'apus-theme'),
'label' => __('Background Colors', 'roi-theme'),
'description' => __('Colors used for posts without featured images', 'roi-theme'),
),
);
}
@@ -118,12 +118,12 @@ function apus_get_related_posts_options() {
/**
* Update a related posts option
*
* @param string $option_key The option key (without 'apus_related_posts_' prefix)
* @param string $option_key The option key (without 'roi_related_posts_' prefix)
* @param mixed $value The new value
* @return bool True if updated successfully
*/
function apus_update_related_posts_option($option_key, $value) {
$full_key = 'apus_related_posts_' . $option_key;
function roi_update_related_posts_option($option_key, $value) {
$full_key = 'roi_related_posts_' . $option_key;
return update_option($full_key, $value);
}
@@ -132,8 +132,8 @@ function apus_update_related_posts_option($option_key, $value) {
*
* @return bool True if reset successfully
*/
function apus_reset_related_posts_options() {
$options = apus_get_related_posts_options();
function roi_reset_related_posts_options() {
$options = roi_get_related_posts_options();
$success = true;
foreach ($options as $option) {
@@ -153,31 +153,31 @@ function apus_reset_related_posts_options() {
*
* @return void
*/
function apus_example_configure_related_posts() {
function roi_example_configure_related_posts() {
// Example usage - uncomment to use:
// Enable related posts
// update_option('apus_related_posts_enabled', true);
// update_option('roi_related_posts_enabled', true);
// Set custom title
// update_option('apus_related_posts_title', __('You Might Also Like', 'apus-theme'));
// update_option('roi_related_posts_title', __('You Might Also Like', 'roi-theme'));
// Show 4 related posts
// update_option('apus_related_posts_count', 4);
// update_option('roi_related_posts_count', 4);
// Use 2 columns layout
// update_option('apus_related_posts_columns', 2);
// update_option('roi_related_posts_columns', 2);
// Show excerpt with 30 words
// update_option('apus_related_posts_show_excerpt', true);
// update_option('apus_related_posts_excerpt_length', 30);
// update_option('roi_related_posts_show_excerpt', true);
// update_option('roi_related_posts_excerpt_length', 30);
// Show date and category
// update_option('apus_related_posts_show_date', true);
// update_option('apus_related_posts_show_category', true);
// update_option('roi_related_posts_show_date', true);
// update_option('roi_related_posts_show_category', true);
// Custom background colors for posts without images
// update_option('apus_related_posts_bg_colors', array(
// update_option('roi_related_posts_bg_colors', array(
// '#FF6B6B', // Red
// '#4ECDC4', // Teal
// '#45B7D1', // Blue
@@ -193,7 +193,7 @@ function apus_example_configure_related_posts() {
* This example shows how to customize the related posts query.
* Add this to your functions.php or child theme.
*/
function apus_example_modify_related_posts_query($args, $post_id) {
function roi_example_modify_related_posts_query($args, $post_id) {
// Example: Order by date instead of random
// $args['orderby'] = 'date';
// $args['order'] = 'DESC';
@@ -210,61 +210,61 @@ function apus_example_modify_related_posts_query($args, $post_id) {
return $args;
}
// add_filter('apus_related_posts_args', 'apus_example_modify_related_posts_query', 10, 2);
// add_filter('roi_related_posts_args', 'roi_example_modify_related_posts_query', 10, 2);
/**
* Get documentation for related posts configuration
*
* @return array Documentation array
*/
function apus_get_related_posts_documentation() {
function roi_get_related_posts_documentation() {
return array(
'overview' => array(
'title' => __('Related Posts Overview', 'apus-theme'),
'title' => __('Related Posts Overview', 'roi-theme'),
'content' => __(
'The related posts feature automatically displays relevant posts at the end of each blog post. ' .
'Posts are related based on shared categories and displayed in a responsive Bootstrap grid.',
'apus-theme'
'roi-theme'
),
),
'features' => array(
'title' => __('Key Features', 'apus-theme'),
'title' => __('Key Features', 'roi-theme'),
'items' => array(
__('Automatic category-based matching', 'apus-theme'),
__('Responsive Bootstrap 5 grid layout', 'apus-theme'),
__('Configurable number of posts and columns', 'apus-theme'),
__('Support for posts with and without featured images', 'apus-theme'),
__('Beautiful color backgrounds for posts without images', 'apus-theme'),
__('Customizable excerpt length', 'apus-theme'),
__('Optional display of dates and categories', 'apus-theme'),
__('Smooth hover animations', 'apus-theme'),
__('Print-friendly styles', 'apus-theme'),
__('Dark mode support', 'apus-theme'),
__('Automatic category-based matching', 'roi-theme'),
__('Responsive Bootstrap 5 grid layout', 'roi-theme'),
__('Configurable number of posts and columns', 'roi-theme'),
__('Support for posts with and without featured images', 'roi-theme'),
__('Beautiful color backgrounds for posts without images', 'roi-theme'),
__('Customizable excerpt length', 'roi-theme'),
__('Optional display of dates and categories', 'roi-theme'),
__('Smooth hover animations', 'roi-theme'),
__('Print-friendly styles', 'roi-theme'),
__('Dark mode support', 'roi-theme'),
),
),
'configuration' => array(
'title' => __('How to Configure', 'apus-theme'),
'title' => __('How to Configure', 'roi-theme'),
'methods' => array(
'database' => array(
'title' => __('Via WordPress Options API', 'apus-theme'),
'code' => "update_option('apus_related_posts_enabled', true);\nupdate_option('apus_related_posts_count', 4);",
'title' => __('Via WordPress Options API', 'roi-theme'),
'code' => "update_option('roi_related_posts_enabled', true);\nupdate_option('roi_related_posts_count', 4);",
),
'filter' => array(
'title' => __('Via Filter Hook', 'apus-theme'),
'code' => "add_filter('apus_related_posts_args', function(\$args, \$post_id) {\n \$args['posts_per_page'] = 6;\n return \$args;\n}, 10, 2);",
'title' => __('Via Filter Hook', 'roi-theme'),
'code' => "add_filter('roi_related_posts_args', function(\$args, \$post_id) {\n \$args['posts_per_page'] = 6;\n return \$args;\n}, 10, 2);",
),
),
),
'customization' => array(
'title' => __('Customization Examples', 'apus-theme'),
'title' => __('Customization Examples', 'roi-theme'),
'examples' => array(
array(
'title' => __('Change title and layout', 'apus-theme'),
'code' => "update_option('apus_related_posts_title', 'También te puede interesar');\nupdate_option('apus_related_posts_columns', 4);",
'title' => __('Change title and layout', 'roi-theme'),
'code' => "update_option('roi_related_posts_title', 'También te puede interesar');\nupdate_option('roi_related_posts_columns', 4);",
),
array(
'title' => __('Customize colors', 'apus-theme'),
'code' => "update_option('apus_related_posts_bg_colors', array(\n '#FF6B6B',\n '#4ECDC4',\n '#45B7D1'\n));",
'title' => __('Customize colors', 'roi-theme'),
'code' => "update_option('roi_related_posts_bg_colors', array(\n '#FF6B6B',\n '#4ECDC4',\n '#45B7D1'\n));",
),
),
),

View File

@@ -2,7 +2,7 @@
/**
* Theme Options Admin Page
*
* @package Apus_Theme
* @package ROI_Theme
* @since 1.0.0
*/
@@ -16,26 +16,26 @@ if (!defined('ABSPATH')) {
* DESACTIVADO: Ahora se usa el nuevo Admin Panel en admin/includes/class-admin-menu.php
*/
/*
function apus_add_admin_menu() {
function roi_add_admin_menu() {
add_theme_page(
__('Apus Theme Options', 'apus-theme'), // Page title
__('Theme Options', 'apus-theme'), // Menu title
__('ROI Theme Options', 'roi-theme'), // Page title
__('Theme Options', 'roi-theme'), // Menu title
'manage_options', // Capability
'apus-theme-options', // Menu slug
'apus_render_options_page', // Callback function
'roi-theme-options', // Menu slug
'roi_render_options_page', // Callback function
30 // Position
);
}
add_action('admin_menu', 'apus_add_admin_menu');
add_action('admin_menu', 'roi_add_admin_menu');
*/
/**
* Render the options page
*/
function apus_render_options_page() {
function roi_render_options_page() {
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'apus-theme'));
wp_die(__('You do not have sufficient permissions to access this page.', 'roi-theme'));
}
// Load the template
@@ -45,9 +45,9 @@ function apus_render_options_page() {
/**
* Enqueue admin scripts and styles
*/
function apus_enqueue_admin_scripts($hook) {
function roi_enqueue_admin_scripts($hook) {
// Only load on our theme options page
if ($hook !== 'appearance_page_apus-theme-options') {
if ($hook !== 'appearance_page_roi-theme-options') {
return;
}
@@ -56,119 +56,119 @@ function apus_enqueue_admin_scripts($hook) {
// Enqueue admin styles
wp_enqueue_style(
'apus-admin-options',
'roiadmin-options',
get_template_directory_uri() . '/admin/assets/css/theme-options.css',
array(),
APUS_VERSION
ROI_VERSION
);
// Enqueue admin scripts
wp_enqueue_script(
'apus-admin-options',
'roiadmin-options',
get_template_directory_uri() . '/admin/assets/js/theme-options.js',
array('jquery', 'wp-color-picker'),
APUS_VERSION,
ROI_VERSION,
true
);
// Localize script
wp_localize_script('apus-admin-options', 'apusAdminOptions', array(
wp_localize_script('roiadmin-options', 'rroiminOptions', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('apus_admin_nonce'),
'nonce' => wp_create_nonce('roi_admin_nonce'),
'strings' => array(
'selectImage' => __('Select Image', 'apus-theme'),
'useImage' => __('Use Image', 'apus-theme'),
'removeImage' => __('Remove Image', 'apus-theme'),
'confirmReset' => __('Are you sure you want to reset all options to default values? This cannot be undone.', 'apus-theme'),
'saved' => __('Settings saved successfully!', 'apus-theme'),
'error' => __('An error occurred while saving settings.', 'apus-theme'),
'selectImage' => __('Select Image', 'roi-theme'),
'useImage' => __('Use Image', 'roi-theme'),
'removeImage' => __('Remove Image', 'roi-theme'),
'confirmReset' => __('Are you sure you want to reset all options to default values? This cannot be undone.', 'roi-theme'),
'saved' => __('Settings saved successfully!', 'roi-theme'),
'error' => __('An error occurred while saving settings.', 'roi-theme'),
),
));
}
add_action('admin_enqueue_scripts', 'apus_enqueue_admin_scripts');
add_action('admin_enqueue_scripts', 'roi_enqueue_admin_scripts');
/**
* Add settings link to theme actions
*/
function apus_add_settings_link($links) {
$settings_link = '<a href="' . admin_url('themes.php?page=apus-theme-options') . '">' . __('Settings', 'apus-theme') . '</a>';
function roi_add_settings_link($links) {
$settings_link = '<a href="' . admin_url('themes.php?page=roi-theme-options') . '">' . __('Settings', 'roi-theme') . '</a>';
array_unshift($links, $settings_link);
return $links;
}
add_filter('theme_action_links_' . get_template(), 'apus_add_settings_link');
add_filter('theme_action_links_' . get_template(), 'roi_add_settings_link');
/**
* AJAX handler for resetting options
*/
function apus_reset_options_ajax() {
check_ajax_referer('apus_admin_nonce', 'nonce');
function roi_reset_options_ajax() {
check_ajax_referer('roi_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('Insufficient permissions.', 'apus-theme')));
wp_send_json_error(array('message' => __('Insufficient permissions.', 'roi-theme')));
}
// Delete options to reset to defaults
delete_option('apus_theme_options');
delete_option('roi_theme_options');
wp_send_json_success(array('message' => __('Options reset to defaults successfully.', 'apus-theme')));
wp_send_json_success(array('message' => __('Options reset to defaults successfully.', 'roi-theme')));
}
add_action('wp_ajax_apus_reset_options', 'apus_reset_options_ajax');
add_action('wp_ajax_roi_reset_options', 'roi_reset_options_ajax');
/**
* AJAX handler for exporting options
*/
function apus_export_options_ajax() {
check_ajax_referer('apus_admin_nonce', 'nonce');
function roi_export_options_ajax() {
check_ajax_referer('roi_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('Insufficient permissions.', 'apus-theme')));
wp_send_json_error(array('message' => __('Insufficient permissions.', 'roi-theme')));
}
$options = get_option('apus_theme_options', array());
$options = get_option('roi_theme_options', array());
wp_send_json_success(array(
'data' => json_encode($options, JSON_PRETTY_PRINT),
'filename' => 'apus-theme-options-' . date('Y-m-d') . '.json'
'filename' => 'roi-theme-options-' . date('Y-m-d') . '.json'
));
}
add_action('wp_ajax_apus_export_options', 'apus_export_options_ajax');
add_action('wp_ajax_roi_export_options', 'roi_export_options_ajax');
/**
* AJAX handler for importing options
*/
function apus_import_options_ajax() {
check_ajax_referer('apus_admin_nonce', 'nonce');
function roi_import_options_ajax() {
check_ajax_referer('roi_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('Insufficient permissions.', 'apus-theme')));
wp_send_json_error(array('message' => __('Insufficient permissions.', 'roi-theme')));
}
if (!isset($_POST['import_data'])) {
wp_send_json_error(array('message' => __('No import data provided.', 'apus-theme')));
wp_send_json_error(array('message' => __('No import data provided.', 'roi-theme')));
}
$import_data = json_decode(stripslashes($_POST['import_data']), true);
if (json_last_error() !== JSON_ERROR_NONE) {
wp_send_json_error(array('message' => __('Invalid JSON data.', 'apus-theme')));
wp_send_json_error(array('message' => __('Invalid JSON data.', 'roi-theme')));
}
// Sanitize imported data
$sanitized_data = apus_sanitize_options($import_data);
$sanitized_data = roi_sanitize_options($import_data);
// Update options
update_option('apus_theme_options', $sanitized_data);
update_option('roi_theme_options', $sanitized_data);
wp_send_json_success(array('message' => __('Options imported successfully.', 'apus-theme')));
wp_send_json_success(array('message' => __('Options imported successfully.', 'roi-theme')));
}
add_action('wp_ajax_apus_import_options', 'apus_import_options_ajax');
add_action('wp_ajax_roi_import_options', 'roi_import_options_ajax');
/**
* Add admin notices
*/
function apus_admin_notices() {
function roi_admin_notices() {
$screen = get_current_screen();
if ($screen->id !== 'appearance_page_apus-theme-options') {
if ($screen->id !== 'appearance_page_roi-theme-options') {
return;
}
@@ -176,42 +176,42 @@ function apus_admin_notices() {
if (isset($_GET['settings-updated']) && $_GET['settings-updated'] === 'true') {
?>
<div class="notice notice-success is-dismissible">
<p><?php _e('Settings saved successfully!', 'apus-theme'); ?></p>
<p><?php _e('Settings saved successfully!', 'roi-theme'); ?></p>
</div>
<?php
}
}
add_action('admin_notices', 'apus_admin_notices');
add_action('admin_notices', 'roi_admin_notices');
/**
* Register theme options in Customizer as well (for preview)
*/
function apus_customize_register($wp_customize) {
function roi_customize_register($wp_customize) {
// Add a panel for theme options
$wp_customize->add_panel('apus_theme_options', array(
'title' => __('Apus Theme Options', 'apus-theme'),
'description' => __('Configure theme options (Also available in Theme Options page)', 'apus-theme'),
$wp_customize->add_panel('roi_theme_options', array(
'title' => __('ROI Theme Options', 'roi-theme'),
'description' => __('Configure theme options (Also available in Theme Options page)', 'roi-theme'),
'priority' => 10,
));
// General Section
$wp_customize->add_section('apus_general', array(
'title' => __('General Settings', 'apus-theme'),
'panel' => 'apus_theme_options',
$wp_customize->add_section('roi_general', array(
'title' => __('General Settings', 'roi-theme'),
'panel' => 'roi_theme_options',
'priority' => 10,
));
// Enable breadcrumbs
$wp_customize->add_setting('apus_theme_options[enable_breadcrumbs]', array(
$wp_customize->add_setting('roi_theme_options[enable_breadcrumbs]', array(
'default' => true,
'type' => 'option',
'sanitize_callback' => 'apus_sanitize_checkbox',
'sanitize_callback' => 'roi_sanitize_checkbox',
));
$wp_customize->add_control('apus_theme_options[enable_breadcrumbs]', array(
'label' => __('Enable Breadcrumbs', 'apus-theme'),
'section' => 'apus_general',
$wp_customize->add_control('roi_theme_options[enable_breadcrumbs]', array(
'label' => __('Enable Breadcrumbs', 'roi-theme'),
'section' => 'roi_general',
'type' => 'checkbox',
));
}
add_action('customize_register', 'apus_customize_register');
add_action('customize_register', 'roi_customize_register');