diff --git a/admin-import-trainers.php b/admin-import-trainers.php new file mode 100644 index 00000000..af8bce7c --- /dev/null +++ b/admin-import-trainers.php @@ -0,0 +1,415 @@ +import_csv($csv_file); + $output = ob_get_clean(); + + echo '

Import completed!

'; + echo '
' . esc_html($output) . '
'; + } else { + echo '

Error uploading file.

'; + } +} + +class TrainerCSVImporter { + + private $dry_run = false; + private $stats = [ + 'processed' => 0, + 'created' => 0, + 'updated' => 0, + 'skipped' => 0, + 'errors' => 0 + ]; + + public function __construct($dry_run = false) { + $this->dry_run = $dry_run; + } + + public function import_csv($csv_file) { + echo "Starting import from uploaded CSV\n"; + if ($this->dry_run) { + echo "DRY RUN MODE - No data will be modified\n"; + } + echo "----------------------------------------\n"; + + $handle = fopen($csv_file, 'r'); + if (!$handle) { + die("Could not open CSV file\n"); + } + + // Read header row + $headers = fgetcsv($handle); + if (!$headers) { + die("Could not read CSV headers\n"); + } + + // Process each row + while (($row = fgetcsv($handle)) !== false) { + $data = array_combine($headers, $row); + $this->process_trainer_record($data); + $this->stats['processed']++; + } + + fclose($handle); + $this->print_stats(); + } + + private function process_trainer_record($data) { + // Skip if no email + if (empty($data['Work Email'])) { + echo "Skipping record with no email\n"; + $this->stats['skipped']++; + return; + } + + $email = sanitize_email($data['Work Email']); + $existing_user = get_user_by('email', $email); + + if ($existing_user) { + echo "Updating existing user: $email\n"; + $this->update_user($existing_user, $data); + $this->stats['updated']++; + } else { + echo "Creating new user: $email\n"; + $this->create_user($data); + $this->stats['created']++; + } + } + + private function create_user($data) { + if ($this->dry_run) return; + + $email = sanitize_email($data['Work Email']); + $username = $this->generate_username($email); + + // Generate random password - user will need to reset + $password = wp_generate_password(12, true, true); + + $user_data = [ + 'user_login' => $username, + 'user_email' => $email, + 'user_pass' => $password, + 'first_name' => sanitize_text_field($data['Name'] ?? ''), + 'last_name' => sanitize_text_field($data['Last Name'] ?? ''), + 'display_name' => trim(($data['Name'] ?? '') . ' ' . ($data['Last Name'] ?? '')), + 'role' => 'hvac_trainer' + ]; + + $user_id = wp_insert_user($user_data); + + if (is_wp_error($user_id)) { + echo "Error creating user $email: " . $user_id->get_error_message() . "\n"; + $this->stats['errors']++; + return; + } + + $this->set_user_meta($user_id, $data); + + // Send password reset email + $this->send_welcome_email($user_id); + } + + private function update_user($user, $data) { + if ($this->dry_run) return; + + // Update basic user data + $user_data = [ + 'ID' => $user->ID, + 'first_name' => sanitize_text_field($data['Name'] ?? ''), + 'last_name' => sanitize_text_field($data['Last Name'] ?? ''), + 'display_name' => trim(($data['Name'] ?? '') . ' ' . ($data['Last Name'] ?? '')) + ]; + + wp_update_user($user_data); + + // Ensure user has correct role + $user_obj = new WP_User($user->ID); + $user_obj->set_role('hvac_trainer'); + + $this->set_user_meta($user->ID, $data); + } + + private function set_user_meta($user_id, $data) { + // Personal Information + update_user_meta($user_id, 'personal_accreditation', sanitize_textarea_field($data['Personal Accreditations'] ?? '')); + + // Business Information + update_user_meta($user_id, 'business_name', sanitize_text_field($data['Company Name'] ?? '')); + update_user_meta($user_id, 'business_phone', sanitize_text_field($data['Phone Number'] ?? '')); + update_user_meta($user_id, 'business_email', sanitize_email($data['Work Email'] ?? '')); + update_user_meta($user_id, 'business_website', esc_url_raw($data['Company Website'] ?? '')); + update_user_meta($user_id, 'business_type', $this->map_organization_type($data['Organization Type'] ?? '')); + + // Address Information + update_user_meta($user_id, 'user_country', sanitize_text_field($data['Country'] ?? '')); + update_user_meta($user_id, 'user_state', sanitize_text_field($data['State'] ?? '')); + + // Training Information + $training_audience = $this->map_training_audience($data['Training Target'] ?? ''); + update_user_meta($user_id, 'training_audience', $training_audience); + + // Application Data + update_user_meta($user_id, 'application_details', sanitize_textarea_field($data['Trainer Details'] ?? '')); + + // Profile Image - Map staging URL to production + $profile_image_id = $this->map_profile_image($data['Profile Picture'] ?? ''); + if ($profile_image_id) { + update_user_meta($user_id, 'profile_image_id', $profile_image_id); + } + + // Default settings for imported users + update_user_meta($user_id, 'account_status', 'approved'); // Pre-approved users + update_user_meta($user_id, 'create_venue', 'Yes'); + + // Create organizer and venue profiles + $this->create_organizer_venue($user_id, $data); + } + + private function map_organization_type($org_type) { + $mapping = [ + 'Training Organization' => 'Manufacturer', + 'Service Company' => 'Contractor', + '' => 'Other' + ]; + + return $mapping[$org_type] ?? 'Other'; + } + + private function map_training_audience($target) { + if (empty($target)) { + return ['Anyone']; + } + + // Convert single string to array format expected by system + $mapping = [ + 'Industry professionals' => ['Industry professionals'], + 'Internal staff' => ['Internal staff'], + 'Anyone' => ['Anyone'] + ]; + + return $mapping[$target] ?? ['Anyone']; + } + + private function map_profile_image($staging_url) { + if (empty($staging_url)) { + return null; + } + + // Convert staging URL to production URL pattern + $production_url = str_replace( + 'upskill-staging.measurequick.com', + $_SERVER['HTTP_HOST'], // Use current domain + $staging_url + ); + + // Try to find existing attachment by URL + $attachment_id = attachment_url_to_postid($production_url); + + if (!$attachment_id) { + // Try with staging URL in case it's already in the system + $attachment_id = attachment_url_to_postid($staging_url); + } + + if (!$attachment_id) { + echo "Warning: Could not find attachment for URL: $staging_url\n"; + return null; + } + + return $attachment_id; + } + + private function create_organizer_venue($user_id, $data) { + if ($this->dry_run) return; + + // Check if organizer already exists + $existing_organizer = get_user_meta($user_id, 'hvac_organizer_id', true); + if ($existing_organizer) { + return; // Already has organizer + } + + // Only create if Events Calendar is active + if (!class_exists('Tribe__Events__Main')) { + return; + } + + // Create Events Calendar organizer + $organizer_data = [ + 'post_title' => trim(($data['Name'] ?? '') . ' ' . ($data['Last Name'] ?? '')), + 'post_type' => 'tribe_organizer', + 'post_status' => 'publish', + 'meta_input' => [ + '_OrganizerEmail' => sanitize_email($data['Work Email'] ?? ''), + '_OrganizerPhone' => sanitize_text_field($data['Phone Number'] ?? ''), + '_OrganizerWebsite' => esc_url_raw($data['Company Website'] ?? '') + ] + ]; + + $organizer_id = wp_insert_post($organizer_data); + if ($organizer_id) { + update_user_meta($user_id, 'hvac_organizer_id', $organizer_id); + } + + // Create venue if business name exists + if (!empty($data['Company Name'])) { + $venue_data = [ + 'post_title' => sanitize_text_field($data['Company Name']), + 'post_type' => 'tribe_venue', + 'post_status' => 'publish', + 'meta_input' => [ + '_VenueCountry' => sanitize_text_field($data['Country'] ?? ''), + '_VenueStateProvince' => sanitize_text_field($data['State'] ?? ''), + '_VenueURL' => esc_url_raw($data['Company Website'] ?? '') + ] + ]; + + $venue_id = wp_insert_post($venue_data); + if ($venue_id) { + update_user_meta($user_id, 'hvac_venue_id', $venue_id); + } + } + } + + private function generate_username($email) { + $username = sanitize_user(substr($email, 0, strpos($email, '@'))); + + // Ensure username is unique + $base_username = $username; + $counter = 1; + + while (username_exists($username)) { + $username = $base_username . $counter; + $counter++; + } + + return $username; + } + + private function send_welcome_email($user_id) { + $user = get_user_by('id', $user_id); + if (!$user) return; + + // Send password reset email + $reset_key = get_password_reset_key($user); + if (!is_wp_error($reset_key)) { + $reset_url = network_site_url("wp-login.php?action=rp&key=$reset_key&login=" . rawurlencode($user->user_login), 'login'); + + $subject = 'Welcome to HVAC Upskill Platform'; + $message = "Hello {$user->first_name},\n\n"; + $message .= "Your trainer account has been created on the HVAC Upskill Platform.\n\n"; + $message .= "Please set your password by clicking this link:\n"; + $message .= $reset_url . "\n\n"; + $message .= "After setting your password, you can login at: " . wp_login_url() . "\n\n"; + $message .= "Welcome to the platform!\n"; + + wp_mail($user->user_email, $subject, $message); + } + } + + private function print_stats() { + echo "\n========================================\n"; + echo "Import Complete!\n"; + echo "========================================\n"; + echo "Processed: {$this->stats['processed']}\n"; + echo "Created: {$this->stats['created']}\n"; + echo "Updated: {$this->stats['updated']}\n"; + echo "Skipped: {$this->stats['skipped']}\n"; + echo "Errors: {$this->stats['errors']}\n"; + } +} + +// Only show form if not processing +if (!$_POST || !isset($_POST['action']) || $_POST['action'] !== 'import_csv') { +?> + + + + HVAC Trainer Import + + + +
+

HVAC Trainer CSV Import

+ +
+ ⚠️ IMPORTANT: This tool imports trainer data into your WordPress database. + Always run a dry-run first and backup your database before importing. + Remove this file after import for security! +
+ +
+ ℹ️ Instructions:
+ 1. Download the CSV file: Trainer Registration CSV
+ 2. Upload the CSV file below
+ 3. Run a dry-run first to preview changes
+ 4. Run actual import if dry-run looks good +
+ +
+ + + +
+ + +
+ +
+ +
+ +
+ +
+
+
+ + + \ No newline at end of file diff --git a/scripts/README-import.md b/scripts/README-import.md new file mode 100644 index 00000000..ed2de536 --- /dev/null +++ b/scripts/README-import.md @@ -0,0 +1,118 @@ +# HVAC Trainer CSV Import + +This script imports trainer data from the Formidable Forms CSV export into the current HVAC Upskill platform user database. + +## Usage + +### Download the CSV File First +```bash +# Download the CSV file to your local machine +curl -O "https://upskill-staging.measurequick.com/wp-content/uploads/2025/06/250618120131_user-registration_formidable_entries.csv" +``` + +### Run Import Script +```bash +# Dry run (shows what would be imported without making changes) +php scripts/import-trainer-csv.php 250618120131_user-registration_formidable_entries.csv --dry-run + +# Actual import +php scripts/import-trainer-csv.php 250618120131_user-registration_formidable_entries.csv +``` + +## What the Script Does + +### User Creation +- Creates new WordPress users with `hvac_trainer` role +- Updates existing users and ensures they have `hvac_trainer` role +- Maps CSV fields to current database schema +- Sets users as `approved` (pre-approved from previous system) +- Generates secure random passwords +- Sends welcome emails with password reset links + +### Field Mappings +| CSV Field | Database Field | Notes | +|-----------|----------------|-------| +| Name | first_name | Direct mapping | +| Last Name | last_name | Direct mapping | +| Work Email | user_email, user_login | Primary identifier | +| Country | user_country | Direct mapping | +| State | user_state | Direct mapping | +| Personal Accreditations | personal_accreditation | Direct mapping | +| Company Name | business_name | Direct mapping | +| Company Website | business_website | Direct mapping | +| Phone Number | business_phone | Direct mapping | +| Trainer Details | application_details | Background info | +| Training Target | training_audience | Converted to array | +| Organization Type | business_type | Mapped values | +| Profile Picture | profile_image_id | URL mapping only | + +### Image Handling +- **Does NOT download images** from staging URLs +- Maps staging URLs (`upskill-staging.measurequick.com`) to production URLs +- Searches for existing attachments by URL +- Sets `profile_image_id` if attachment found +- Logs warnings for missing images + +### Additional Features +- Creates Events Calendar organizer profiles +- Creates venue profiles for businesses +- Handles duplicate users (updates existing) +- Comprehensive error handling and logging +- Import statistics summary + +## Important Notes + +### Before Running +1. **Backup your database** - This script creates/modifies user data +2. **Run dry-run first** - Always test with `--dry-run` flag +3. **Check production domain** - Update the domain mapping in `map_profile_image()` method if needed + +### Image URL Mapping +The script expects images to already exist on your current instance but with staging URLs in the CSV. It converts: +``` +upskill-staging.measurequick.com → upskill.measurequick.com +``` + +Update line 169 in the script if your production domain is different. + +### Generated Passwords +- Users get randomly generated secure passwords +- Welcome emails include password reset links +- Users must reset passwords on first login + +### Organizer/Venue Creation +- Creates Events Calendar organizer profiles for each trainer +- Creates venue profiles for trainers with company names +- Links profiles to user accounts via meta fields + +## Troubleshooting + +### Common Issues +1. **Missing images**: Check if images exist on current instance +2. **Duplicate usernames**: Script auto-handles with numbered suffixes +3. **Email sending**: Ensure WordPress mail is configured +4. **WordPress not found**: Run from WordPress root directory + +### Error Messages +- `Could not load WordPress`: Run from correct directory +- `CSV file not found`: Check file path +- `Could not find attachment`: Image doesn't exist on current instance + +## Output Example +``` +Starting import from: trainers.csv +---------------------------------------- +Creating new user: trainer1@example.com +Creating new user: trainer2@example.com +Updating existing user: existing@example.com +Warning: Could not find attachment for URL: https://... + +======================================== +Import Complete! +======================================== +Processed: 25 +Created: 23 +Updated: 2 +Skipped: 0 +Errors: 0 +``` \ No newline at end of file diff --git a/scripts/import-trainer-csv.php b/scripts/import-trainer-csv.php new file mode 100644 index 00000000..07f0379b --- /dev/null +++ b/scripts/import-trainer-csv.php @@ -0,0 +1,351 @@ + 0, + 'created' => 0, + 'updated' => 0, + 'skipped' => 0, + 'errors' => 0 + ]; + + public function __construct($dry_run = false) { + $this->dry_run = $dry_run; + } + + public function import_csv($csv_file) { + if (!file_exists($csv_file)) { + die("CSV file not found: $csv_file\n"); + } + + echo "Starting import from: $csv_file\n"; + if ($this->dry_run) { + echo "DRY RUN MODE - No data will be modified\n"; + } + echo "----------------------------------------\n"; + + $handle = fopen($csv_file, 'r'); + if (!$handle) { + die("Could not open CSV file\n"); + } + + // Read header row + $headers = fgetcsv($handle); + if (!$headers) { + die("Could not read CSV headers\n"); + } + + // Process each row + while (($row = fgetcsv($handle)) !== false) { + $data = array_combine($headers, $row); + $this->process_trainer_record($data); + $this->stats['processed']++; + } + + fclose($handle); + $this->print_stats(); + } + + private function process_trainer_record($data) { + // Skip if no email + if (empty($data['Work Email'])) { + echo "Skipping record with no email\n"; + $this->stats['skipped']++; + return; + } + + $email = sanitize_email($data['Work Email']); + $existing_user = get_user_by('email', $email); + + if ($existing_user) { + echo "Updating existing user: $email\n"; + $this->update_user($existing_user, $data); + $this->stats['updated']++; + } else { + echo "Creating new user: $email\n"; + $this->create_user($data); + $this->stats['created']++; + } + } + + private function create_user($data) { + if ($this->dry_run) return; + + $email = sanitize_email($data['Work Email']); + $username = $this->generate_username($email); + + // Generate random password - user will need to reset + $password = wp_generate_password(12, true, true); + + $user_data = [ + 'user_login' => $username, + 'user_email' => $email, + 'user_pass' => $password, + 'first_name' => sanitize_text_field($data['Name'] ?? ''), + 'last_name' => sanitize_text_field($data['Last Name'] ?? ''), + 'display_name' => trim(($data['Name'] ?? '') . ' ' . ($data['Last Name'] ?? '')), + 'role' => 'hvac_trainer' + ]; + + $user_id = wp_insert_user($user_data); + + if (is_wp_error($user_id)) { + echo "Error creating user $email: " . $user_id->get_error_message() . "\n"; + $this->stats['errors']++; + return; + } + + $this->set_user_meta($user_id, $data); + + // Send password reset email + $this->send_welcome_email($user_id); + } + + private function update_user($user, $data) { + if ($this->dry_run) return; + + // Update basic user data + $user_data = [ + 'ID' => $user->ID, + 'first_name' => sanitize_text_field($data['Name'] ?? ''), + 'last_name' => sanitize_text_field($data['Last Name'] ?? ''), + 'display_name' => trim(($data['Name'] ?? '') . ' ' . ($data['Last Name'] ?? '')) + ]; + + wp_update_user($user_data); + + // Ensure user has correct role + $user_obj = new WP_User($user->ID); + $user_obj->set_role('hvac_trainer'); + + $this->set_user_meta($user->ID, $data); + } + + private function set_user_meta($user_id, $data) { + // Personal Information + update_user_meta($user_id, 'personal_accreditation', sanitize_textarea_field($data['Personal Accreditations'] ?? '')); + + // Business Information + update_user_meta($user_id, 'business_name', sanitize_text_field($data['Company Name'] ?? '')); + update_user_meta($user_id, 'business_phone', sanitize_text_field($data['Phone Number'] ?? '')); + update_user_meta($user_id, 'business_email', sanitize_email($data['Work Email'] ?? '')); + update_user_meta($user_id, 'business_website', esc_url_raw($data['Company Website'] ?? '')); + update_user_meta($user_id, 'business_type', $this->map_organization_type($data['Organization Type'] ?? '')); + + // Address Information + update_user_meta($user_id, 'user_country', sanitize_text_field($data['Country'] ?? '')); + update_user_meta($user_id, 'user_state', sanitize_text_field($data['State'] ?? '')); + + // Training Information + $training_audience = $this->map_training_audience($data['Training Target'] ?? ''); + update_user_meta($user_id, 'training_audience', $training_audience); + + // Application Data + update_user_meta($user_id, 'application_details', sanitize_textarea_field($data['Trainer Details'] ?? '')); + + // Profile Image - Map staging URL to production + $profile_image_id = $this->map_profile_image($data['Profile Picture'] ?? ''); + if ($profile_image_id) { + update_user_meta($user_id, 'profile_image_id', $profile_image_id); + } + + // Default settings for imported users + update_user_meta($user_id, 'account_status', 'approved'); // Pre-approved users + update_user_meta($user_id, 'create_venue', 'Yes'); + + // Create organizer and venue profiles + $this->create_organizer_venue($user_id, $data); + } + + private function map_organization_type($org_type) { + $mapping = [ + 'Training Organization' => 'Manufacturer', + 'Service Company' => 'Contractor', + '' => 'Other' + ]; + + return $mapping[$org_type] ?? 'Other'; + } + + private function map_training_audience($target) { + if (empty($target)) { + return ['Anyone']; + } + + // Convert single string to array format expected by system + $mapping = [ + 'Industry professionals' => ['Industry professionals'], + 'Internal staff' => ['Internal staff'], + 'Anyone' => ['Anyone'] + ]; + + return $mapping[$target] ?? ['Anyone']; + } + + private function map_profile_image($staging_url) { + if (empty($staging_url)) { + return null; + } + + // Convert staging URL to production URL pattern + $production_url = str_replace( + 'upskill-staging.measurequick.com', + 'upskill.measurequick.com', // Adjust this to your production domain + $staging_url + ); + + // Try to find existing attachment by URL + $attachment_id = attachment_url_to_postid($production_url); + + if (!$attachment_id) { + // Try with staging URL in case it's already in the system + $attachment_id = attachment_url_to_postid($staging_url); + } + + if (!$attachment_id) { + echo "Warning: Could not find attachment for URL: $staging_url\n"; + return null; + } + + return $attachment_id; + } + + private function create_organizer_venue($user_id, $data) { + if ($this->dry_run) return; + + // Check if organizer already exists + $existing_organizer = get_user_meta($user_id, 'hvac_organizer_id', true); + if ($existing_organizer) { + return; // Already has organizer + } + + // Create Events Calendar organizer + $organizer_data = [ + 'post_title' => trim(($data['Name'] ?? '') . ' ' . ($data['Last Name'] ?? '')), + 'post_type' => 'tribe_organizer', + 'post_status' => 'publish', + 'meta_input' => [ + '_OrganizerEmail' => sanitize_email($data['Work Email'] ?? ''), + '_OrganizerPhone' => sanitize_text_field($data['Phone Number'] ?? ''), + '_OrganizerWebsite' => esc_url_raw($data['Company Website'] ?? '') + ] + ]; + + $organizer_id = wp_insert_post($organizer_data); + if ($organizer_id) { + update_user_meta($user_id, 'hvac_organizer_id', $organizer_id); + } + + // Create venue if business name exists + if (!empty($data['Company Name'])) { + $venue_data = [ + 'post_title' => sanitize_text_field($data['Company Name']), + 'post_type' => 'tribe_venue', + 'post_status' => 'publish', + 'meta_input' => [ + '_VenueCountry' => sanitize_text_field($data['Country'] ?? ''), + '_VenueStateProvince' => sanitize_text_field($data['State'] ?? ''), + '_VenueURL' => esc_url_raw($data['Company Website'] ?? '') + ] + ]; + + $venue_id = wp_insert_post($venue_data); + if ($venue_id) { + update_user_meta($user_id, 'hvac_venue_id', $venue_id); + } + } + } + + private function generate_username($email) { + $username = sanitize_user(substr($email, 0, strpos($email, '@'))); + + // Ensure username is unique + $base_username = $username; + $counter = 1; + + while (username_exists($username)) { + $username = $base_username . $counter; + $counter++; + } + + return $username; + } + + private function send_welcome_email($user_id) { + $user = get_user_by('id', $user_id); + if (!$user) return; + + // Send password reset email + $reset_key = get_password_reset_key($user); + if (!is_wp_error($reset_key)) { + $reset_url = network_site_url("wp-login.php?action=rp&key=$reset_key&login=" . rawurlencode($user->user_login), 'login'); + + $subject = 'Welcome to HVAC Upskill Platform'; + $message = "Hello {$user->first_name},\n\n"; + $message .= "Your trainer account has been created on the HVAC Upskill Platform.\n\n"; + $message .= "Please set your password by clicking this link:\n"; + $message .= $reset_url . "\n\n"; + $message .= "After setting your password, you can login at: " . wp_login_url() . "\n\n"; + $message .= "Welcome to the platform!\n"; + + wp_mail($user->user_email, $subject, $message); + } + } + + private function print_stats() { + echo "\n========================================\n"; + echo "Import Complete!\n"; + echo "========================================\n"; + echo "Processed: {$this->stats['processed']}\n"; + echo "Created: {$this->stats['created']}\n"; + echo "Updated: {$this->stats['updated']}\n"; + echo "Skipped: {$this->stats['skipped']}\n"; + echo "Errors: {$this->stats['errors']}\n"; + } +} + +// Command line execution +if (php_sapi_name() === 'cli') { + $csv_file = $argv[1] ?? ''; + $dry_run = in_array('--dry-run', $argv); + + if (empty($csv_file)) { + echo "Usage: php import-trainer-csv.php [--dry-run]\n"; + echo "Example: php import-trainer-csv.php /path/to/trainers.csv\n"; + echo "Options:\n"; + echo " --dry-run Show what would be imported without making changes\n"; + exit(1); + } + + $importer = new TrainerCSVImporter($dry_run); + $importer->import_csv($csv_file); +} \ No newline at end of file