validate_registration($submitted_data); $errors = array_merge($errors, $validation_errors); // Combine file errors and validation errors // --- Process if No Errors --- if (empty($errors)) { error_log('[HVAC REG DEBUG] Validation passed. Attempting account creation...'); $user_id = $this->create_trainer_account($submitted_data, $profile_image_data); if (is_wp_error($user_id)) { $errors['account'] = $user_id->get_error_message(); error_log('[HVAC REG DEBUG] Account creation WP_Error: ' . $user_id->get_error_message()); } elseif ($user_id) { error_log('[HVAC REG DEBUG] Account creation SUCCESS. User ID: ' . $user_id); error_log('[HVAC REG DEBUG] Sending admin notification...'); $this->send_admin_notification($user_id, $submitted_data); // $this->send_user_pending_notification($user_id); // TODO // Set flag for success message display instead of redirecting immediately $registration_success = true; error_log('[HVAC REG DEBUG] Registration success flag set.'); // Clear submitted data on success so form doesn't repopulate $submitted_data = []; // Optionally redirect here if preferred, but displaying message avoids issues // wp_safe_redirect(home_url('/registration-pending/')); // exit; } else { $errors['account'] = 'An unknown error occurred during registration. Please contact support.'; error_log('[HVAC REG DEBUG] Account creation failed silently (returned false/0).'); } } else { error_log('[HVAC REG DEBUG] Validation errors found: ' . print_r($errors, true)); } } } // --- End Handle POST Submission --- // --- Render Form --- ob_start(); // Display general/account errors above the form if (isset($errors['nonce']) || isset($errors['account'])) { echo '
'; if (isset($errors['nonce'])) echo '

' . esc_html($errors['nonce']) . '

'; if (isset($errors['account'])) echo '

' . esc_html($errors['account']) . '

'; echo '
'; } // Display success message OR the form if ($registration_success) { echo '
'; // Add a class for styling echo '

Thank you for registering!

'; echo '

Your account is currently pending approval. You will receive an email once your account is approved.

'; echo '
'; } else { // Display the form HTML, passing errors and submitted data $this->display_form_html($submitted_data, $errors); } return ob_get_clean(); // --- End Render Form --- } /** * Displays the actual form HTML. * Receives submitted data and errors as arguments. */ private function display_form_html($data = [], $errors = []) { // This method remains largely the same as before, // ensuring it uses the passed $data and $errors arrays. ?>

HVAC Trainer Registration

By submitting this form, you will be creating an account in the Upskill HVAC online event system. Once approved, you will be able to login to the trainer portal to manage your profile and event listings.

Account Information

' . esc_html($errors['user_email']) . '

'; ?>
Password must be at least 8 characters long, and include at least one uppercase letter, one lowercase letter, and one number. ' . esc_html($errors['user_pass']) . '

'; ?>
' . esc_html($errors['confirm_password']) . '

'; ?>

Personal Information

' . esc_html($errors['first_name']) . '

'; ?>
' . esc_html($errors['last_name']) . '

'; ?>
This will be the name displayed to other users on the site. ' . esc_html($errors['display_name']) . '

'; ?>
' . esc_html($errors['user_url']) . '

'; ?>
' . esc_html($errors['user_linkedin']) . '

'; ?>
Enter your abbreviated accreditations separated by commas.
A short bio about yourself. This will be displayed on your profile page. ' . esc_html($errors['description']) . '

'; ?>
Please attach a .jpg, .png, or .gif image. By attaching an image to use as your profile picture you assert that you have rights to use the image and it is not illegal, pornographic or violent in any way. This will be displayed on your profile page. ' . esc_html($errors['profile_image']) . '

'; ?>

Business Information

' . esc_html($errors['business_name']) . '

'; ?>
' . esc_html($errors['business_phone']) . '

'; ?>
' . esc_html($errors['business_email']) . '

'; ?>
' . esc_html($errors['business_website']) . '

'; ?>
' . esc_html($errors['business_description']) . '

'; ?>

Address Information

' . esc_html($errors['user_country']) . '

'; ?>
' . esc_html($errors['user_state']) . '

'; ?> ' . esc_html($errors['user_state_other']) . '

'; ?>
' . esc_html($errors['user_city']) . '

'; ?>
' . esc_html($errors['user_zip']) . '

'; ?>

Training Venue

Do you want to create a Training Venue Profile for your business to use when listing your training events? If yes, we will use the address provided above.
' . esc_html($errors['create_venue']) . '

'; ?>

Training Information

What type of business are you?
' . esc_html($type) . ''; } ?>
' . esc_html($errors['business_type']) . '

'; ?>
Who do you offer training to? (Select all that apply)
"Anyone (open to the public)", "Industry professionals" => "Industry professionals", "Internal staff" => "Internal staff in my company", "Registered students" => "Registered students/members of my org/institution" ]; $selected_audience = $data['training_audience'] ?? []; if (!is_array($selected_audience)) $selected_audience = []; foreach ($audience_options as $value => $label) { echo ''; } ?>
' . esc_html($errors['training_audience']) . '

'; ?>
What formats of training do you offer?
' . esc_html($format) . ''; } ?>
' . esc_html($errors['training_formats']) . '

'; ?>
Where are you willing to provide training? (Select all that apply)
' . esc_html($location) . ''; } ?>
' . esc_html($errors['training_locations']) . '

'; ?>
What training resources do you have access to? (Select all that apply)
' . esc_html($resource) . ''; } ?>
' . esc_html($errors['training_resources']) . '

'; ?>

Application Details

Please explain why you want to create a training account on Upskill HVAC. ' . esc_html($errors['application_details']) . '

'; ?>
It's our goal to help you generate revenue through your training. How much revenue are you looking to generate annually though your training on Upskill HVAC?
post_content, 'hvac_trainer_registration')) { wp_enqueue_style( 'hvac-registration-style', HVAC_CE_PLUGIN_URL . 'assets/css/hvac-registration.css', // Ensure this CSS file exists and is styled array(), HVAC_CE_VERSION ); wp_enqueue_script( 'hvac-registration-js', HVAC_CE_PLUGIN_URL . 'assets/js/hvac-registration.js', // Ensure this JS file exists array('jquery'), HVAC_CE_VERSION, true ); // Localize script to pass states/provinces data and AJAX URL wp_localize_script('hvac-registration-js', 'hvacRegistrationData', array( 'ajax_url' => admin_url('admin-ajax.php'), // Needed if JS fetches states/provinces via AJAX 'states' => $this->get_us_states(), // Pass US states 'provinces' => $this->get_canadian_provinces(), // Pass CA provinces // Pass other country data if needed, or handle via AJAX )); } } // Removed the separate process_registration method /** * Handle profile image upload after user is created. */ private function handle_profile_image_upload($user_id, $file_data) { if (!$user_id || empty($file_data) || !isset($file_data['tmp_name']) || $file_data['error'] !== UPLOAD_ERR_OK) { return false; // No valid user or file } // These files need to be included as dependencies when on the front-end. require_once(ABSPATH . 'wp-admin/includes/image.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); // Pass the file array directly to media_handle_sideload $attachment_id = media_handle_sideload($file_data, 0); // 0 means don't attach to a post if (is_wp_error($attachment_id)) { // Handle upload error error_log('Profile image upload error for user ' . $user_id . ': ' . $attachment_id->get_error_message()); // Optionally add this error to be displayed to the user via transient? return false; } else { // Store the attachment ID as user meta update_user_meta($user_id, 'profile_image_id', $attachment_id); return $attachment_id; } } /** * Validate registration form data */ public function validate_registration($data) { $errors = array(); // Required field validation $required_fields = [ 'user_email', 'user_pass', 'confirm_password', 'first_name', 'last_name', 'display_name', 'description', 'business_name', 'business_phone', 'business_email', 'business_description', 'user_country', 'user_state', 'user_city', 'user_zip', 'create_venue', 'business_type', 'application_details' ]; // Required checkbox groups $required_checkboxes = [ 'training_audience', 'training_formats', 'training_locations', 'training_resources' ]; foreach ($required_fields as $field) { // Special handling for state dropdown if 'Other' is selected if ($field === 'user_state' && isset($data[$field]) && $data[$field] === 'Other') { if (empty(trim($data['user_state_other']))) { $errors['user_state_other'] = 'Please enter your state/province.'; } continue; // Skip the main 'user_state' empty check if 'Other' is selected } if (empty(trim($data[$field]))) { $errors[$field] = 'This field is required.'; } } foreach ($required_checkboxes as $field) { // Checkboxes send value only if checked, check if the key exists and is an array with items if (empty($data[$field]) || !is_array($data[$field]) || count($data[$field]) === 0) { $errors[$field] = 'Please select at least one option.'; } } // Email validation if (!empty($data['user_email']) && !is_email($data['user_email'])) { $errors['user_email'] = 'Please enter a valid email address.'; } elseif (email_exists($data['user_email'])) { $errors['user_email'] = 'This email address is already registered.'; } // Check business email format, but allow it to be the same as user email if (!empty($data['business_email']) && !is_email($data['business_email'])) { $errors['business_email'] = 'Please enter a valid business email address.'; } // Password validation if (!empty($data['user_pass'])) { if (strlen($data['user_pass']) < 8 || !preg_match('/[A-Z]/', $data['user_pass']) || !preg_match('/[a-z]/', $data['user_pass']) || !preg_match('/[0-9]/', $data['user_pass'])) { $errors['user_pass'] = 'Password must be at least 8 characters with uppercase, lowercase and numbers.'; } elseif (empty($data['confirm_password'])) { $errors['confirm_password'] = 'Please confirm your password.'; } elseif ($data['user_pass'] !== $data['confirm_password']) { $errors['confirm_password'] = 'Passwords do not match.'; } } else { // This case should be caught by the required check, but added for robustness if (!isset($errors['user_pass'])) $errors['user_pass'] = 'Password is required.'; } // URL validation (optional fields) if (!empty($data['user_url']) && !filter_var($data['user_url'], FILTER_VALIDATE_URL)) { $errors['user_url'] = 'Please enter a valid website URL (e.g., https://example.com).'; } if (!empty($data['user_linkedin']) && !filter_var($data['user_linkedin'], FILTER_VALIDATE_URL)) { $errors['user_linkedin'] = 'Please enter a valid LinkedIn Profile URL.'; } if (!empty($data['business_website']) && !filter_var($data['business_website'], FILTER_VALIDATE_URL)) { $errors['business_website'] = 'Please enter a valid business website URL.'; } // Conditional State/Province validation if (isset($data['user_country']) && $data['user_country'] !== 'United States' && $data['user_country'] !== 'Canada') { // If country is not US/CA if (isset($data['user_state']) && $data['user_state'] !== 'Other') { $errors['user_state'] = 'Please select "Other" for state/province if outside US/Canada.'; } elseif (empty(trim($data['user_state_other']))) { // This case is handled by the required check above if user_state is 'Other' // $errors['user_state_other'] = 'Please enter your state/province.'; } } elseif (isset($data['user_country']) && ($data['user_country'] === 'United States' || $data['user_country'] === 'Canada')) { // If country IS US/CA if (empty($data['user_state'])) { $errors['user_state'] = 'Please select your state/province.'; } elseif ($data['user_state'] === 'Other') { $errors['user_state'] = 'Please select your state/province from the list, not "Other".'; } } // File upload validation is handled before this function is called return $errors; } /** * Create HVAC trainer account */ private function create_trainer_account($data, $profile_image_data = null) { // Generate username from email - use user_email now $email_parts = explode('@', $data['user_email']); $username_base = sanitize_user($email_parts[0], true); // Add random suffix to further ensure uniqueness if base is common $username_base = preg_replace('/[^a-zA-Z0-9]/', '', $username_base); // Clean base username if (strlen($username_base) > 50) { // Limit base length $username_base = substr($username_base, 0, 50); } $username = $username_base; $counter = 1; // Ensure username is unique while (username_exists($username)) { $username = $username_base . $counter++; if ($counter > 100) { // Safety break return new WP_Error('registration_failed_username', 'Could not generate a unique username. Please try a different email address.'); } } // Create user with pending status initially $user_data = array( 'user_login' => $username, 'user_pass' => $data['user_pass'], 'user_email' => $data['user_email'], 'first_name' => sanitize_text_field($data['first_name']), 'last_name' => sanitize_text_field($data['last_name']), 'display_name' => sanitize_text_field($data['display_name']), 'description' => wp_kses_post($data['description']), // Allow some HTML in bio 'user_url' => esc_url_raw($data['user_url'] ?? ''), 'role' => 'pending_hvac_trainer' // Assign custom pending role defined in class-hvac-roles.php ); $user_id = wp_insert_user($user_data); if (is_wp_error($user_id)) { error_log('HVAC Reg Error wp_insert_user: ' . $user_id->get_error_message()); return $user_id; // Return WP_Error object } // Save custom user meta fields update_user_meta($user_id, 'user_linkedin', esc_url_raw($data['user_linkedin'] ?? '')); update_user_meta($user_id, 'personal_accreditation', sanitize_text_field($data['personal_accreditation'] ?? '')); update_user_meta($user_id, 'business_name', sanitize_text_field($data['business_name'])); update_user_meta($user_id, 'business_phone', sanitize_text_field($data['business_phone'])); update_user_meta($user_id, 'business_email', sanitize_email($data['business_email'])); // Use business email meta update_user_meta($user_id, 'business_website', esc_url_raw($data['business_website'] ?? '')); update_user_meta($user_id, 'business_description', wp_kses_post($data['business_description'])); // Allow some HTML update_user_meta($user_id, 'user_country', sanitize_text_field($data['user_country'])); // Save state/province correctly based on 'Other' if ($data['user_state'] === 'Other') { update_user_meta($user_id, 'user_state', sanitize_text_field($data['user_state_other'])); } else { update_user_meta($user_id, 'user_state', sanitize_text_field($data['user_state'])); } update_user_meta($user_id, 'user_city', sanitize_text_field($data['user_city'])); update_user_meta($user_id, 'user_zip', sanitize_text_field($data['user_zip'])); update_user_meta($user_id, 'create_venue', sanitize_text_field($data['create_venue'])); update_user_meta($user_id, 'business_type', sanitize_text_field($data['business_type'])); update_user_meta($user_id, 'training_audience', array_map('sanitize_text_field', $data['training_audience'] ?? [])); update_user_meta($user_id, 'training_formats', array_map('sanitize_text_field', $data['training_formats'] ?? [])); update_user_meta($user_id, 'training_locations', array_map('sanitize_text_field', $data['training_locations'] ?? [])); update_user_meta($user_id, 'training_resources', array_map('sanitize_text_field', $data['training_resources'] ?? [])); update_user_meta($user_id, 'application_details', wp_kses_post($data['application_details'])); // Allow some HTML if (isset($data['annual_revenue_target']) && is_numeric($data['annual_revenue_target'])) { update_user_meta($user_id, 'annual_revenue_target', intval($data['annual_revenue_target'])); } else { delete_user_meta($user_id, 'annual_revenue_target'); // Remove if empty or non-numeric } // Handle profile image upload if ($profile_image_data) { $this->handle_profile_image_upload($user_id, $profile_image_data); } // Create Events Calendar organizer profile (using business details) $this->create_organizer_profile($user_id, $data); // Create venue if requested if (isset($data['create_venue']) && $data['create_venue'] === 'Yes') { $this->create_training_venue($user_id, $data); } return $user_id; // Return the user ID on success } /** * Create organizer profile in The Events Calendar */ private function create_organizer_profile($user_id, $data) { if (!function_exists('tribe_create_organizer')) { error_log('HVAC Reg Error: tribe_create_organizer function does not exist.'); return false; } $organizer_data = array( 'Organizer' => sanitize_text_field($data['business_name']), 'Phone' => sanitize_text_field($data['business_phone']), 'Email' => sanitize_email($data['business_email']), 'Website' => esc_url_raw($data['business_website'] ?? ''), 'Description' => wp_kses_post($data['business_description']), // Add description 'post_status' => 'publish', // Ensure organizer is published 'post_author' => $user_id // Assign user as author ); $organizer_id = tribe_create_organizer($organizer_data); if (is_wp_error($organizer_id)) { error_log('HVAC Reg Error creating organizer: ' . $organizer_id->get_error_message()); return false; } elseif ($organizer_id) { // Associate organizer with user update_user_meta($user_id, '_hvac_organizer_id', $organizer_id); // TEC Community Events might use this meta key to link user and organizer CPT // update_post_meta($organizer_id, '_tribe_organizer_user_id', $user_id); // Check if TEC CE uses this return true; } error_log('HVAC Reg Error: tribe_create_organizer returned non-error false/0.'); return false; } /** * Create a training venue in The Events Calendar */ private function create_training_venue($user_id, $data) { if (!function_exists('tribe_create_venue')) { error_log('HVAC Reg Error: tribe_create_venue function does not exist.'); return false; } $state = ($data['user_state'] === 'Other') ? sanitize_text_field($data['user_state_other']) : sanitize_text_field($data['user_state']); $venue_data = array( 'Venue' => sanitize_text_field($data['business_name']), // Use business name for venue name // 'Address' => '', // TEC often uses individual fields below 'City' => sanitize_text_field($data['user_city']), 'Province' => $state, // Use Province field for TEC 'State' => $state, // Also set State for compatibility 'Zip' => sanitize_text_field($data['user_zip']), 'Country' => sanitize_text_field($data['user_country']), 'Phone' => sanitize_text_field($data['business_phone']), // Add phone to venue 'ShowMap' => true, 'ShowMapLink' => true, 'post_status' => 'publish', // Ensure venue is published 'post_author' => $user_id // Assign user as author ); $venue_id = tribe_create_venue($venue_data); if (is_wp_error($venue_id)) { error_log('HVAC Reg Error creating venue: ' . $venue_id->get_error_message()); return false; } elseif ($venue_id) { // Associate venue with user update_user_meta($user_id, '_hvac_training_venue_id', $venue_id); // TEC Community Events might use this meta key // update_post_meta($venue_id, '_tribe_venue_user_id', $user_id); // Check if TEC CE uses this return true; } error_log('HVAC Reg Error: tribe_create_venue returned non-error false/0.'); return false; } /** * Send admin notification about new trainer registration (pending approval) */ private function send_admin_notification($user_id, $data) { // Use settings or default admin email $options = get_option('hvac_ce_options'); // Assuming settings are stored here $emails_str = $options['notification_emails'] ?? get_option('admin_email'); $emails = array_filter(array_map('trim', explode(',', $emails_str)), 'is_email'); if (empty($emails)) { error_log('HVAC Reg Error: No valid admin notification emails configured.'); return false; } $user = get_userdata($user_id); if (!$user) return false; $subject = sprintf(__('New HVAC Trainer Registration Pending Approval: %s', 'hvac-community-events'), $user->display_name); $message = __('A new HVAC trainer has registered and requires approval:', 'hvac-community-events') . "\n\n"; $message .= __('Name:', 'hvac-community-events') . ' ' . $user->first_name . ' ' . $user->last_name . "\n"; $message .= __('Email:', 'hvac-community-events') . ' ' . $user->user_email . "\n"; $message .= __('Business:', 'hvac-community-events') . ' ' . ($data['business_name'] ?? 'N/A') . "\n"; $message .= __('Phone:', 'hvac-community-events') . ' ' . ($data['business_phone'] ?? 'N/A') . "\n"; $state_display = ($data['user_state'] === 'Other') ? ($data['user_state_other'] ?? 'N/A') : ($data['user_state'] ?? 'N/A'); $message .= __('Location:', 'hvac-community-events') . ' ' . ($data['user_city'] ?? 'N/A') . ', ' . $state_display . "\n\n"; $message .= __('Business Type:', 'hvac-community-events') . ' ' . ($data['business_type'] ?? 'N/A') . "\n"; $message .= __('Training Audience:', 'hvac-community-events') . ' ' . implode(', ', $data['training_audience'] ?? ['N/A']) . "\n"; $message .= __('Application Details:', 'hvac-community-events') . "\n" . ($data['application_details'] ?? 'N/A') . "\n\n"; // Add link to approve/deny user (might need a custom admin page later) $message .= __('View/Approve User Profile:', 'hvac-community-events') . ' ' . admin_url('user-edit.php?user_id=' . $user_id) . "\n"; // Or link to Users list filtered by pending role $message .= __('Approve/Deny Users:', 'hvac-community-events') . ' ' . admin_url('users.php?role=pending_hvac_trainer'); $headers = array('Content-Type: text/plain; charset=UTF-8'); foreach ($emails as $email) { wp_mail($email, $subject, $message, $headers); } return true; } // --- Helper methods for dropdowns --- private function get_country_list() { // Basic list, consider a library for a full list or WP options // Using Name as value to match requirements doc example, but using Code (US, CA) might be better for JS return [ 'United States' => 'United States', 'Canada' => 'Canada', 'Afghanistan' => 'Afghanistan', 'Albania' => 'Albania', 'Algeria' => 'Algeria', 'Mexico' => 'Mexico', 'United Kingdom' => 'United Kingdom', 'Germany' => 'Germany', 'Australia' => 'Australia', // Add many more countries as needed ]; } private function get_us_states() { // Key/Value should match what JS expects/populates // Using full name as value based on E2E test attempt `selectOption({ label: 'California' })` return [ 'Alabama' => 'Alabama', 'Alaska' => 'Alaska', 'Arizona' => 'Arizona', 'Arkansas' => 'Arkansas', 'California' => 'California', 'Colorado' => 'Colorado', 'Connecticut' => 'Connecticut', 'Delaware' => 'Delaware', 'District Of Columbia' => 'District Of Columbia', 'Florida' => 'Florida', 'Georgia' => 'Georgia', 'Hawaii' => 'Hawaii', 'Idaho' => 'Idaho', 'Illinois' => 'Illinois', 'Indiana' => 'Indiana', 'Iowa' => 'Iowa', 'Kansas' => 'Kansas', 'Kentucky' => 'Kentucky', 'Louisiana' => 'Louisiana', 'Maine' => 'Maine', 'Maryland' => 'Maryland', 'Massachusetts' => 'Massachusetts', 'Michigan' => 'Michigan', 'Minnesota' => 'Minnesota', 'Mississippi' => 'Mississippi', 'Missouri' => 'Missouri', 'Montana' => 'Montana', 'Nebraska' => 'Nebraska', 'Nevada' => 'Nevada', 'New Hampshire' => 'New Hampshire', 'New Jersey' => 'New Jersey', 'New Mexico' => 'New Mexico', 'New York' => 'New York', 'North Carolina' => 'North Carolina', 'North Dakota' => 'North Dakota', 'Ohio' => 'Ohio', 'Oklahoma' => 'Oklahoma', 'Oregon' => 'Oregon', 'Pennsylvania' => 'Pennsylvania', 'Rhode Island' => 'Rhode Island', 'South Carolina' => 'South Carolina', 'South Dakota' => 'South Dakota', 'Tennessee' => 'Tennessee', 'Texas' => 'Texas', 'Utah' => 'Utah', 'Vermont' => 'Vermont', 'Virginia' => 'Virginia', 'Washington' => 'Washington', 'West Virginia' => 'West Virginia', 'Wisconsin' => 'Wisconsin', 'Wyoming' => 'Wyoming' ]; } private function get_canadian_provinces() { // Key/Value should match what JS expects/populates // Using full name as value based on E2E test attempt return [ 'Alberta' => 'Alberta', 'British Columbia' => 'British Columbia', 'Manitoba' => 'Manitoba', 'New Brunswick' => 'New Brunswick', 'Newfoundland and Labrador' => 'Newfoundland and Labrador', 'Nova Scotia' => 'Nova Scotia', 'Ontario' => 'Ontario', 'Prince Edward Island' => 'Prince Edward Island', 'Quebec' => 'Quebec', 'Saskatchewan' => 'Saskatchewan', 'Northwest Territories' => 'Northwest Territories', 'Nunavut' => 'Nunavut', 'Yukon' => 'Yukon' ]; } }