HVAC trainer profile geocoding system with outstanding results: - 45 out of 53 trainer profiles successfully geocoded (85% coverage) - Coverage spans 15+ US states and 3 Canadian provinces - Google Maps API integration with intelligent rate limiting - Real-time statistics and comprehensive error handling Core Implementation: - HVAC_Geocoding_Ajax class with three AJAX endpoints: * hvac_trigger_geocoding: Manual geocoding operations * hvac_run_enhanced_import: CSV location data population * hvac_get_geocoding_stats: Coverage monitoring and statistics - Enhanced CSV import with corrected email field mapping - Proper field priority mapping for location data extraction - Automatic scheduling of geocoding operations after data import Technical Features: - Singleton pattern for proper class initialization - WordPress AJAX security with nonce verification - Role-based access control for master trainers - Comprehensive error logging and status tracking - API rate limiting (0.5s delays) to respect Google quotas - Multiple address format support (US/International) User Experience: - Master trainer controls for manual geocoding triggers - Real-time progress monitoring and statistics - Detailed error reporting for failed geocoding attempts - Production-ready interface for location data management Documentation: - Complete API reference with endpoint specifications - Comprehensive manual geocoding system documentation - Usage examples and troubleshooting guidelines - Error codes and integration patterns 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			536 lines
		
	
	
		
			No EOL
		
	
	
		
			22 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			536 lines
		
	
	
		
			No EOL
		
	
	
		
			22 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Data Migration Script for Trainer Profile Custom Post Types
 | |
|  * 
 | |
|  * This script migrates existing user meta data to the new trainer_profile custom post type system.
 | |
|  * It should be run once after the new system is deployed.
 | |
|  */
 | |
| 
 | |
| if (!defined('ABSPATH')) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| class HVAC_Trainer_Profile_Migration {
 | |
|     
 | |
|     private static $migration_log = [];
 | |
|     private static $migration_stats = [
 | |
|         'total_users' => 0,
 | |
|         'profiles_created' => 0,
 | |
|         'profiles_updated' => 0,
 | |
|         'errors' => 0,
 | |
|         'skipped' => 0
 | |
|     ];
 | |
|     
 | |
|     public static function run_migration($dry_run = false) {
 | |
|         $migration_id = uniqid('migration_');
 | |
|         $start_time = microtime(true);
 | |
|         
 | |
|         self::log_message("Starting trainer profile migration (ID: {$migration_id})", 'info');
 | |
|         
 | |
|         if ($dry_run) {
 | |
|             self::log_message("Running in DRY RUN mode - no changes will be made", 'info');
 | |
|         }
 | |
|         
 | |
|         // Initialize migration tracking
 | |
|         if (!$dry_run) {
 | |
|             update_option('hvac_migration_status', [
 | |
|                 'id' => $migration_id,
 | |
|                 'status' => 'in_progress',
 | |
|                 'start_time' => time(),
 | |
|                 'dry_run' => false
 | |
|             ]);
 | |
|         }
 | |
|         
 | |
|         try {
 | |
|             // Get all users with trainer roles
 | |
|             $trainers = get_users([
 | |
|                 'role__in' => ['hvac_trainer', 'hvac_master_trainer', 'event_trainer'], // Include legacy role
 | |
|                 'meta_query' => [
 | |
|                     'relation' => 'OR',
 | |
|                     [
 | |
|                         'key' => 'trainer_profile_id',
 | |
|                         'compare' => 'NOT EXISTS'
 | |
|                     ],
 | |
|                     [
 | |
|                         'key' => 'trainer_profile_id',
 | |
|                         'value' => '',
 | |
|                         'compare' => '='
 | |
|                     ]
 | |
|                 ]
 | |
|             ]);
 | |
|             
 | |
|             self::$migration_stats['total_users'] = count($trainers);
 | |
|             self::log_message("Found " . count($trainers) . " trainer users to migrate", 'info');
 | |
|             
 | |
|             foreach ($trainers as $user) {
 | |
|                 try {
 | |
|                     self::migrate_user_to_profile($user, !$dry_run);
 | |
|                 } catch (Exception $e) {
 | |
|                     self::$migration_stats['errors']++;
 | |
|                     self::log_message("Error migrating user {$user->ID} ({$user->user_email}): " . $e->getMessage(), 'error');
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // Migrate any existing CSV import data
 | |
|             self::migrate_csv_data(!$dry_run);
 | |
|             
 | |
|             // Complete migration
 | |
|             if (!$dry_run) {
 | |
|                 self::complete_migration($migration_id);
 | |
|             }
 | |
|             
 | |
|             $end_time = microtime(true);
 | |
|             $duration = round($end_time - $start_time, 2);
 | |
|             
 | |
|             self::log_message("Migration completed in {$duration} seconds", 'info');
 | |
|             self::log_message("Statistics: " . json_encode(self::$migration_stats), 'info');
 | |
|             
 | |
|         } catch (Exception $e) {
 | |
|             if (!$dry_run) {
 | |
|                 self::fail_migration($migration_id, $e->getMessage());
 | |
|             }
 | |
|             self::log_message("Migration failed: " . $e->getMessage(), 'error');
 | |
|             throw $e;
 | |
|         }
 | |
|         
 | |
|         return [
 | |
|             'success' => true,
 | |
|             'stats' => self::$migration_stats,
 | |
|             'log' => self::$migration_log
 | |
|         ];
 | |
|     }
 | |
|     
 | |
|     private static function migrate_user_to_profile($user, $commit = true) {
 | |
|         // Check if user already has a profile
 | |
|         $existing_profile_id = get_user_meta($user->ID, 'trainer_profile_id', true);
 | |
|         if ($existing_profile_id && get_post($existing_profile_id)) {
 | |
|             self::log_message("User {$user->ID} already has profile {$existing_profile_id}, skipping", 'info');
 | |
|             self::$migration_stats['skipped']++;
 | |
|             return $existing_profile_id;
 | |
|         }
 | |
|         
 | |
|         self::log_message("Migrating user {$user->ID} ({$user->user_email})", 'info');
 | |
|         
 | |
|         if (!$commit) {
 | |
|             self::log_message("DRY RUN: Would create trainer profile for user {$user->ID}", 'info');
 | |
|             self::$migration_stats['profiles_created']++;
 | |
|             return true;
 | |
|         }
 | |
|         
 | |
|         // Create trainer profile post
 | |
|         $profile_data = [
 | |
|             'post_type' => 'trainer_profile',
 | |
|             'post_title' => $user->display_name . ' - Trainer Profile',
 | |
|             'post_status' => 'publish',
 | |
|             'post_author' => $user->ID,
 | |
|             'post_content' => get_user_meta($user->ID, 'description', true) ?: get_user_meta($user->ID, 'biographical_info', true) ?: ''
 | |
|         ];
 | |
|         
 | |
|         $profile_id = wp_insert_post($profile_data);
 | |
|         
 | |
|         if (is_wp_error($profile_id)) {
 | |
|             throw new Exception("Failed to create profile post: " . $profile_id->get_error_message());
 | |
|         }
 | |
|         
 | |
|         // Establish relationships
 | |
|         update_post_meta($profile_id, 'user_id', $user->ID);
 | |
|         update_user_meta($user->ID, 'trainer_profile_id', $profile_id);
 | |
|         
 | |
|         // Migrate user meta to profile meta
 | |
|         $migrated_fields = self::migrate_user_meta_fields($user->ID, $profile_id);
 | |
|         
 | |
|         // Set default visibility
 | |
|         update_post_meta($profile_id, 'is_public_profile', '1');
 | |
|         
 | |
|         // Trigger geocoding if address data exists
 | |
|         $address_fields = ['trainer_city', 'trainer_state', 'trainer_country'];
 | |
|         $has_address = false;
 | |
|         foreach ($address_fields as $field) {
 | |
|             if (get_post_meta($profile_id, $field, true)) {
 | |
|                 $has_address = true;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         if ($has_address) {
 | |
|             wp_schedule_single_event(time() + 5, 'hvac_geocode_address', [$profile_id]);
 | |
|             self::log_message("Scheduled geocoding for profile {$profile_id}", 'info');
 | |
|         }
 | |
|         
 | |
|         self::$migration_stats['profiles_created']++;
 | |
|         self::log_message("Created profile {$profile_id} for user {$user->ID}, migrated " . count($migrated_fields) . " fields", 'info');
 | |
|         
 | |
|         return $profile_id;
 | |
|     }
 | |
|     
 | |
|     private static function migrate_user_meta_fields($user_id, $profile_id) {
 | |
|         $user = get_userdata($user_id);
 | |
|         $migrated_fields = [];
 | |
|         
 | |
|         // Synchronized fields (user ↔ profile)
 | |
|         $sync_fields = [
 | |
|             'first_name' => 'trainer_first_name',
 | |
|             'last_name' => 'trainer_last_name',
 | |
|             'display_name' => 'trainer_display_name'
 | |
|         ];
 | |
|         
 | |
|         foreach ($sync_fields as $user_field => $profile_field) {
 | |
|             $value = $user->$user_field;
 | |
|             if ($value) {
 | |
|                 update_post_meta($profile_id, $profile_field, $value);
 | |
|                 $migrated_fields[] = $profile_field;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Profile-exclusive fields with potential user meta mappings
 | |
|         $profile_field_mappings = [
 | |
|             'linkedin_profile_url' => ['user_linkedin', 'linkedin_profile_url', 'linkedin_url'],
 | |
|             'personal_accreditation' => ['personal_accreditation', 'accreditation'],
 | |
|             'biographical_info' => ['biographical_info', 'bio', 'description'],
 | |
|             'training_audience' => ['training_audience', 'target_audience'],
 | |
|             'training_formats' => ['training_formats', 'training_methods'],
 | |
|             'training_locations' => ['training_locations', 'training_areas'],
 | |
|             'training_resources' => ['training_resources', 'resources'],
 | |
|             'annual_revenue_target' => ['annual_revenue_target', 'revenue_target'],
 | |
|             'application_details' => ['application_details', 'application_reason'],
 | |
|             'date_certified' => ['date_certified', 'certification_date'],
 | |
|             'certification_type' => ['certification_type', 'cert_type'],
 | |
|             'certification_status' => ['certification_status', 'cert_status'],
 | |
|             'trainer_city' => ['user_city', 'trainer_city', 'city'],
 | |
|             'trainer_state' => ['user_state', 'trainer_state', 'state'],
 | |
|             'trainer_country' => ['user_country', 'trainer_country', 'country'],
 | |
|             'role' => ['role', 'job_role', 'position'],
 | |
|             'years_experience' => ['years_experience', 'experience_years']
 | |
|         ];
 | |
|         
 | |
|         foreach ($profile_field_mappings as $profile_field => $possible_meta_keys) {
 | |
|             $value = null;
 | |
|             
 | |
|             // Try each possible meta key until we find a value
 | |
|             foreach ($possible_meta_keys as $meta_key) {
 | |
|                 $temp_value = get_user_meta($user_id, $meta_key, true);
 | |
|                 if (!empty($temp_value)) {
 | |
|                     $value = $temp_value;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             if ($value) {
 | |
|                 update_post_meta($profile_id, $profile_field, $value);
 | |
|                 $migrated_fields[] = $profile_field;
 | |
|                 
 | |
|                 // Clean up old user meta fields to prevent confusion
 | |
|                 foreach ($possible_meta_keys as $old_key) {
 | |
|                     if ($old_key !== $profile_field) { // Don't delete if same name
 | |
|                         delete_user_meta($user_id, $old_key);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Handle business type (migrate from organizer category if available)
 | |
|         $organizer_id = get_user_meta($user_id, 'organizer_id', true);
 | |
|         if ($organizer_id) {
 | |
|             $organizer = get_post($organizer_id);
 | |
|             if ($organizer) {
 | |
|                 $organizer_category = get_post_meta($organizer_id, '_hvac_organizer_category', true);
 | |
|                 if ($organizer_category) {
 | |
|                     // Try to match with existing business type terms
 | |
|                     $term = get_term_by('name', $organizer_category, 'business_type');
 | |
|                     if (!$term) {
 | |
|                         // Create the term if it doesn't exist
 | |
|                         $term_result = wp_insert_term($organizer_category, 'business_type');
 | |
|                         if (!is_wp_error($term_result)) {
 | |
|                             $term = get_term($term_result['term_id'], 'business_type');
 | |
|                         }
 | |
|                     }
 | |
|                     
 | |
|                     if ($term && !is_wp_error($term)) {
 | |
|                         wp_set_post_terms($profile_id, [$term->term_id], 'business_type');
 | |
|                         $migrated_fields[] = 'business_type';
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Set default certification status if not provided
 | |
|         if (!get_post_meta($profile_id, 'certification_status', true)) {
 | |
|             // Determine default status based on user role
 | |
|             $user_roles = $user->roles;
 | |
|             if (in_array('hvac_master_trainer', $user_roles)) {
 | |
|                 update_post_meta($profile_id, 'certification_status', 'Active');
 | |
|                 update_post_meta($profile_id, 'certification_type', 'Certified measureQuick Champion');
 | |
|             } else {
 | |
|                 update_post_meta($profile_id, 'certification_status', 'Active');
 | |
|                 update_post_meta($profile_id, 'certification_type', 'Certified measureQuick Trainer');
 | |
|             }
 | |
|             $migrated_fields[] = 'certification_status';
 | |
|             $migrated_fields[] = 'certification_type';
 | |
|         }
 | |
|         
 | |
|         return $migrated_fields;
 | |
|     }
 | |
|     
 | |
|     private static function migrate_csv_data($commit = true) {
 | |
|         // Check if there's any CSV import data to migrate
 | |
|         $csv_import_log = get_option('hvac_csv_import_log', []);
 | |
|         
 | |
|         if (empty($csv_import_log)) {
 | |
|             self::log_message("No CSV import data found to migrate", 'info');
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         self::log_message("Found CSV import data, processing additional mappings", 'info');
 | |
|         
 | |
|         foreach ($csv_import_log as $import_session) {
 | |
|             if (isset($import_session['imported_users'])) {
 | |
|                 foreach ($import_session['imported_users'] as $user_data) {
 | |
|                     if (isset($user_data['user_id'])) {
 | |
|                         $user_id = $user_data['user_id'];
 | |
|                         $profile_id = get_user_meta($user_id, 'trainer_profile_id', true);
 | |
|                         
 | |
|                         if ($profile_id && $commit) {
 | |
|                             // Update any CSV-specific fields that might have been missed
 | |
|                             if (isset($user_data['csv_data'])) {
 | |
|                                 self::update_profile_from_csv($profile_id, $user_data['csv_data']);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private static function update_profile_from_csv($profile_id, $csv_data) {
 | |
|         $csv_field_mappings = [
 | |
|             // Basic information
 | |
|             'Company Name' => 'organization_name',
 | |
|             'Organization Logo URL' => 'organization_logo_url',
 | |
|             
 | |
|             // Location data - using correct CSV field names
 | |
|             'City' => 'trainer_city',
 | |
|             'State' => 'trainer_state', 
 | |
|             'Country' => 'trainer_country',
 | |
|             
 | |
|             // Business information
 | |
|             'Organizer Category' => 'business_type',
 | |
|             'mapped_business_type' => 'business_type', // Use mapped version if available
 | |
|             
 | |
|             // Training information
 | |
|             'Training Audience' => 'training_audience',
 | |
|             'parsed_training_audience' => 'training_audience', // Use parsed version if available
 | |
|             'Training Experience' => 'training_experience',
 | |
|             'Specialization' => 'specialization',
 | |
|             
 | |
|             // Certification data
 | |
|             'Certification Type' => 'certification_type',
 | |
|             'Certification Status' => 'certification_status',
 | |
|             'Date Certified,' => 'date_certified', // Note the comma in CSV field name
 | |
|             'standardized_date' => 'date_certified', // Use standardized version if available
 | |
|             
 | |
|             // Contact and business details
 | |
|             'Company Website' => 'business_website',
 | |
|             'Phone Number' => 'business_phone',
 | |
|             'Application Details' => 'application_details',
 | |
|             
 | |
|             // Role information
 | |
|             'Role' => 'role',
 | |
|             'mapped_role' => 'role', // Use mapped version if available
 | |
|             
 | |
|             // Additional fields
 | |
|             'Create Venue' => 'create_venue',
 | |
|             'Create Organizer' => 'create_organizer'
 | |
|         ];
 | |
|         
 | |
|         // Group mappings by profile field to handle multiple CSV sources
 | |
|         $field_priority_mappings = [
 | |
|             'trainer_city' => ['City'],
 | |
|             'trainer_state' => ['State'], 
 | |
|             'trainer_country' => ['Country'],
 | |
|             'business_type' => ['mapped_business_type', 'Organizer Category'],
 | |
|             'training_audience' => ['parsed_training_audience', 'Training Audience'],
 | |
|             'date_certified' => ['standardized_date', 'Date Certified,'],
 | |
|             'role' => ['mapped_role', 'Role'],
 | |
|             'organization_name' => ['Company Name'],
 | |
|             'certification_type' => ['Certification Type'],
 | |
|             'certification_status' => ['Certification Status'],
 | |
|             'business_website' => ['Company Website'],
 | |
|             'business_phone' => ['Phone Number'],
 | |
|             'application_details' => ['Application Details'],
 | |
|             'create_venue' => ['Create Venue'],
 | |
|             'create_organizer' => ['Create Organizer'],
 | |
|             'training_experience' => ['Training Experience'],
 | |
|             'specialization' => ['Specialization'],
 | |
|             'organization_logo_url' => ['Organization Logo URL']
 | |
|         ];
 | |
|         
 | |
|         foreach ($field_priority_mappings as $profile_field => $csv_keys) {
 | |
|             $value = null;
 | |
|             
 | |
|             // Try each CSV key in priority order until we find a value
 | |
|             foreach ($csv_keys as $csv_key) {
 | |
|                 if (isset($csv_data[$csv_key]) && !empty(trim($csv_data[$csv_key]))) {
 | |
|                     $value = trim($csv_data[$csv_key]);
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             if ($value) {
 | |
|                 if ($profile_field === 'business_type') {
 | |
|                     // Handle taxonomy
 | |
|                     $term = get_term_by('name', $value, 'business_type');
 | |
|                     if (!$term) {
 | |
|                         // Create the term if it doesn't exist
 | |
|                         $term_result = wp_insert_term($value, 'business_type');
 | |
|                         if (!is_wp_error($term_result)) {
 | |
|                             $term = get_term($term_result['term_id'], 'business_type');
 | |
|                         }
 | |
|                     }
 | |
|                     if ($term && !is_wp_error($term)) {
 | |
|                         wp_set_post_terms($profile_id, [$term->term_id], 'business_type');
 | |
|                         self::log_message("Updated {$profile_field} with taxonomy term: {$value}", 'info');
 | |
|                     }
 | |
|                 } else {
 | |
|                     update_post_meta($profile_id, $profile_field, sanitize_text_field($value));
 | |
|                     self::log_message("Updated {$profile_field} with value: {$value}", 'info');
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private static function complete_migration($migration_id) {
 | |
|         update_option('hvac_migration_status', [
 | |
|             'id' => $migration_id,
 | |
|             'status' => 'completed',
 | |
|             'end_time' => time(),
 | |
|             'stats' => self::$migration_stats
 | |
|         ]);
 | |
|         
 | |
|         // Clean up any scheduled events that might conflict
 | |
|         wp_clear_scheduled_hook('hvac_verify_sync_integrity');
 | |
|         
 | |
|         // Schedule sync verification
 | |
|         if (!wp_next_scheduled('hvac_verify_sync_integrity')) {
 | |
|             wp_schedule_event(time() + 300, 'hourly', 'hvac_verify_sync_integrity');
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private static function fail_migration($migration_id, $error_message) {
 | |
|         update_option('hvac_migration_status', [
 | |
|             'id' => $migration_id,
 | |
|             'status' => 'failed',
 | |
|             'end_time' => time(),
 | |
|             'error' => $error_message,
 | |
|             'stats' => self::$migration_stats
 | |
|         ]);
 | |
|     }
 | |
|     
 | |
|     private static function log_message($message, $level = 'info') {
 | |
|         $timestamp = date('Y-m-d H:i:s');
 | |
|         $log_entry = "[{$timestamp}] [{$level}] {$message}";
 | |
|         
 | |
|         self::$migration_log[] = $log_entry;
 | |
|         
 | |
|         // Also log to WordPress debug log if enabled
 | |
|         if (defined('WP_DEBUG') && WP_DEBUG) {
 | |
|             error_log("HVAC Profile Migration: " . $log_entry);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public static function get_migration_status() {
 | |
|         return get_option('hvac_migration_status', ['status' => 'not_started']);
 | |
|     }
 | |
|     
 | |
|     public static function rollback_migration($migration_id = null) {
 | |
|         $migration_status = self::get_migration_status();
 | |
|         
 | |
|         if (!$migration_id) {
 | |
|             $migration_id = $migration_status['id'] ?? null;
 | |
|         }
 | |
|         
 | |
|         if (!$migration_id) {
 | |
|             throw new Exception('No migration ID provided for rollback');
 | |
|         }
 | |
|         
 | |
|         self::log_message("Starting rollback for migration {$migration_id}", 'info');
 | |
|         
 | |
|         // Get all trainer profiles created by this migration
 | |
|         $profiles = get_posts([
 | |
|             'post_type' => 'trainer_profile',
 | |
|             'posts_per_page' => -1,
 | |
|             'fields' => 'ids'
 | |
|         ]);
 | |
|         
 | |
|         $rolled_back = 0;
 | |
|         foreach ($profiles as $profile_id) {
 | |
|             $user_id = get_post_meta($profile_id, 'user_id', true);
 | |
|             
 | |
|             if ($user_id) {
 | |
|                 // Remove the relationship
 | |
|                 delete_user_meta($user_id, 'trainer_profile_id');
 | |
|                 
 | |
|                 // Delete the profile
 | |
|                 wp_delete_post($profile_id, true);
 | |
|                 
 | |
|                 $rolled_back++;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Update migration status
 | |
|         update_option('hvac_migration_status', [
 | |
|             'id' => $migration_id,
 | |
|             'status' => 'rolled_back',
 | |
|             'rollback_time' => time(),
 | |
|             'profiles_removed' => $rolled_back
 | |
|         ]);
 | |
|         
 | |
|         self::log_message("Rollback completed: removed {$rolled_back} profiles", 'info');
 | |
|         
 | |
|         return $rolled_back;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // CLI Command support
 | |
| if (defined('WP_CLI') && WP_CLI) {
 | |
|     WP_CLI::add_command('hvac migrate-profiles', function($args, $assoc_args) {
 | |
|         $dry_run = isset($assoc_args['dry-run']) && $assoc_args['dry-run'];
 | |
|         
 | |
|         WP_CLI::line('Starting HVAC Trainer Profile Migration...');
 | |
|         
 | |
|         try {
 | |
|             $result = HVAC_Trainer_Profile_Migration::run_migration($dry_run);
 | |
|             
 | |
|             WP_CLI::success('Migration completed successfully!');
 | |
|             WP_CLI::line('Statistics:');
 | |
|             WP_CLI::line('  Total users: ' . $result['stats']['total_users']);
 | |
|             WP_CLI::line('  Profiles created: ' . $result['stats']['profiles_created']);
 | |
|             WP_CLI::line('  Profiles updated: ' . $result['stats']['profiles_updated']);
 | |
|             WP_CLI::line('  Errors: ' . $result['stats']['errors']);
 | |
|             WP_CLI::line('  Skipped: ' . $result['stats']['skipped']);
 | |
|             
 | |
|             if (!empty($result['log'])) {
 | |
|                 WP_CLI::line("\nDetailed log:");
 | |
|                 foreach ($result['log'] as $log_entry) {
 | |
|                     WP_CLI::line('  ' . $log_entry);
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|         } catch (Exception $e) {
 | |
|             WP_CLI::error('Migration failed: ' . $e->getMessage());
 | |
|         }
 | |
|     });
 | |
|     
 | |
|     WP_CLI::add_command('hvac rollback-profiles', function($args, $assoc_args) {
 | |
|         $migration_id = $assoc_args['migration-id'] ?? null;
 | |
|         
 | |
|         WP_CLI::line('Starting HVAC Trainer Profile Migration Rollback...');
 | |
|         
 | |
|         try {
 | |
|             $rolled_back = HVAC_Trainer_Profile_Migration::rollback_migration($migration_id);
 | |
|             WP_CLI::success("Rollback completed! Removed {$rolled_back} profiles.");
 | |
|             
 | |
|         } catch (Exception $e) {
 | |
|             WP_CLI::error('Rollback failed: ' . $e->getMessage());
 | |
|         }
 | |
|     });
 | |
| } |