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 ); } }