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