backup: estado antes de limpieza de defaults
This commit is contained in:
136
admin/includes/class-admin-menu.php
Normal file
136
admin/includes/class-admin-menu.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Menu Class
|
||||
*
|
||||
* Registra menú en WordPress admin y carga assets
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_Admin_Menu {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('admin_menu', array($this, 'add_menu_page'));
|
||||
add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registrar página de admin
|
||||
*/
|
||||
public function add_menu_page() {
|
||||
add_theme_page(
|
||||
'APUs Theme Settings', // Page title
|
||||
'Tema APUs', // Menu title
|
||||
'manage_options', // Capability
|
||||
'apus-theme-settings', // Menu slug
|
||||
array($this, 'render_admin_page'), // Callback
|
||||
59 // Position
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderizar página de admin
|
||||
*/
|
||||
public function render_admin_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die(__('No tienes permisos para acceder a esta página.'));
|
||||
}
|
||||
|
||||
require_once APUS_ADMIN_PANEL_PATH . 'pages/main.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encolar assets (CSS/JS)
|
||||
*/
|
||||
public function enqueue_assets($hook) {
|
||||
// Solo cargar en nuestra página
|
||||
if ($hook !== 'appearance_page_apus-theme-settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bootstrap 5.3.2 CSS
|
||||
wp_enqueue_style(
|
||||
'bootstrap',
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css',
|
||||
array(),
|
||||
'5.3.2'
|
||||
);
|
||||
|
||||
// Bootstrap Icons
|
||||
wp_enqueue_style(
|
||||
'bootstrap-icons',
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css',
|
||||
array(),
|
||||
'1.11.1'
|
||||
);
|
||||
|
||||
// Admin Panel CSS (Core)
|
||||
wp_enqueue_style(
|
||||
'apus-admin-panel-css',
|
||||
APUS_ADMIN_PANEL_URL . 'assets/css/admin-panel.css',
|
||||
array('bootstrap'),
|
||||
APUS_ADMIN_PANEL_VERSION
|
||||
);
|
||||
|
||||
// Component: Navbar CSS (estilos admin específicos)
|
||||
wp_enqueue_style(
|
||||
'apus-component-navbar-css',
|
||||
APUS_ADMIN_PANEL_URL . 'assets/css/component-navbar.css',
|
||||
array('apus-admin-panel-css'),
|
||||
APUS_ADMIN_PANEL_VERSION
|
||||
);
|
||||
|
||||
// Bootstrap 5.3.2 JS
|
||||
wp_enqueue_script(
|
||||
'bootstrap',
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js',
|
||||
array(),
|
||||
'5.3.2',
|
||||
true
|
||||
);
|
||||
|
||||
// Axios (para AJAX)
|
||||
wp_enqueue_script(
|
||||
'axios',
|
||||
'https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js',
|
||||
array(),
|
||||
'1.6.0',
|
||||
true
|
||||
);
|
||||
|
||||
// Component: Navbar JS (cargar antes de admin-app.js)
|
||||
wp_enqueue_script(
|
||||
'apus-component-navbar-js',
|
||||
APUS_ADMIN_PANEL_URL . 'assets/js/component-navbar.js',
|
||||
array('jquery'),
|
||||
APUS_ADMIN_PANEL_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Admin Panel JS (Core - depende de componentes)
|
||||
wp_enqueue_script(
|
||||
'apus-admin-panel-js',
|
||||
APUS_ADMIN_PANEL_URL . 'assets/js/admin-app.js',
|
||||
array('jquery', 'axios', 'apus-component-navbar-js'),
|
||||
APUS_ADMIN_PANEL_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Pasar datos a JavaScript
|
||||
wp_localize_script('apus-admin-panel-js', 'apusAdminData', array(
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('apus_admin_nonce')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Instanciar clase
|
||||
new APUS_Admin_Menu();
|
||||
310
admin/includes/class-data-migrator.php
Normal file
310
admin/includes/class-data-migrator.php
Normal file
@@ -0,0 +1,310 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
251
admin/includes/class-db-manager.php
Normal file
251
admin/includes/class-db-manager.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* Database Manager Class
|
||||
*
|
||||
* Gestión de tablas personalizadas del tema
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.2.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_DB_Manager {
|
||||
|
||||
/**
|
||||
* Nombre de la tabla de componentes (sin prefijo)
|
||||
*/
|
||||
const TABLE_COMPONENTS = 'apus_theme_components';
|
||||
|
||||
/**
|
||||
* Versión de la base de datos
|
||||
*/
|
||||
const DB_VERSION = '1.0';
|
||||
|
||||
/**
|
||||
* Opción para almacenar la versión de la DB
|
||||
*/
|
||||
const DB_VERSION_OPTION = 'apus_db_version';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
// Hook para verificar/actualizar DB en cada carga
|
||||
add_action('admin_init', array($this, 'maybe_create_tables'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener nombre completo de tabla con prefijo
|
||||
*/
|
||||
public function get_table_name() {
|
||||
global $wpdb;
|
||||
return $wpdb->prefix . self::TABLE_COMPONENTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar si las tablas necesitan ser creadas o actualizadas
|
||||
*/
|
||||
public function maybe_create_tables() {
|
||||
$installed_version = get_option(self::DB_VERSION_OPTION);
|
||||
|
||||
if ($installed_version !== self::DB_VERSION) {
|
||||
$this->create_tables();
|
||||
update_option(self::DB_VERSION_OPTION, self::DB_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear tablas personalizadas
|
||||
*/
|
||||
public function create_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
$table_name = $this->get_table_name();
|
||||
|
||||
$sql = "CREATE TABLE $table_name (
|
||||
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)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
dbDelta($sql);
|
||||
|
||||
// Verificar si la tabla se creó correctamente
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name) {
|
||||
error_log("APUS DB Manager: Tabla $table_name creada/actualizada exitosamente");
|
||||
return true;
|
||||
} else {
|
||||
error_log("APUS DB Manager: Error al crear tabla $table_name");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar si una tabla existe
|
||||
*/
|
||||
public function table_exists() {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name();
|
||||
return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar configuración de un componente
|
||||
*
|
||||
* @param string $component_name Nombre del componente
|
||||
* @param string $config_key Clave de configuración
|
||||
* @param mixed $config_value Valor de configuración
|
||||
* @param string $data_type Tipo de dato (string, boolean, integer, json)
|
||||
* @param string $version Versión del tema
|
||||
* @return bool|int ID del registro o false en caso de error
|
||||
*/
|
||||
public function save_config($component_name, $config_key, $config_value, $data_type = 'string', $version = null) {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name();
|
||||
|
||||
// Convertir valor según tipo
|
||||
if ($data_type === 'json' && is_array($config_value)) {
|
||||
$config_value = json_encode($config_value, JSON_UNESCAPED_UNICODE);
|
||||
} elseif ($data_type === 'boolean') {
|
||||
$config_value = $config_value ? '1' : '0';
|
||||
}
|
||||
|
||||
// Usar ON DUPLICATE KEY UPDATE para INSERT o UPDATE
|
||||
$result = $wpdb->query($wpdb->prepare(
|
||||
"INSERT INTO $table_name (component_name, config_key, config_value, data_type, version, updated_at)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
config_value = VALUES(config_value),
|
||||
data_type = VALUES(data_type),
|
||||
version = VALUES(version),
|
||||
updated_at = VALUES(updated_at)",
|
||||
$component_name,
|
||||
$config_key,
|
||||
$config_value,
|
||||
$data_type,
|
||||
$version,
|
||||
current_time('mysql')
|
||||
));
|
||||
|
||||
return $result !== false ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener configuración de un componente
|
||||
*
|
||||
* @param string $component_name Nombre del componente
|
||||
* @param string $config_key Clave específica (opcional)
|
||||
* @return array|mixed Configuración completa o valor específico
|
||||
*/
|
||||
public function get_config($component_name, $config_key = null) {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name();
|
||||
|
||||
if ($config_key !== null) {
|
||||
// Obtener un valor específico
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT config_value, data_type FROM $table_name
|
||||
WHERE component_name = %s AND config_key = %s",
|
||||
$component_name,
|
||||
$config_key
|
||||
));
|
||||
|
||||
if ($row) {
|
||||
return $this->parse_value($row->config_value, $row->data_type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Obtener toda la configuración del componente
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT config_key, config_value, data_type FROM $table_name
|
||||
WHERE component_name = %s",
|
||||
$component_name
|
||||
));
|
||||
|
||||
$config = array();
|
||||
foreach ($rows as $row) {
|
||||
$config[$row->config_key] = $this->parse_value($row->config_value, $row->data_type);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsear valor según tipo de dato
|
||||
*
|
||||
* @param string $value Valor almacenado
|
||||
* @param string $data_type Tipo de dato
|
||||
* @return mixed Valor parseado
|
||||
*/
|
||||
private function parse_value($value, $data_type) {
|
||||
switch ($data_type) {
|
||||
case 'boolean':
|
||||
return (bool) $value;
|
||||
case 'integer':
|
||||
return (int) $value;
|
||||
case 'json':
|
||||
return json_decode($value, true);
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar configuraciones de un componente
|
||||
*
|
||||
* @param string $component_name Nombre del componente
|
||||
* @param string $config_key Clave específica (opcional)
|
||||
* @return bool Éxito de la operación
|
||||
*/
|
||||
public function delete_config($component_name, $config_key = null) {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name();
|
||||
|
||||
if ($config_key !== null) {
|
||||
return $wpdb->delete(
|
||||
$table_name,
|
||||
array(
|
||||
'component_name' => $component_name,
|
||||
'config_key' => $config_key
|
||||
),
|
||||
array('%s', '%s')
|
||||
) !== false;
|
||||
}
|
||||
|
||||
// Eliminar todas las configuraciones del componente
|
||||
return $wpdb->delete(
|
||||
$table_name,
|
||||
array('component_name' => $component_name),
|
||||
array('%s')
|
||||
) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listar todos los componentes con configuraciones
|
||||
*
|
||||
* @return array Lista de nombres de componentes
|
||||
*/
|
||||
public function list_components() {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name();
|
||||
|
||||
return $wpdb->get_col(
|
||||
"SELECT DISTINCT component_name FROM $table_name ORDER BY component_name"
|
||||
);
|
||||
}
|
||||
}
|
||||
195
admin/includes/class-settings-manager.php
Normal file
195
admin/includes/class-settings-manager.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Settings Manager Class
|
||||
*
|
||||
* CRUD de configuraciones por componentes
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_Settings_Manager {
|
||||
|
||||
const OPTION_NAME = 'apus_theme_settings';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('wp_ajax_apus_get_settings', array($this, 'ajax_get_settings'));
|
||||
add_action('wp_ajax_apus_save_settings', array($this, 'ajax_save_settings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener configuraciones
|
||||
*/
|
||||
public function get_settings() {
|
||||
$settings = get_option(self::OPTION_NAME, array());
|
||||
$defaults = $this->get_defaults();
|
||||
|
||||
return wp_parse_args($settings, $defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar configuraciones
|
||||
*/
|
||||
public function save_settings($data) {
|
||||
// Validar
|
||||
$validator = new APUS_Validator();
|
||||
$validation = $validator->validate($data);
|
||||
|
||||
if (!$validation['valid']) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => 'Error de validación',
|
||||
'errors' => $validation['errors']
|
||||
);
|
||||
}
|
||||
|
||||
// Sanitizar
|
||||
$sanitized = $this->sanitize_settings($data);
|
||||
|
||||
// Agregar metadata
|
||||
$sanitized['version'] = APUS_ADMIN_PANEL_VERSION;
|
||||
$sanitized['updated_at'] = current_time('mysql');
|
||||
|
||||
// Guardar
|
||||
update_option(self::OPTION_NAME, $sanitized, false);
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'message' => 'Configuración guardada correctamente'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valores por defecto
|
||||
* NOTA: Aquí se agregan los defaults de cada componente
|
||||
*/
|
||||
public function get_defaults() {
|
||||
return array(
|
||||
'version' => APUS_ADMIN_PANEL_VERSION,
|
||||
'components' => array(
|
||||
'top_bar' => array(
|
||||
'enabled' => true,
|
||||
'show_on_mobile' => true,
|
||||
'show_on_desktop' => true,
|
||||
'icon_class' => 'bi bi-megaphone-fill',
|
||||
'show_icon' => true,
|
||||
'highlight_text' => 'Nuevo:',
|
||||
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
|
||||
'link_text' => 'Ver Catálogo',
|
||||
'link_url' => '/catalogo',
|
||||
'link_target' => '_self',
|
||||
'show_link' => true,
|
||||
'custom_styles' => array(
|
||||
// Valores extraídos de componente-top-bar.css
|
||||
'background_color' => '#0E2337', // var(--color-navy-dark)
|
||||
'text_color' => '#ffffff',
|
||||
'highlight_color' => '#FF8600', // var(--color-orange-primary)
|
||||
'link_hover_color' => '#FF8600', // var(--color-orange-primary)
|
||||
'font_size' => 'normal' // 0.9rem del CSS
|
||||
)
|
||||
)
|
||||
// Navbar - Pendiente
|
||||
// Hero - Pendiente
|
||||
// Footer - Pendiente
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizar configuraciones
|
||||
* NOTA: Aquí se agrega sanitización de cada componente
|
||||
*/
|
||||
public function sanitize_settings($data) {
|
||||
$sanitized = array(
|
||||
'components' => array()
|
||||
);
|
||||
|
||||
// Sanitizar Top Bar
|
||||
if (isset($data['components']['top_bar'])) {
|
||||
$top_bar = $data['components']['top_bar'];
|
||||
|
||||
$sanitized['components']['top_bar'] = array(
|
||||
'enabled' => !empty($top_bar['enabled']),
|
||||
'show_on_mobile' => !empty($top_bar['show_on_mobile']),
|
||||
'show_on_desktop' => !empty($top_bar['show_on_desktop']),
|
||||
'icon_class' => sanitize_text_field($top_bar['icon_class'] ?? ''),
|
||||
'show_icon' => !empty($top_bar['show_icon']),
|
||||
'highlight_text' => sanitize_text_field($top_bar['highlight_text'] ?? ''),
|
||||
'message_text' => sanitize_text_field($top_bar['message_text'] ?? ''),
|
||||
'link_text' => sanitize_text_field($top_bar['link_text'] ?? ''),
|
||||
'link_url' => esc_url_raw($top_bar['link_url'] ?? ''),
|
||||
'link_target' => in_array($top_bar['link_target'] ?? '', array('_self', '_blank')) ? $top_bar['link_target'] : '_self',
|
||||
'show_link' => !empty($top_bar['show_link']),
|
||||
'custom_styles' => array(
|
||||
'background_color' => sanitize_hex_color($top_bar['custom_styles']['background_color'] ?? ''),
|
||||
'text_color' => sanitize_hex_color($top_bar['custom_styles']['text_color'] ?? ''),
|
||||
'highlight_color' => sanitize_hex_color($top_bar['custom_styles']['highlight_color'] ?? ''),
|
||||
'link_hover_color' => sanitize_hex_color($top_bar['custom_styles']['link_hover_color'] ?? ''),
|
||||
'font_size' => in_array($top_bar['custom_styles']['font_size'] ?? '', array('small', 'normal', 'large')) ? $top_bar['custom_styles']['font_size'] : 'normal'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Obtener configuraciones
|
||||
*/
|
||||
public function ajax_get_settings() {
|
||||
// Verificar nonce usando check_ajax_referer (método recomendado para AJAX)
|
||||
check_ajax_referer('apus_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Permisos insuficientes');
|
||||
}
|
||||
|
||||
$settings = $this->get_settings();
|
||||
wp_send_json_success($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Guardar configuraciones
|
||||
*/
|
||||
public function ajax_save_settings() {
|
||||
// Verificar nonce usando check_ajax_referer (método recomendado para AJAX)
|
||||
check_ajax_referer('apus_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Permisos insuficientes');
|
||||
}
|
||||
|
||||
// Los datos vienen como JSON string en $_POST['components']
|
||||
if (!isset($_POST['components'])) {
|
||||
wp_send_json_error('Datos inválidos - falta components');
|
||||
}
|
||||
|
||||
$components = json_decode(stripslashes($_POST['components']), true);
|
||||
|
||||
if (!is_array($components)) {
|
||||
wp_send_json_error('Datos inválidos - components no es un array válido');
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'components' => $components
|
||||
);
|
||||
|
||||
$result = $this->save_settings($data);
|
||||
|
||||
if ($result['success']) {
|
||||
wp_send_json_success($result);
|
||||
} else {
|
||||
wp_send_json_error($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instanciar clase
|
||||
new APUS_Settings_Manager();
|
||||
382
admin/includes/class-theme-options-migrator.php
Normal file
382
admin/includes/class-theme-options-migrator.php
Normal file
@@ -0,0 +1,382 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
106
admin/includes/class-validator.php
Normal file
106
admin/includes/class-validator.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Validator Class
|
||||
*
|
||||
* Validación de datos por componentes
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class APUS_Validator {
|
||||
|
||||
/**
|
||||
* Validar todas las configuraciones
|
||||
*/
|
||||
public function validate($data) {
|
||||
$errors = array();
|
||||
|
||||
// Validar estructura base
|
||||
if (!isset($data['components']) || !is_array($data['components'])) {
|
||||
$errors[] = 'Estructura de datos inválida';
|
||||
return array('valid' => false, 'errors' => $errors);
|
||||
}
|
||||
|
||||
// Validar Top Bar
|
||||
if (isset($data['components']['top_bar'])) {
|
||||
$top_bar_errors = $this->validate_top_bar($data['components']['top_bar']);
|
||||
$errors = array_merge($errors, $top_bar_errors);
|
||||
}
|
||||
|
||||
return array(
|
||||
'valid' => empty($errors),
|
||||
'errors' => $errors
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar Top Bar
|
||||
*/
|
||||
public function validate_top_bar($top_bar) {
|
||||
$errors = array();
|
||||
|
||||
// Validar icon_class
|
||||
if (!empty($top_bar['icon_class']) && strlen($top_bar['icon_class']) > 50) {
|
||||
$errors[] = 'La clase del icono no puede exceder 50 caracteres';
|
||||
}
|
||||
|
||||
// Validar highlight_text
|
||||
if (!empty($top_bar['highlight_text']) && strlen($top_bar['highlight_text']) > 30) {
|
||||
$errors[] = 'El texto destacado no puede exceder 30 caracteres';
|
||||
}
|
||||
|
||||
// Validar message_text
|
||||
if (empty($top_bar['message_text'])) {
|
||||
$errors[] = 'El mensaje principal es obligatorio';
|
||||
} elseif (strlen($top_bar['message_text']) > 250) {
|
||||
$errors[] = 'El mensaje principal no puede exceder 250 caracteres';
|
||||
}
|
||||
|
||||
// Validar link_text
|
||||
if (!empty($top_bar['link_text']) && strlen($top_bar['link_text']) > 50) {
|
||||
$errors[] = 'El texto del enlace no puede exceder 50 caracteres';
|
||||
}
|
||||
|
||||
// Validar link_url (acepta URLs completas y relativas que empiecen con /)
|
||||
if (!empty($top_bar['link_url'])) {
|
||||
$url = $top_bar['link_url'];
|
||||
$is_valid_url = filter_var($url, FILTER_VALIDATE_URL) !== false;
|
||||
$is_relative_url = preg_match('/^\//', $url);
|
||||
|
||||
if (!$is_valid_url && !$is_relative_url) {
|
||||
$errors[] = 'La URL del enlace no es válida';
|
||||
}
|
||||
}
|
||||
|
||||
// Validar link_target
|
||||
if (!in_array($top_bar['link_target'] ?? '', array('_self', '_blank'))) {
|
||||
$errors[] = 'El target del enlace debe ser _self o _blank';
|
||||
}
|
||||
|
||||
// Validar colores
|
||||
if (!empty($top_bar['custom_styles']['background_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['background_color'])) {
|
||||
$errors[] = 'El color de fondo debe ser un color hexadecimal válido';
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['text_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['text_color'])) {
|
||||
$errors[] = 'El color de texto debe ser un color hexadecimal válido';
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['highlight_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['highlight_color'])) {
|
||||
$errors[] = 'El color del highlight debe ser un color hexadecimal válido';
|
||||
}
|
||||
if (!empty($top_bar['custom_styles']['link_hover_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['link_hover_color'])) {
|
||||
$errors[] = 'El color hover del enlace debe ser un color hexadecimal válido';
|
||||
}
|
||||
|
||||
// Validar font_size
|
||||
if (!in_array($top_bar['custom_styles']['font_size'] ?? '', array('small', 'normal', 'large'))) {
|
||||
$errors[] = 'El tamaño de fuente debe ser small, normal o large';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
173
admin/includes/sanitizers/class-herosection-sanitizer.php
Normal file
173
admin/includes/sanitizers/class-herosection-sanitizer.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* Hero Section Sanitizer
|
||||
*
|
||||
* Sanitiza configuraciones del componente Hero Section
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @subpackage Admin_Panel\Sanitizers
|
||||
* @since 2.1.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class APUS_HeroSection_Sanitizer
|
||||
*
|
||||
* Sanitiza todas las configuraciones del componente Hero Section
|
||||
*/
|
||||
class APUS_HeroSection_Sanitizer {
|
||||
|
||||
/**
|
||||
* Obtiene los valores por defecto del Hero Section
|
||||
*
|
||||
* @return array Valores por defecto
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public function get_defaults() {
|
||||
return array(
|
||||
// Activación y Visibilidad
|
||||
'enabled' => true,
|
||||
'show_on_mobile' => true,
|
||||
'show_on_desktop' => true,
|
||||
|
||||
// Contenido y Estructura
|
||||
'show_category_badges' => true,
|
||||
'category_badge_icon' => 'bi bi-folder-fill',
|
||||
'excluded_categories' => array('Uncategorized', 'Sin categoría'),
|
||||
'title_alignment' => 'center',
|
||||
'title_display_class' => 'display-5',
|
||||
|
||||
// Colores del Hero
|
||||
'use_gradient_background' => true,
|
||||
'gradient_start_color' => '#1e3a5f',
|
||||
'gradient_end_color' => '#2c5282',
|
||||
'gradient_angle' => 135,
|
||||
'hero_text_color' => '#ffffff',
|
||||
'solid_background_color' => '#1e3a5f',
|
||||
|
||||
// Colores de Category Badges
|
||||
'badge_bg_color' => 'rgba(255, 255, 255, 0.15)',
|
||||
'badge_bg_hover_color' => 'rgba(255, 133, 0, 0.2)',
|
||||
'badge_border_color' => 'rgba(255, 255, 255, 0.2)',
|
||||
'badge_text_color' => 'rgba(255, 255, 255, 0.95)',
|
||||
'badge_icon_color' => '#FFB800',
|
||||
|
||||
// Espaciado y Dimensiones
|
||||
'hero_padding_vertical' => 3.0,
|
||||
'hero_padding_horizontal' => 0.0,
|
||||
'hero_margin_bottom' => 1.5,
|
||||
'badges_gap' => 0.5,
|
||||
'badge_padding_vertical' => 0.375,
|
||||
'badge_padding_horizontal' => 0.875,
|
||||
'badge_border_radius' => 20,
|
||||
|
||||
// Tipografía
|
||||
'h1_font_weight' => 700,
|
||||
'badge_font_size' => 0.813,
|
||||
'badge_font_weight' => 500,
|
||||
'h1_line_height' => 1.4,
|
||||
|
||||
// Efectos Visuales
|
||||
'enable_h1_text_shadow' => true,
|
||||
'h1_text_shadow' => '1px 1px 2px rgba(0, 0, 0, 0.2)',
|
||||
'enable_hero_box_shadow' => true,
|
||||
'hero_box_shadow' => '0 4px 16px rgba(30, 58, 95, 0.25)',
|
||||
'enable_badge_backdrop_filter' => true,
|
||||
'badge_backdrop_filter' => 'blur(10px)',
|
||||
|
||||
// Transiciones y Animaciones
|
||||
'badge_transition_speed' => 'normal',
|
||||
'badge_hover_effect' => 'background',
|
||||
|
||||
// Avanzado
|
||||
'custom_hero_classes' => '',
|
||||
'custom_badge_classes' => ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza los datos del Hero Section
|
||||
*
|
||||
* @param array $data Datos sin sanitizar del Hero Section
|
||||
* @return array Datos sanitizados
|
||||
*/
|
||||
public function sanitize($data) {
|
||||
return array_merge(
|
||||
// Activación y Visibilidad - Booleanos
|
||||
APUS_Sanitizer_Helper::sanitize_booleans($data, array(
|
||||
'enabled', 'show_on_mobile', 'show_on_desktop', 'show_category_badges',
|
||||
'use_gradient_background', 'enable_h1_text_shadow', 'enable_hero_box_shadow',
|
||||
'enable_badge_backdrop_filter'
|
||||
)),
|
||||
|
||||
// Contenido y Estructura - Textos
|
||||
APUS_Sanitizer_Helper::sanitize_texts($data, array(
|
||||
'category_badge_icon' => 'bi bi-folder-fill',
|
||||
'title_display_class' => 'display-5',
|
||||
'h1_text_shadow' => '1px 1px 2px rgba(0, 0, 0, 0.2)',
|
||||
'hero_box_shadow' => '0 4px 16px rgba(30, 58, 95, 0.25)',
|
||||
'badge_backdrop_filter' => 'blur(10px)',
|
||||
'custom_hero_classes' => '',
|
||||
'custom_badge_classes' => ''
|
||||
)),
|
||||
|
||||
// Colores de Category Badges - RGBA strings (text)
|
||||
array(
|
||||
'badge_bg_color' => APUS_Sanitizer_Helper::sanitize_text($data, 'badge_bg_color', 'rgba(255, 255, 255, 0.15)'),
|
||||
'badge_bg_hover_color' => APUS_Sanitizer_Helper::sanitize_text($data, 'badge_bg_hover_color', 'rgba(255, 133, 0, 0.2)'),
|
||||
'badge_border_color' => APUS_Sanitizer_Helper::sanitize_text($data, 'badge_border_color', 'rgba(255, 255, 255, 0.2)'),
|
||||
'badge_text_color' => APUS_Sanitizer_Helper::sanitize_text($data, 'badge_text_color', 'rgba(255, 255, 255, 0.95)')
|
||||
),
|
||||
|
||||
// Colores del Hero - Hex colors
|
||||
array(
|
||||
'gradient_start_color' => APUS_Sanitizer_Helper::sanitize_color($data, 'gradient_start_color', '#1e3a5f'),
|
||||
'gradient_end_color' => APUS_Sanitizer_Helper::sanitize_color($data, 'gradient_end_color', '#2c5282'),
|
||||
'hero_text_color' => APUS_Sanitizer_Helper::sanitize_color($data, 'hero_text_color', '#ffffff'),
|
||||
'solid_background_color' => APUS_Sanitizer_Helper::sanitize_color($data, 'solid_background_color', '#1e3a5f'),
|
||||
'badge_icon_color' => APUS_Sanitizer_Helper::sanitize_color($data, 'badge_icon_color', '#FFB800')
|
||||
),
|
||||
|
||||
// Enums
|
||||
APUS_Sanitizer_Helper::sanitize_enums($data, array(
|
||||
'title_alignment' => array('allowed' => array('left', 'center', 'right'), 'default' => 'center'),
|
||||
'badge_transition_speed' => array('allowed' => array('fast', 'normal', 'slow'), 'default' => 'normal'),
|
||||
'badge_hover_effect' => array('allowed' => array('none', 'background', 'scale', 'brightness'), 'default' => 'background')
|
||||
)),
|
||||
|
||||
// Enteros
|
||||
APUS_Sanitizer_Helper::sanitize_ints($data, array(
|
||||
'gradient_angle' => 135,
|
||||
'badge_border_radius' => 20
|
||||
)),
|
||||
|
||||
// Enteros en arrays (h1_font_weight, badge_font_weight)
|
||||
array(
|
||||
'h1_font_weight' => APUS_Sanitizer_Helper::sanitize_enum($data, 'h1_font_weight', array(400, 500, 600, 700), 700),
|
||||
'badge_font_weight' => APUS_Sanitizer_Helper::sanitize_enum($data, 'badge_font_weight', array(400, 500, 600, 700), 500)
|
||||
),
|
||||
|
||||
// Floats
|
||||
APUS_Sanitizer_Helper::sanitize_floats($data, array(
|
||||
'hero_padding_vertical' => 3.0,
|
||||
'hero_padding_horizontal' => 0.0,
|
||||
'hero_margin_bottom' => 1.5,
|
||||
'badges_gap' => 0.5,
|
||||
'badge_padding_vertical' => 0.375,
|
||||
'badge_padding_horizontal' => 0.875,
|
||||
'badge_font_size' => 0.813,
|
||||
'h1_line_height' => 1.4
|
||||
)),
|
||||
|
||||
// Array de strings
|
||||
array('excluded_categories' => APUS_Sanitizer_Helper::sanitize_array_of_strings(
|
||||
$data,
|
||||
'excluded_categories',
|
||||
array('Uncategorized', 'Sin categoría')
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
99
admin/includes/sanitizers/class-letstalkbutton-sanitizer.php
Normal file
99
admin/includes/sanitizers/class-letstalkbutton-sanitizer.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* Let's Talk Button Sanitizer
|
||||
*
|
||||
* Sanitiza configuraciones del componente Let's Talk Button
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @subpackage Admin_Panel\Sanitizers
|
||||
* @since 2.1.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class APUS_LetsTalkButton_Sanitizer
|
||||
*
|
||||
* Sanitiza todas las configuraciones del componente Let's Talk Button
|
||||
*/
|
||||
class APUS_LetsTalkButton_Sanitizer {
|
||||
|
||||
/**
|
||||
* Obtiene los valores por defecto del Let's Talk Button
|
||||
*
|
||||
* @return array Valores por defecto
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public function get_defaults() {
|
||||
return array(
|
||||
'enabled' => true,
|
||||
'text' => "Let's Talk",
|
||||
'icon_class' => 'bi bi-lightning-charge-fill',
|
||||
'show_icon' => true,
|
||||
'position' => 'right',
|
||||
'enable_box_shadow' => false,
|
||||
'hover_effect' => 'none',
|
||||
'modal_target' => '#contactModal',
|
||||
'custom_styles' => array(
|
||||
'background_color' => '#FF8600',
|
||||
'background_hover_color' => '#FF6B35',
|
||||
'text_color' => '#ffffff',
|
||||
'icon_color' => '#ffffff',
|
||||
'font_weight' => '600',
|
||||
'padding_vertical' => 0.5,
|
||||
'padding_horizontal' => 1.5,
|
||||
'border_radius' => 6,
|
||||
'border_width' => 0,
|
||||
'border_color' => '',
|
||||
'border_style' => 'solid',
|
||||
'transition_speed' => 'normal',
|
||||
'box_shadow' => '0 2px 8px rgba(0, 0, 0, 0.15)'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza los datos del Let's Talk Button
|
||||
*
|
||||
* @param array $data Datos sin sanitizar del Let's Talk Button
|
||||
* @return array Datos sanitizados
|
||||
*/
|
||||
public function sanitize($data) {
|
||||
return array_merge(
|
||||
// Booleanos
|
||||
APUS_Sanitizer_Helper::sanitize_booleans($data, array(
|
||||
'enabled', 'show_icon', 'enable_box_shadow'
|
||||
)),
|
||||
|
||||
// Textos
|
||||
APUS_Sanitizer_Helper::sanitize_texts($data, array(
|
||||
'text', 'icon_class', 'modal_target'
|
||||
)),
|
||||
|
||||
// Enums
|
||||
APUS_Sanitizer_Helper::sanitize_enums($data, array(
|
||||
'position' => array('allowed' => array('left', 'center', 'right'), 'default' => 'right'),
|
||||
'hover_effect' => array('allowed' => array('none', 'scale', 'brightness'), 'default' => 'none')
|
||||
)),
|
||||
|
||||
// Custom styles anidado
|
||||
array('custom_styles' => APUS_Sanitizer_Helper::sanitize_nested_group($data, 'custom_styles', array(
|
||||
'background_color' => array('type' => 'color', 'default' => ''),
|
||||
'background_hover_color' => array('type' => 'color', 'default' => ''),
|
||||
'text_color' => array('type' => 'color', 'default' => ''),
|
||||
'icon_color' => array('type' => 'color', 'default' => ''),
|
||||
'font_weight' => array('type' => 'text', 'default' => ''),
|
||||
'padding_vertical' => array('type' => 'float', 'default' => 0.0),
|
||||
'padding_horizontal' => array('type' => 'float', 'default' => 0.0),
|
||||
'border_radius' => array('type' => 'int', 'default' => 0),
|
||||
'border_width' => array('type' => 'int', 'default' => 0),
|
||||
'border_color' => array('type' => 'color', 'default' => ''),
|
||||
'border_style' => array('type' => 'enum', 'allowed' => array('solid', 'dashed', 'dotted'), 'default' => 'solid'),
|
||||
'transition_speed' => array('type' => 'enum', 'allowed' => array('fast', 'normal', 'slow'), 'default' => 'normal'),
|
||||
'box_shadow' => array('type' => 'text', 'default' => '')
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
136
admin/includes/sanitizers/class-navbar-sanitizer.php
Normal file
136
admin/includes/sanitizers/class-navbar-sanitizer.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Navbar Sanitizer
|
||||
*
|
||||
* Sanitiza configuraciones del componente Navbar
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @subpackage Admin_Panel\Sanitizers
|
||||
* @since 2.1.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class APUS_Navbar_Sanitizer
|
||||
*
|
||||
* Sanitiza todas las configuraciones del componente Navbar
|
||||
*/
|
||||
class APUS_Navbar_Sanitizer {
|
||||
|
||||
/**
|
||||
* Obtiene los valores por defecto del Navbar
|
||||
*
|
||||
* @return array Valores por defecto
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public function get_defaults() {
|
||||
return array(
|
||||
'enabled' => true,
|
||||
'show_on_mobile' => true,
|
||||
'show_on_desktop' => true,
|
||||
'position' => 'sticky',
|
||||
'responsive_breakpoint' => 'lg',
|
||||
'enable_box_shadow' => true,
|
||||
'enable_underline_effect' => true,
|
||||
'enable_hover_background' => true,
|
||||
|
||||
'lets_talk_button' => array(
|
||||
'enabled' => true,
|
||||
'text' => "Let's Talk",
|
||||
'icon_class' => 'bi bi-lightning-charge-fill',
|
||||
'show_icon' => true,
|
||||
'position' => 'right'
|
||||
),
|
||||
|
||||
'dropdown' => array(
|
||||
'enable_hover_desktop' => true,
|
||||
'max_height' => 70,
|
||||
'border_radius' => 8,
|
||||
'item_padding_vertical' => 0.5,
|
||||
'item_padding_horizontal' => 1.25
|
||||
),
|
||||
|
||||
'custom_styles' => array(
|
||||
'background_color' => '#1e3a5f',
|
||||
'text_color' => '#ffffff',
|
||||
'link_hover_color' => '#FF8600',
|
||||
'link_hover_bg_color' => '#FF8600',
|
||||
'dropdown_bg_color' => '#ffffff',
|
||||
'dropdown_item_color' => '#4A5568',
|
||||
'dropdown_item_hover_color' => '#FF8600',
|
||||
'font_size' => 'normal',
|
||||
'font_weight' => '500',
|
||||
'box_shadow_intensity' => 'normal',
|
||||
'border_radius' => 4,
|
||||
'padding_vertical' => 0.75,
|
||||
'link_padding_vertical' => 0.5,
|
||||
'link_padding_horizontal' => 0.65,
|
||||
'z_index' => 1030,
|
||||
'transition_speed' => 'normal'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza los datos del Navbar
|
||||
*
|
||||
* @param array $data Datos sin sanitizar del Navbar
|
||||
* @return array Datos sanitizados
|
||||
*/
|
||||
public function sanitize($data) {
|
||||
return array_merge(
|
||||
// Booleanos principales
|
||||
APUS_Sanitizer_Helper::sanitize_booleans($data, array(
|
||||
'enabled', 'show_on_mobile', 'show_on_desktop',
|
||||
'enable_box_shadow', 'enable_underline_effect', 'enable_hover_background'
|
||||
)),
|
||||
|
||||
// Enums principales
|
||||
APUS_Sanitizer_Helper::sanitize_enums($data, array(
|
||||
'position' => array('allowed' => array('sticky', 'static', 'fixed'), 'default' => 'sticky'),
|
||||
'responsive_breakpoint' => array('allowed' => array('sm', 'md', 'lg', 'xl', 'xxl'), 'default' => 'lg')
|
||||
)),
|
||||
|
||||
// Let's Talk Button anidado
|
||||
array('lets_talk_button' => APUS_Sanitizer_Helper::sanitize_nested_group($data, 'lets_talk_button', array(
|
||||
'enabled' => array('type' => 'bool'),
|
||||
'text' => array('type' => 'text', 'default' => ''),
|
||||
'icon_class' => array('type' => 'text', 'default' => ''),
|
||||
'show_icon' => array('type' => 'bool'),
|
||||
'position' => array('type' => 'enum', 'allowed' => array('left', 'center', 'right'), 'default' => 'right')
|
||||
))),
|
||||
|
||||
// Dropdown anidado
|
||||
array('dropdown' => APUS_Sanitizer_Helper::sanitize_nested_group($data, 'dropdown', array(
|
||||
'enable_hover_desktop' => array('type' => 'bool'),
|
||||
'max_height' => array('type' => 'int', 'default' => 70),
|
||||
'border_radius' => array('type' => 'int', 'default' => 8),
|
||||
'item_padding_vertical' => array('type' => 'float', 'default' => 0.5),
|
||||
'item_padding_horizontal' => array('type' => 'float', 'default' => 1.25)
|
||||
))),
|
||||
|
||||
// Custom styles anidado
|
||||
array('custom_styles' => APUS_Sanitizer_Helper::sanitize_nested_group($data, 'custom_styles', array(
|
||||
'background_color' => array('type' => 'color', 'default' => ''),
|
||||
'text_color' => array('type' => 'color', 'default' => ''),
|
||||
'link_hover_color' => array('type' => 'color', 'default' => ''),
|
||||
'link_hover_bg_color' => array('type' => 'color', 'default' => ''),
|
||||
'dropdown_bg_color' => array('type' => 'color', 'default' => ''),
|
||||
'dropdown_item_color' => array('type' => 'color', 'default' => ''),
|
||||
'dropdown_item_hover_color' => array('type' => 'color', 'default' => ''),
|
||||
'font_size' => array('type' => 'enum', 'allowed' => array('small', 'normal', 'large'), 'default' => 'normal'),
|
||||
'font_weight' => array('type' => 'enum', 'allowed' => array('400', '500', '600', '700'), 'default' => '500'),
|
||||
'box_shadow_intensity' => array('type' => 'enum', 'allowed' => array('none', 'light', 'normal', 'strong'), 'default' => 'normal'),
|
||||
'border_radius' => array('type' => 'int', 'default' => 4),
|
||||
'padding_vertical' => array('type' => 'float', 'default' => 0.75),
|
||||
'link_padding_vertical' => array('type' => 'float', 'default' => 0.5),
|
||||
'link_padding_horizontal' => array('type' => 'float', 'default' => 0.65),
|
||||
'z_index' => array('type' => 'int', 'default' => 1030),
|
||||
'transition_speed' => array('type' => 'enum', 'allowed' => array('fast', 'normal', 'slow'), 'default' => 'normal')
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
271
admin/includes/sanitizers/class-sanitizer-helper.php
Normal file
271
admin/includes/sanitizers/class-sanitizer-helper.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* Sanitizer Helper
|
||||
*
|
||||
* Métodos estáticos reutilizables para sanitización de datos
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @subpackage Admin_Panel\Sanitizers
|
||||
* @since 2.1.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class APUS_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 {
|
||||
|
||||
/**
|
||||
* Sanitiza un valor booleano
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @return bool Valor booleano sanitizado
|
||||
*/
|
||||
public static function sanitize_boolean($data, $key) {
|
||||
return !empty($data[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza múltiples valores booleanos
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param array $keys Array de claves a sanitizar
|
||||
* @return array Array asociativo con valores booleanos sanitizados
|
||||
*/
|
||||
public static function sanitize_booleans($data, $keys) {
|
||||
$result = array();
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = self::sanitize_boolean($data, $key);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un campo de texto con valor por defecto
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param string $default Valor por defecto (default: '')
|
||||
* @return string Texto sanitizado
|
||||
*/
|
||||
public static function sanitize_text($data, $key, $default = '') {
|
||||
return sanitize_text_field($data[$key] ?? $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza múltiples campos de texto
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param array $keys Array de claves a sanitizar
|
||||
* @param string $default Valor por defecto para todos (default: '')
|
||||
* @return array Array asociativo con textos sanitizados
|
||||
*/
|
||||
public static function sanitize_texts($data, $keys, $default = '') {
|
||||
$result = array();
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = self::sanitize_text($data, $key, $default);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un color hexadecimal con valor por defecto
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param string $default Valor por defecto (default: '')
|
||||
* @return string Color hexadecimal sanitizado
|
||||
*/
|
||||
public static function sanitize_color($data, $key, $default = '') {
|
||||
return sanitize_hex_color($data[$key] ?? $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza múltiples colores hexadecimales
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param array $keys Array de claves a sanitizar
|
||||
* @param string $default Valor por defecto para todos (default: '')
|
||||
* @return array Array asociativo con colores sanitizados
|
||||
*/
|
||||
public static function sanitize_colors($data, $keys, $default = '') {
|
||||
$result = array();
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = self::sanitize_color($data, $key, $default);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un valor con validación enum (in_array)
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param array $allowed_values Valores permitidos
|
||||
* @param mixed $default Valor por defecto
|
||||
* @return mixed Valor sanitizado
|
||||
*/
|
||||
public static function sanitize_enum($data, $key, $allowed_values, $default) {
|
||||
return in_array($data[$key] ?? '', $allowed_values, true)
|
||||
? $data[$key]
|
||||
: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza múltiples valores enum
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param array $config Array de configuración [key => ['allowed' => [...], 'default' => ...]]
|
||||
* @return array Array asociativo con valores enum sanitizados
|
||||
*/
|
||||
public static function sanitize_enums($data, $config) {
|
||||
$result = array();
|
||||
foreach ($config as $key => $settings) {
|
||||
$result[$key] = self::sanitize_enum(
|
||||
$data,
|
||||
$key,
|
||||
$settings['allowed'],
|
||||
$settings['default']
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un valor entero con valor por defecto
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param int $default Valor por defecto
|
||||
* @return int Entero sanitizado
|
||||
*/
|
||||
public static function sanitize_int($data, $key, $default = 0) {
|
||||
return isset($data[$key]) ? intval($data[$key]) : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza múltiples valores enteros
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param array $config Array de configuración [key => default_value]
|
||||
* @return array Array asociativo con enteros sanitizados
|
||||
*/
|
||||
public static function sanitize_ints($data, $config) {
|
||||
$result = array();
|
||||
foreach ($config as $key => $default) {
|
||||
$result[$key] = self::sanitize_int($data, $key, $default);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un valor float con valor por defecto
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param float $default Valor por defecto
|
||||
* @return float Float sanitizado
|
||||
*/
|
||||
public static function sanitize_float($data, $key, $default = 0.0) {
|
||||
return isset($data[$key]) ? floatval($data[$key]) : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza múltiples valores float
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param array $config Array de configuración [key => default_value]
|
||||
* @return array Array asociativo con floats sanitizados
|
||||
*/
|
||||
public static function sanitize_floats($data, $config) {
|
||||
$result = array();
|
||||
foreach ($config as $key => $default) {
|
||||
$result[$key] = self::sanitize_float($data, $key, $default);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza una URL con valor por defecto
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param string $default Valor por defecto (default: '')
|
||||
* @return string URL sanitizada
|
||||
*/
|
||||
public static function sanitize_url($data, $key, $default = '') {
|
||||
return esc_url_raw($data[$key] ?? $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un array de strings
|
||||
*
|
||||
* @param array $data Array de datos
|
||||
* @param string $key Clave del dato
|
||||
* @param array $default Array por defecto
|
||||
* @return array Array de strings sanitizados
|
||||
*/
|
||||
public static function sanitize_array_of_strings($data, $key, $default = array()) {
|
||||
return isset($data[$key]) && is_array($data[$key])
|
||||
? array_map('sanitize_text_field', $data[$key])
|
||||
: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza un grupo de campos anidados (custom_styles, dropdown, etc.)
|
||||
*
|
||||
* @param array $data Array de datos completo
|
||||
* @param string $group_key Clave del grupo (ej: 'custom_styles')
|
||||
* @param array $sanitization_rules Reglas de sanitización por campo
|
||||
* Formato: [
|
||||
* 'campo' => ['type' => 'text|color|int|float|enum|bool', 'default' => valor, 'allowed' => array()]
|
||||
* ]
|
||||
* @return array Array con campos del grupo sanitizados
|
||||
*/
|
||||
public static function sanitize_nested_group($data, $group_key, $sanitization_rules) {
|
||||
$result = array();
|
||||
$group_data = $data[$group_key] ?? array();
|
||||
|
||||
foreach ($sanitization_rules as $field => $rule) {
|
||||
$type = $rule['type'];
|
||||
$default = $rule['default'] ?? null;
|
||||
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
$result[$field] = self::sanitize_text($group_data, $field, $default ?? '');
|
||||
break;
|
||||
case 'color':
|
||||
$result[$field] = self::sanitize_color($group_data, $field, $default ?? '');
|
||||
break;
|
||||
case 'int':
|
||||
$result[$field] = self::sanitize_int($group_data, $field, $default ?? 0);
|
||||
break;
|
||||
case 'float':
|
||||
$result[$field] = self::sanitize_float($group_data, $field, $default ?? 0.0);
|
||||
break;
|
||||
case 'enum':
|
||||
$result[$field] = self::sanitize_enum(
|
||||
$group_data,
|
||||
$field,
|
||||
$rule['allowed'] ?? array(),
|
||||
$default
|
||||
);
|
||||
break;
|
||||
case 'bool':
|
||||
$result[$field] = self::sanitize_boolean($group_data, $field);
|
||||
break;
|
||||
default:
|
||||
$result[$field] = $group_data[$field] ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
88
admin/includes/sanitizers/class-topbar-sanitizer.php
Normal file
88
admin/includes/sanitizers/class-topbar-sanitizer.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Top Bar Sanitizer
|
||||
*
|
||||
* Sanitiza configuraciones del componente Top Bar
|
||||
*
|
||||
* @package Apus_Theme
|
||||
* @subpackage Admin_Panel\Sanitizers
|
||||
* @since 2.1.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class APUS_TopBar_Sanitizer
|
||||
*
|
||||
* Sanitiza todas las configuraciones del componente Top Bar
|
||||
*/
|
||||
class APUS_TopBar_Sanitizer {
|
||||
|
||||
/**
|
||||
* Obtiene los valores por defecto del Top Bar
|
||||
*
|
||||
* @return array Valores por defecto
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public function get_defaults() {
|
||||
return array(
|
||||
'enabled' => true,
|
||||
'show_on_mobile' => true,
|
||||
'show_on_desktop' => true,
|
||||
'icon_class' => 'bi bi-megaphone-fill',
|
||||
'show_icon' => true,
|
||||
'highlight_text' => 'Nuevo:',
|
||||
'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.',
|
||||
'link_text' => 'Ver Catálogo',
|
||||
'link_url' => '/catalogo',
|
||||
'link_target' => '_self',
|
||||
'show_link' => true,
|
||||
'custom_styles' => array(
|
||||
'background_color' => '#0E2337',
|
||||
'text_color' => '#ffffff',
|
||||
'highlight_color' => '#FF8600',
|
||||
'link_hover_color' => '#FF8600',
|
||||
'font_size' => 'normal'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza los datos del Top Bar
|
||||
*
|
||||
* @param array $data Datos sin sanitizar del Top Bar
|
||||
* @return array Datos sanitizados
|
||||
*/
|
||||
public function sanitize($data) {
|
||||
return array_merge(
|
||||
// Booleanos
|
||||
APUS_Sanitizer_Helper::sanitize_booleans($data, array(
|
||||
'enabled', 'show_on_mobile', 'show_on_desktop', 'show_icon', 'show_link'
|
||||
)),
|
||||
|
||||
// Textos
|
||||
APUS_Sanitizer_Helper::sanitize_texts($data, array(
|
||||
'icon_class', 'highlight_text', 'message_text', 'link_text'
|
||||
)),
|
||||
|
||||
// URL
|
||||
array('link_url' => APUS_Sanitizer_Helper::sanitize_url($data, 'link_url')),
|
||||
|
||||
// Enum
|
||||
array('link_target' => APUS_Sanitizer_Helper::sanitize_enum(
|
||||
$data, 'link_target', array('_self', '_blank'), '_self'
|
||||
)),
|
||||
|
||||
// Custom styles anidado
|
||||
array('custom_styles' => APUS_Sanitizer_Helper::sanitize_nested_group($data, 'custom_styles', array(
|
||||
'background_color' => array('type' => 'color', 'default' => ''),
|
||||
'text_color' => array('type' => 'color', 'default' => ''),
|
||||
'highlight_color' => array('type' => 'color', 'default' => ''),
|
||||
'link_hover_color' => array('type' => 'color', 'default' => ''),
|
||||
'font_size' => array('type' => 'enum', 'allowed' => array('small', 'normal', 'large'), 'default' => 'normal')
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user