[ 'class' => SettingsMigration::class, 'dependencies' => [] ], 'clients' => [ 'class' => ClientMigration::class, 'dependencies' => ['settings'] ], 'post_types' => [ 'class' => PostTypeMigration::class, 'dependencies' => ['clients'] ], 'meta' => [ 'class' => MetaMigration::class, 'dependencies' => ['clients', 'post_types'] ], 'pro_options' => [ 'class' => ProOptionsMigration::class, 'dependencies' => ['settings'] ], 'cleanup' => [ 'class' => DataCleanupMigration::class, 'dependencies' => ['post_types', 'meta'] ] ]; /** * Constructor. * * @since 2.0.0 * @param VersionChecker $version_checker Version checker instance */ public function __construct(VersionChecker $version_checker) { $this->version_checker = $version_checker; } /** * Check if migration is needed. * * @since 2.0.0 * @return bool */ public function should_run_migration(): bool { // Get current version $current_version = $this->version_checker->get_stored_version(); // Get target version $target_version = $this->version_checker->get_target_version(); // Compare versions if (version_compare($current_version, $target_version, '<')) { $this->log_migration_check(true, $current_version, $target_version); return true; } $this->log_migration_check(false, $current_version, $target_version); return false; } /** * Run the migration. * * @since 2.0.0 * @return array */ public function run_migration(): array { try { // Set migration running flag $this->set_migration_running(true); // Initialize results $results = []; $errors = []; // Get migration order based on dependencies $migration_order = $this->get_migration_order(); // Run each migration step in order foreach ($migration_order as $step) { $result = $this->run_migration_step($step); $results[$step] = $result; // Check for errors if (!$result['success']) { $errors[] = sprintf('%s migration failed: %s', $step, $result['message']); break; // Stop on first error } // Store step completion update_option('easy_invoice_migration_step_' . $step . '_completed', true); } // Update version after successful migration if (empty($errors)) { $this->update_version(); $this->set_migration_completed(true); $this->log_migration_success($results); } else { $this->log_migration_error($errors); } // Clear migration running flag $this->set_migration_running(false); return [ 'success' => empty($errors), 'results' => $results, 'errors' => $errors ]; } catch (\Exception $e) { // Log error and clear flags $this->log_migration_error([$e->getMessage()]); $this->set_migration_running(false); return [ 'success' => false, 'message' => $e->getMessage(), 'errors' => [$e->getMessage()] ]; } } /** * Get migration order based on dependencies. * * @since 2.0.0 * @return array */ private function get_migration_order(): array { $order = []; $visited = []; foreach (array_keys($this->migration_steps) as $step) { if (!isset($visited[$step])) { $this->visit_step($step, $visited, $order); } } return $order; } /** * Visit migration step for topological sort. * * @since 2.0.0 * @param string $step Step name * @param array $visited Visited steps * @param array $order Migration order * @return void */ private function visit_step(string $step, array &$visited, array &$order): void { $visited[$step] = true; foreach ($this->migration_steps[$step]['dependencies'] as $dependency) { if (!isset($visited[$dependency])) { $this->visit_step($dependency, $visited, $order); } } $order[] = $step; } /** * Check if a migration step is completed. * * @since 2.0.0 * @param string $step Migration step * @return bool */ private function is_step_completed(string $step): bool { return get_option('easy_invoice_migration_step_' . $step . '_completed', false); } /** * Run a specific migration step. * * @since 2.0.0 * @param string $step Migration step * @return array */ public function run_migration_step(string $step): array { if (!isset($this->migration_steps[$step])) { return [ 'success' => false, 'message' => sprintf('Invalid migration step: %s', $step) ]; } try { // Check dependencies foreach ($this->migration_steps[$step]['dependencies'] as $dependency) { if (!$this->is_step_completed($dependency)) { return [ 'success' => false, 'message' => sprintf('Dependency %s must be migrated first', $dependency) ]; } } $class = $this->migration_steps[$step]['class']; $migration = new $class(); if (!$migration->is_needed()) { // Mark step as completed even if not needed update_option('easy_invoice_migration_step_' . $step . '_completed', true); return [ 'success' => true, 'message' => sprintf('%s migration not needed', $step) ]; } $result = $migration->migrate(); if ($result['success']) { // Mark step as completed on success update_option('easy_invoice_migration_step_' . $step . '_completed', true); } return $result; } catch (\Exception $e) { return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * Set migration running flag. * * @since 2.0.0 * @param bool $running Running state * @return void */ private function set_migration_running(bool $running): void { update_option('easy_invoice_migration_running', $running); } /** * Set migration completed flag. * * @since 2.0.0 * @param bool $completed Completed state * @return void */ private function set_migration_completed(bool $completed): void { update_option('easy_invoice_migration_completed', $completed); if ($completed) { update_option('easy_invoice_migration_completed_time', current_time('mysql')); } } /** * Update version after successful migration. * * @since 2.0.0 * @return void */ private function update_version(): void { $target_version = $this->version_checker->get_target_version(); update_option('easy_invoice_version', $target_version); } /** * Log migration check. * * @since 2.0.0 * @param bool $needed Whether migration is needed * @param string $current_version Current version * @param string $target_version Target version * @return void */ private function log_migration_check(bool $needed, string $current_version, string $target_version): void { $message = $needed ? sprintf('Migration needed: Current version %s is less than target version %s', $current_version, $target_version) : sprintf('Migration not needed: Current version %s is greater than or equal to target version %s', $current_version, $target_version); $this->log($message); } /** * Log migration success. * * @since 2.0.0 * @param array $results Migration results * @return void */ private function log_migration_success(array $results): void { $message = 'Migration completed successfully:' . PHP_EOL; foreach ($results as $step => $result) { $message .= sprintf('- %s: %s' . PHP_EOL, $step, $result['message']); } $message .= PHP_EOL . 'System Information:' . PHP_EOL; $message .= sprintf('- WordPress Version: %s' . PHP_EOL, get_bloginfo('version')); $message .= sprintf('- PHP Version: %s' . PHP_EOL, PHP_VERSION); $message .= sprintf('- MySQL Version: %s' . PHP_EOL, $this->get_mysql_version()); $message .= sprintf('- Memory Limit: %s' . PHP_EOL, WP_MEMORY_LIMIT); $message .= sprintf('- Max Execution Time: %s' . PHP_EOL, ini_get('max_execution_time')); $this->log($message); } /** * Log migration error. * * @since 2.0.0 * @param array $errors Error messages * @return void */ private function log_migration_error(array $errors): void { $message = 'Migration failed:' . PHP_EOL; foreach ($errors as $error) { $message .= '- ' . $error . PHP_EOL; } $this->log($message, 'error'); } /** * Get MySQL version. * * @since 2.0.0 * @return string */ private function get_mysql_version(): string { global $wpdb; return $wpdb->get_var('SELECT VERSION()'); } /** * Log a message. * * @since 2.0.0 * @param string $message Message to log * @param string $level Log level * @return void */ private function log(string $message, string $level = 'info'): void { $log = [ 'message' => $message, 'level' => $level, 'timestamp' => current_time('mysql') ]; $logs = get_option('easy_invoice_migration_logs', []); $logs[] = $log; update_option('easy_invoice_migration_logs', $logs); } }