- Changed headquarters country and state fields from text inputs to dropdown selections - Added dynamic state/province loading based on selected country (US/Canada) - Added 'Other' option for non-US/Canada countries with text input fallback - Properly handle org_headquarters_state_other field in backend processing - JavaScript handlers for dynamic country/state interaction - Consistent with Training Venue Information dropdown behavior Co-Authored-By: Ben Reed <ben@tealmaker.com>
1728 lines
No EOL
99 KiB
PHP
1728 lines
No EOL
99 KiB
PHP
<?php
|
|
/**
|
|
* Handles the HVAC trainer registration functionality using admin-post for processing.
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class HVAC_Registration {
|
|
|
|
const TRANSIENT_PREFIX = 'hvac_reg_'; // Prefix for transients
|
|
const PROFILE_TRANSIENT_PREFIX = 'hvac_prof_'; // Prefix for profile transients
|
|
const REGISTRATION_ACTION = 'hvac_register'; // Action name for admin-post
|
|
const PROFILE_UPDATE_ACTION = 'hvac_update_profile'; // Action name for profile update
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
// Register shortcode for registration form
|
|
add_shortcode('hvac_trainer_registration', array($this, 'render_registration_form'));
|
|
|
|
// Register shortcode for edit profile form
|
|
add_shortcode('hvac_edit_profile', array($this, 'render_edit_profile_form'));
|
|
|
|
// Enqueue styles and scripts
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
|
|
|
// Hook the processing function to admin-post actions
|
|
add_action('admin_post_nopriv_' . self::REGISTRATION_ACTION, array($this, 'process_registration_submission'));
|
|
add_action('admin_post_' . self::REGISTRATION_ACTION, array($this, 'process_registration_submission')); // Allow logged-in users? Maybe not needed but harmless.
|
|
|
|
// Hook the profile update function to admin-post actions (only for logged in users)
|
|
add_action('admin_post_' . self::PROFILE_UPDATE_ACTION, array($this, 'process_profile_update'));
|
|
}
|
|
|
|
/**
|
|
* Renders the registration form. Retrieves errors/data from transient if redirected back.
|
|
*/
|
|
public function render_registration_form() {
|
|
$errors = [];
|
|
$submitted_data = [];
|
|
$transient_key = null;
|
|
|
|
// Check if redirected back with errors
|
|
if (isset($_GET['reg_error']) && $_GET['reg_error'] === '1' && isset($_GET['tid'])) {
|
|
|
|
$transient_key = self::TRANSIENT_PREFIX . sanitize_key($_GET['tid']);
|
|
$transient_data = get_transient($transient_key);
|
|
|
|
if ($transient_data && is_array($transient_data)) {
|
|
$errors = $transient_data['errors'] ?? [];
|
|
|
|
$submitted_data = $transient_data['data'] ?? [];
|
|
// Delete the transient immediately after retrieving
|
|
delete_transient($transient_key);
|
|
|
|
} else {
|
|
// Transient expired or invalid, show a generic error
|
|
$errors['transient'] = 'There was a problem retrieving your submission details. Please try again.';
|
|
|
|
}
|
|
}
|
|
|
|
// --- Render Form ---
|
|
ob_start();
|
|
|
|
// Display general errors (transient, nonce, account creation)
|
|
// These errors are now set in the transient and retrieved above
|
|
if (!empty($errors['transient']) || !empty($errors['nonce']) || !empty($errors['account'])) {
|
|
echo '<div class="hvac-errors">';
|
|
if (!empty($errors['transient'])) echo '<p class="error">' . esc_html($errors['transient']) . '</p>';
|
|
// Nonce errors should ideally be caught in admin-post, but display if somehow set
|
|
if (!empty($errors['nonce'])) echo '<p class="error">' . esc_html($errors['nonce']) . '</p>';
|
|
if (!empty($errors['account'])) echo '<p class="error">' . esc_html($errors['account']) . '</p>';
|
|
echo '</div>';
|
|
}
|
|
|
|
// Display the form HTML, passing retrieved errors and submitted data
|
|
// No success message here anymore, success leads to redirect
|
|
$this->display_form_html($submitted_data, $errors);
|
|
|
|
return ob_get_clean();
|
|
// --- End Render Form ---
|
|
}
|
|
|
|
/**
|
|
* Processes the registration form submission via admin-post.
|
|
* Handles validation, user creation, notifications, and redirects.
|
|
*/
|
|
public function process_registration_submission() {
|
|
|
|
$errors = [];
|
|
$submitted_data = $_POST; // Capture submitted data early for potential repopulation
|
|
$registration_page_url = home_url('/trainer/registration/'); // Updated to hierarchical URL
|
|
|
|
// --- Verify Nonce ---
|
|
if (!isset($_POST['hvac_registration_nonce']) || !wp_verify_nonce($_POST['hvac_registration_nonce'], 'hvac_trainer_registration')) {
|
|
$errors['nonce'] = 'Security check failed. Please try submitting the form again.';
|
|
|
|
$this->redirect_with_errors($errors, $submitted_data, $registration_page_url);
|
|
// No need for return/exit here, redirect_with_errors exits.
|
|
}
|
|
|
|
// --- File Upload Handling ---
|
|
$profile_image_data = null;
|
|
if (isset($_FILES['profile_image']) && $_FILES['profile_image']['error'] !== UPLOAD_ERR_NO_FILE) {
|
|
if ($_FILES['profile_image']['error'] === UPLOAD_ERR_OK) {
|
|
// Check if it's actually an uploaded file
|
|
if (!is_uploaded_file($_FILES['profile_image']['tmp_name'])) {
|
|
$errors['profile_image'] = 'File upload error (invalid temp file).';
|
|
} else {
|
|
// Security: Check file size (max 5MB for profile images)
|
|
$max_file_size = 5 * 1024 * 1024; // 5MB
|
|
if ($_FILES['profile_image']['size'] > $max_file_size) {
|
|
$errors['profile_image'] = 'Profile image is too large. Maximum size is 5MB.';
|
|
} else {
|
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
|
|
// Use wp_check_filetype on the actual file name for extension check
|
|
// Use finfo_file or getimagesize on tmp_name for actual MIME type check for better security
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$mime_type = finfo_file($finfo, $_FILES['profile_image']['tmp_name']);
|
|
finfo_close($finfo);
|
|
|
|
if (!in_array($mime_type, $allowed_types)) {
|
|
$errors['profile_image'] = 'Invalid file type detected (' . esc_html($mime_type) . '). Please upload a JPG, PNG, or GIF.';
|
|
} else {
|
|
// Additional security: Verify image dimensions using getimagesize
|
|
$image_info = getimagesize($_FILES['profile_image']['tmp_name']);
|
|
if ($image_info === false) {
|
|
$errors['profile_image'] = 'Invalid image file detected.';
|
|
} else {
|
|
$profile_image_data = $_FILES['profile_image']; // Store the whole $_FILES entry
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$errors['profile_image'] = 'There was an error uploading the profile image. Code: ' . $_FILES['profile_image']['error'];
|
|
}
|
|
}
|
|
|
|
// Handle Organization Logo Upload
|
|
$org_logo_data = null;
|
|
if (isset($_FILES['org_logo']) && $_FILES['org_logo']['error'] !== UPLOAD_ERR_NO_FILE) {
|
|
if ($_FILES['org_logo']['error'] === UPLOAD_ERR_OK) {
|
|
if (!is_uploaded_file($_FILES['org_logo']['tmp_name'])) {
|
|
$errors['org_logo'] = 'File upload error (invalid temp file).';
|
|
} else {
|
|
// Security: Check file size (max 2MB for logos)
|
|
$max_file_size = 2 * 1024 * 1024; // 2MB
|
|
if ($_FILES['org_logo']['size'] > $max_file_size) {
|
|
$errors['org_logo'] = 'Organization logo is too large. Maximum size is 2MB.';
|
|
} else {
|
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$mime_type = finfo_file($finfo, $_FILES['org_logo']['tmp_name']);
|
|
finfo_close($finfo);
|
|
|
|
if (!in_array($mime_type, $allowed_types)) {
|
|
$errors['org_logo'] = 'Invalid file type detected (' . esc_html($mime_type) . '). Please upload a JPG, PNG, or GIF.';
|
|
} else {
|
|
// Additional security: Verify image dimensions using getimagesize
|
|
$image_info = getimagesize($_FILES['org_logo']['tmp_name']);
|
|
if ($image_info === false) {
|
|
$errors['org_logo'] = 'Invalid image file detected.';
|
|
} else {
|
|
$org_logo_data = $_FILES['org_logo'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$errors['org_logo'] = 'There was an error uploading the organization logo. Code: ' . $_FILES['org_logo']['error'];
|
|
}
|
|
}
|
|
// --- End File Upload Handling ---
|
|
|
|
// Validate the rest of the form data
|
|
$validation_errors = $this->validate_registration($submitted_data);
|
|
$errors = array_merge($errors, $validation_errors); // Combine file errors and validation errors
|
|
|
|
// --- Process if No Errors ---
|
|
if (empty($errors)) {
|
|
|
|
$user_id = $this->create_trainer_account($submitted_data, $profile_image_data, $org_logo_data);
|
|
|
|
if (is_wp_error($user_id)) {
|
|
$errors['account'] = $user_id->get_error_message();
|
|
$this->redirect_with_errors($errors, $submitted_data, $registration_page_url);
|
|
// No need for return/exit here
|
|
} elseif ($user_id) {
|
|
|
|
// Send admin notification using the approval workflow
|
|
if (class_exists('HVAC_Approval_Workflow')) {
|
|
$approval_workflow = new HVAC_Approval_Workflow();
|
|
$approval_workflow->send_new_registration_notification($user_id, $submitted_data);
|
|
} else {
|
|
// Fallback to old method
|
|
$this->send_admin_notification($user_id, $submitted_data);
|
|
}
|
|
|
|
// --- Success Redirect ---
|
|
$success_redirect_url = home_url('/registration-pending/'); // URL from E2E test
|
|
|
|
wp_safe_redirect($success_redirect_url);
|
|
exit; // Important after redirect
|
|
|
|
} else {
|
|
// This case should ideally not happen if wp_insert_user works correctly
|
|
$errors['account'] = 'An unknown error occurred during registration. Please contact support.';
|
|
|
|
$this->redirect_with_errors($errors, $submitted_data, $registration_page_url);
|
|
// No need for return/exit here
|
|
}
|
|
} else {
|
|
$this->redirect_with_errors($errors, $submitted_data, $registration_page_url);
|
|
// No need for return/exit here
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to store errors/data in transient and redirect back to the form page.
|
|
*
|
|
* @param array $errors Array of error messages.
|
|
* @param array $data Submitted form data.
|
|
* @param string $redirect_url The URL to redirect back to.
|
|
*/
|
|
private function redirect_with_errors($errors, $data, $redirect_url) {
|
|
$transient_id = uniqid(); // Generate unique ID for transient key
|
|
$transient_key = self::TRANSIENT_PREFIX . $transient_id;
|
|
$transient_data = [
|
|
'errors' => $errors,
|
|
'data' => $data, // Store submitted data to repopulate form
|
|
];
|
|
// Store for 5 minutes
|
|
set_transient($transient_key, $transient_data, MINUTE_IN_SECONDS * 5);
|
|
|
|
// Add query arguments to the redirect URL
|
|
$redirect_url = add_query_arg([
|
|
'reg_error' => '1',
|
|
'tid' => $transient_id,
|
|
], $redirect_url);
|
|
|
|
wp_safe_redirect($redirect_url);
|
|
exit; // Stop execution after redirect
|
|
}
|
|
|
|
/**
|
|
* Displays the actual form HTML.
|
|
* Receives submitted data and errors as arguments (potentially retrieved from transient).
|
|
*/
|
|
private function display_form_html($data = [], $errors = []) {
|
|
// Ensure $data and $errors are arrays, even if transient failed
|
|
$data = is_array($data) ? $data : [];
|
|
$errors = is_array($errors) ? $errors : [];
|
|
?>
|
|
<div class="hvac-registration-form">
|
|
<h2>HVAC Trainer Registration</h2>
|
|
<p>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.</p>
|
|
|
|
<?php /* Display validation summary if there are errors */ ?>
|
|
<?php if (!empty($errors)): ?>
|
|
<div class="hvac-form-errors" role="alert">
|
|
<h3>Please correct the following errors:</h3>
|
|
<ul>
|
|
<?php foreach ($errors as $field => $error): ?>
|
|
<li><?php echo esc_html($error); ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php /* Point form action to admin-post.php */ ?>
|
|
<form method="post" action="<?php echo esc_url( admin_url('admin-post.php') ); ?>" id="hvac-registration-form" enctype="multipart/form-data" novalidate> <?php // Added novalidate ?>
|
|
<?php /* Add hidden action field for admin-post hook */ ?>
|
|
<input type="hidden" name="action" value="<?php echo esc_attr(self::REGISTRATION_ACTION); ?>">
|
|
<?php wp_nonce_field('hvac_trainer_registration', 'hvac_registration_nonce'); ?>
|
|
|
|
<!-- Account Information -->
|
|
<div class="form-section">
|
|
<h3>Account Information</h3>
|
|
<div class="form-row">
|
|
<label for="user_email"><strong>Email *</strong></label>
|
|
<input type="email" name="user_email" id="user_email" value="<?php echo esc_attr($data['user_email'] ?? ''); ?>" required aria-describedby="user_email_error">
|
|
<?php if (isset($errors['user_email'])) echo '<p class="error-message" id="user_email_error">' . esc_html($errors['user_email']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="user_pass"><strong>Password *</strong></label>
|
|
<input type="password" name="user_pass" id="user_pass" required autocomplete="new-password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must be at least 8 characters long, and include at least one uppercase letter, one lowercase letter, and one number." aria-describedby="user_pass_hint user_pass_error">
|
|
<small id="user_pass_hint">Password must be at least 8 characters long, and include at least one uppercase letter, one lowercase letter, and one number.</small>
|
|
<?php if (isset($errors['user_pass'])) echo '<p class="error-message" id="user_pass_error">' . esc_html($errors['user_pass']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="confirm_password"><strong>Confirm Password *</strong></label>
|
|
<input type="password" name="confirm_password" id="confirm_password" required autocomplete="new-password" aria-describedby="confirm_password_error">
|
|
<?php if (isset($errors['confirm_password'])) echo '<p class="error-message" id="confirm_password_error">' . esc_html($errors['confirm_password']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
<!-- Username is hidden and derived from email server-side -->
|
|
<input type="hidden" name="user_login" value="">
|
|
</div>
|
|
|
|
<!-- Personal Information Section -->
|
|
<div class="form-section">
|
|
<h3>Personal Information</h3>
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="first_name"><strong>First Name *</strong></label>
|
|
<input type="text" name="first_name" id="first_name" value="<?php echo esc_attr($data['first_name'] ?? ''); ?>" required aria-describedby="first_name_error">
|
|
<?php if (isset($errors['first_name'])) echo '<p class="error-message" id="first_name_error">' . esc_html($errors['first_name']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="last_name"><strong>Last Name *</strong></label>
|
|
<input type="text" name="last_name" id="last_name" value="<?php echo esc_attr($data['last_name'] ?? ''); ?>" required aria-describedby="last_name_error">
|
|
<?php if (isset($errors['last_name'])) echo '<p class="error-message" id="last_name_error">' . esc_html($errors['last_name']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="display_name"><strong>Display Name *</strong></label>
|
|
<input type="text" name="display_name" id="display_name" value="<?php echo esc_attr($data['display_name'] ?? ''); ?>" required aria-describedby="display_name_hint display_name_error">
|
|
<small id="display_name_hint">This will be the name displayed to other users on the site.</small>
|
|
<?php if (isset($errors['display_name'])) echo '<p class="error-message" id="display_name_error">' . esc_html($errors['display_name']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="user_url">Personal Website (optional)</label>
|
|
<input type="url" name="user_url" id="user_url" value="<?php echo esc_attr($data['user_url'] ?? ''); ?>" aria-describedby="user_url_error">
|
|
<?php if (isset($errors['user_url'])) echo '<p class="error-message" id="user_url_error">' . esc_html($errors['user_url']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="user_linkedin">LinkedIn Profile URL (optional)</label>
|
|
<input type="url" name="user_linkedin" id="user_linkedin" value="<?php echo esc_attr($data['user_linkedin'] ?? ''); ?>" aria-describedby="user_linkedin_error">
|
|
<?php if (isset($errors['user_linkedin'])) echo '<p class="error-message" id="user_linkedin_error">' . esc_html($errors['user_linkedin']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="personal_accreditation">Personal Accreditation (optional)</label>
|
|
<input type="text" name="personal_accreditation" id="personal_accreditation" value="<?php echo esc_attr($data['personal_accreditation'] ?? ''); ?>" aria-describedby="personal_accreditation_hint">
|
|
<small id="personal_accreditation_hint">Enter your abbreviated accreditations separated by commas.</small>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="description"><strong>Biographical Info *</strong></label>
|
|
<textarea name="description" id="description" rows="4" required aria-describedby="description_hint description_error"><?php echo esc_textarea($data['description'] ?? ''); ?></textarea>
|
|
<small id="description_hint">A short bio about yourself. This will be displayed on your profile page.</small>
|
|
<?php if (isset($errors['description'])) echo '<p class="error-message" id="description_error">' . esc_html($errors['description']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="application_details"><strong>Application Details *</strong></label>
|
|
<small>Please explain why you want to create a training account on Upskill HVAC.</small>
|
|
<textarea name="application_details" id="application_details" rows="5" required aria-describedby="application_details_error"><?php echo esc_textarea($data['application_details'] ?? ''); ?></textarea>
|
|
<?php if (isset($errors['application_details'])) echo '<p class="error-message" id="application_details_error">' . esc_html($errors['application_details']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label id="role_label"><strong>Role *</strong></label>
|
|
<small>What is your primary role in the HVAC industry?</small>
|
|
<div class="radio-group" role="radiogroup" aria-labelledby="role_label">
|
|
<?php
|
|
$role_options = [
|
|
"technician" => "Technician",
|
|
"installer" => "Installer",
|
|
"supervisor" => "Supervisor",
|
|
"manager" => "Manager",
|
|
"trainer" => "Trainer",
|
|
"consultant" => "Consultant",
|
|
"sales" => "Sales Representative",
|
|
"engineer" => "Engineer",
|
|
"owner" => "Business Owner",
|
|
"other" => "Other"
|
|
];
|
|
foreach ($role_options as $value => $label) {
|
|
echo '<label><input type="radio" name="role" value="' . esc_attr($value) . '" ' . checked($data['role'] ?? '', $value, false) . ' required> ' . esc_html($label) . '</label>';
|
|
}
|
|
?>
|
|
</div>
|
|
<?php if (isset($errors['role'])) echo '<p class="error-message">' . esc_html($errors['role']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="profile_image">Profile Image (optional)</label>
|
|
<input type="file" name="profile_image" id="profile_image" accept="image/jpeg,image/png,image/gif" aria-describedby="profile_image_hint profile_image_error">
|
|
<small id="profile_image_hint">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.</small>
|
|
<?php if (isset($errors['profile_image'])) echo '<p class="error-message" id="profile_image_error">' . esc_html($errors['profile_image']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Training Organization Information Section -->
|
|
<div class="form-section">
|
|
<h3>Training Organization Information</h3>
|
|
<div class="form-row">
|
|
<label for="business_name"><strong>Organization Name *</strong></label>
|
|
<input type="text" name="business_name" id="business_name" value="<?php echo esc_attr($data['business_name'] ?? ''); ?>" required aria-describedby="business_name_error">
|
|
<?php if (isset($errors['business_name'])) echo '<p class="error-message" id="business_name_error">' . esc_html($errors['business_name']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="business_phone"><strong>Organization Phone *</strong></label>
|
|
<input type="tel" name="business_phone" id="business_phone" value="<?php echo esc_attr($data['business_phone'] ?? ''); ?>" required aria-describedby="business_phone_error">
|
|
<?php if (isset($errors['business_phone'])) echo '<p class="error-message" id="business_phone_error">' . esc_html($errors['business_phone']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="business_email"><strong>Organization Email *</strong></label>
|
|
<input type="email" name="business_email" id="business_email" value="<?php echo esc_attr($data['business_email'] ?? ''); ?>" required aria-describedby="business_email_error">
|
|
<?php if (isset($errors['business_email'])) echo '<p class="error-message" id="business_email_error">' . esc_html($errors['business_email']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="business_website">Organization Website (optional)</label>
|
|
<input type="url" name="business_website" id="business_website" value="<?php echo esc_attr($data['business_website'] ?? ''); ?>" aria-describedby="business_website_error">
|
|
<?php if (isset($errors['business_website'])) echo '<p class="error-message" id="business_website_error">' . esc_html($errors['business_website']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="business_description"><strong>Organization Description *</strong></label>
|
|
<textarea name="business_description" id="business_description" rows="4" required aria-describedby="business_description_error"><?php echo esc_textarea($data['business_description'] ?? ''); ?></textarea>
|
|
<?php if (isset($errors['business_description'])) echo '<p class="error-message" id="business_description_error">' . esc_html($errors['business_description']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<!-- Organization Logo -->
|
|
<div class="form-row">
|
|
<label for="org_logo"><strong>Organization Logo * </strong></label>
|
|
<input type="file" name="org_logo" id="org_logo" accept="image/jpeg,image/png,image/gif" required aria-describedby="org_logo_hint org_logo_error">
|
|
<small id="org_logo_hint">Please attach a .jpg, .png, or .gif image. This will be used as your organization's logo.</small>
|
|
<?php if (isset($errors['org_logo'])) echo '<p class="error-message" id="org_logo_error">' . esc_html($errors['org_logo']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<!-- Headquarters Location -->
|
|
<div class="form-row">
|
|
<h4>Organization Headquarters</h4>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="org_headquarters_country">Headquarters Country</label>
|
|
<select name="org_headquarters_country" id="org_headquarters_country" aria-describedby="org_headquarters_country_error">
|
|
<option value="">Select Country</option>
|
|
<option value="United States" <?php selected($data['org_headquarters_country'] ?? '', 'United States'); ?>>United States</option>
|
|
<option value="Canada" <?php selected($data['org_headquarters_country'] ?? '', 'Canada'); ?>>Canada</option>
|
|
<option value="" disabled>---</option>
|
|
<?php
|
|
$countries = $this->get_country_list();
|
|
foreach ($countries as $code => $name) {
|
|
if ($code !== 'US' && $code !== 'CA') {
|
|
echo '<option value="' . esc_attr($name) . '" ' . selected($data['org_headquarters_country'] ?? '', $name, false) . '>' . esc_html($name) . '</option>';
|
|
}
|
|
}
|
|
?>
|
|
</select>
|
|
<?php if (isset($errors['org_headquarters_country'])) echo '<p class="error-message" id="org_headquarters_country_error">' . esc_html($errors['org_headquarters_country']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="org_headquarters_state">Headquarters State/Province</label>
|
|
<select name="org_headquarters_state" id="org_headquarters_state" aria-describedby="org_headquarters_state_error">
|
|
<option value="">Select State/Province</option>
|
|
<option value="Other" <?php selected($data['org_headquarters_state'] ?? '', 'Other'); ?>>Other</option>
|
|
<?php
|
|
// Pre-populate selected state if available from transient
|
|
$selected_hq_state = $data['org_headquarters_state'] ?? '';
|
|
if (!empty($selected_hq_state) && $selected_hq_state !== 'Other') {
|
|
// Simply add the selected state option
|
|
echo '<option value="' . esc_attr($selected_hq_state) . '" selected>' . esc_html($selected_hq_state) . '</option>';
|
|
}
|
|
?>
|
|
</select>
|
|
<input type="text" name="org_headquarters_state_other" id="org_headquarters_state_other"
|
|
value="<?php echo esc_attr($data['org_headquarters_state_other'] ?? ''); ?>"
|
|
style="<?php echo (($data['org_headquarters_state'] ?? '') === 'Other' && ($data['org_headquarters_country'] ?? '') !== 'United States' && ($data['org_headquarters_country'] ?? '') !== 'Canada') ? '' : 'display:none;'; ?> margin-top: 0.5rem;"
|
|
placeholder="Enter your state/province"
|
|
aria-describedby="org_headquarters_state_other_error">
|
|
<?php if (isset($errors['org_headquarters_state'])) echo '<p class="error-message" id="org_headquarters_state_error">' . esc_html($errors['org_headquarters_state']) . '</p>'; ?>
|
|
<?php if (isset($errors['org_headquarters_state_other'])) echo '<p class="error-message" id="org_headquarters_state_other_error">' . esc_html($errors['org_headquarters_state_other']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="org_headquarters_city">Headquarters City</label>
|
|
<input type="text" name="org_headquarters_city" id="org_headquarters_city" value="<?php echo esc_attr($data['org_headquarters_city'] ?? ''); ?>" aria-describedby="org_headquarters_city_error">
|
|
<?php if (isset($errors['org_headquarters_city'])) echo '<p class="error-message" id="org_headquarters_city_error">' . esc_html($errors['org_headquarters_city']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Training Information (moved from previous section) -->
|
|
<div class="form-row">
|
|
<h4>Training Capabilities</h4>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="business_type"><strong>Business Type *</strong></label>
|
|
<small>What best describes your business type?</small>
|
|
<select name="business_type" id="business_type" required aria-describedby="business_type_error">
|
|
<option value="">Select Business Type</option>
|
|
<option value="Association" <?php selected($data['business_type'] ?? '', 'Association'); ?>>Association</option>
|
|
<option value="Consultant" <?php selected($data['business_type'] ?? '', 'Consultant'); ?>>Consultant</option>
|
|
<option value="Service Company" <?php selected($data['business_type'] ?? '', 'Service Company'); ?>>Service Company</option>
|
|
<option value="Distributor or Supplier" <?php selected($data['business_type'] ?? '', 'Distributor or Supplier'); ?>>Distributor or Supplier</option>
|
|
<option value="Sales Representative" <?php selected($data['business_type'] ?? '', 'Sales Representative'); ?>>Sales Representative</option>
|
|
<option value="Educational Institution" <?php selected($data['business_type'] ?? '', 'Educational Institution'); ?>>Educational Institution</option>
|
|
<option value="Training Organization" <?php selected($data['business_type'] ?? '', 'Training Organization'); ?>>Training Organization</option>
|
|
<option value="Equipment Manufacturer" <?php selected($data['business_type'] ?? '', 'Equipment Manufacturer'); ?>>Equipment Manufacturer</option>
|
|
<option value="Other Manufacturer" <?php selected($data['business_type'] ?? '', 'Other Manufacturer'); ?>>Other Manufacturer</option>
|
|
<option value="Government" <?php selected($data['business_type'] ?? '', 'Government'); ?>>Government</option>
|
|
<option value="Other" <?php selected($data['business_type'] ?? '', 'Other'); ?>>Other</option>
|
|
</select>
|
|
<?php if (isset($errors['business_type'])) echo '<p class="error-message" id="business_type_error">' . esc_html($errors['business_type']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label id="training_audience_label"><strong>Training Audience *</strong></label>
|
|
<small>Who do you offer training to? (Select all that apply)</small>
|
|
<div class="checkbox-group" role="group" aria-labelledby="training_audience_label">
|
|
<?php
|
|
$selected_audience = $data['training_audience'] ?? [];
|
|
if (!is_array($selected_audience)) $selected_audience = []; // Ensure it's an array
|
|
|
|
// Use only the 4 specified options
|
|
$audience_options = [
|
|
"Anyone (open to the public)",
|
|
"Industry professionals",
|
|
"Internal staff in my company",
|
|
"Registered students/members of my org/institution"
|
|
];
|
|
foreach ($audience_options as $option) {
|
|
echo '<label><input type="checkbox" name="training_audience[]" value="' . esc_attr($option) . '" ' . checked(in_array($option, $selected_audience), true, false) . '> ' . esc_html($option) . '</label>';
|
|
}
|
|
?>
|
|
</div>
|
|
<?php if (isset($errors['training_audience'])) echo '<p class="error-message">' . esc_html($errors['training_audience']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label id="training_formats_label"><strong>Training Formats *</strong></label>
|
|
<small>What formats of training do you offer?</small>
|
|
<div class="checkbox-group" role="group" aria-labelledby="training_formats_label">
|
|
<?php
|
|
$format_terms = get_terms(['taxonomy' => 'training_formats', 'hide_empty' => false]);
|
|
$selected_formats = $data['training_formats'] ?? [];
|
|
if (!is_array($selected_formats)) $selected_formats = []; // Ensure it's an array
|
|
|
|
if (!is_wp_error($format_terms) && !empty($format_terms)) {
|
|
foreach ($format_terms as $term) {
|
|
echo '<label><input type="checkbox" name="training_formats[]" value="' . esc_attr($term->name) . '" ' . checked(in_array($term->name, $selected_formats), true, false) . '> ' . esc_html($term->name) . '</label>';
|
|
}
|
|
} else {
|
|
// Fallback to hardcoded options if taxonomy not available
|
|
$format_options = ["In-person", "Virtual", "Hybrid", "On-demand"];
|
|
foreach ($format_options as $format) {
|
|
echo '<label><input type="checkbox" name="training_formats[]" value="' . esc_attr($format) . '" ' . checked(in_array($format, $selected_formats), true, false) . '> ' . esc_html($format) . '</label>';
|
|
}
|
|
}
|
|
?>
|
|
</div>
|
|
<?php if (isset($errors['training_formats'])) echo '<p class="error-message">' . esc_html($errors['training_formats']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label id="training_locations_label"><strong>Training Locations *</strong></label>
|
|
<small>Where are you willing to provide training? (Select all that apply)</small>
|
|
<div class="checkbox-group" role="group" aria-labelledby="training_locations_label">
|
|
<?php
|
|
$location_terms = get_terms(['taxonomy' => 'training_locations', 'hide_empty' => false]);
|
|
$selected_locations = $data['training_locations'] ?? [];
|
|
if (!is_array($selected_locations)) $selected_locations = []; // Ensure it's an array
|
|
|
|
if (!is_wp_error($location_terms) && !empty($location_terms)) {
|
|
foreach ($location_terms as $term) {
|
|
echo '<label><input type="checkbox" name="training_locations[]" value="' . esc_attr($term->name) . '" ' . checked(in_array($term->name, $selected_locations), true, false) . '> ' . esc_html($term->name) . '</label>';
|
|
}
|
|
} else {
|
|
// Fallback to hardcoded options if taxonomy not available
|
|
$location_options = ["Online", "Local", "Regional Travel", "National Travel", "International Travel"];
|
|
foreach ($location_options as $location) {
|
|
echo '<label><input type="checkbox" name="training_locations[]" value="' . esc_attr($location) . '" ' . checked(in_array($location, $selected_locations), true, false) . '> ' . esc_html($location) . '</label>';
|
|
}
|
|
}
|
|
?>
|
|
</div>
|
|
<?php if (isset($errors['training_locations'])) echo '<p class="error-message">' . esc_html($errors['training_locations']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label id="training_resources_label"><strong>Training Resources *</strong></label>
|
|
<small>What training resources do you have access to? (Select all that apply)</small>
|
|
<div class="checkbox-group" role="group" aria-labelledby="training_resources_label">
|
|
<?php
|
|
$resource_terms = get_terms(['taxonomy' => 'training_resources', 'hide_empty' => false]);
|
|
$selected_resources = $data['training_resources'] ?? [];
|
|
if (!is_array($selected_resources)) $selected_resources = []; // Ensure it's an array
|
|
|
|
if (!is_wp_error($resource_terms) && !empty($resource_terms)) {
|
|
foreach ($resource_terms as $term) {
|
|
echo '<label><input type="checkbox" name="training_resources[]" value="' . esc_attr($term->name) . '" ' . checked(in_array($term->name, $selected_resources), true, false) . '> ' . esc_html($term->name) . '</label>';
|
|
}
|
|
} else {
|
|
// Fallback to hardcoded options if taxonomy not available
|
|
$resource_options = ["Classroom", "Training Lab", "Ducted Furnace(s)", "Ducted Air Handler(s)", "Ducted Air Conditioner(s)", "Ducted Heat Pump(s)", "Ductless Heat Pump(s)", "Training Manuals", "Presentation Slides", "LMS Platform / SCORM Files", "Custom Curriculum", "Other"];
|
|
foreach ($resource_options as $resource) {
|
|
echo '<label><input type="checkbox" name="training_resources[]" value="' . esc_attr($resource) . '" ' . checked(in_array($resource, $selected_resources), true, false) . '> ' . esc_html($resource) . '</label>';
|
|
}
|
|
}
|
|
?>
|
|
</div>
|
|
<?php if (isset($errors['training_resources'])) echo '<p class="error-message">' . esc_html($errors['training_resources']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="annual_revenue_target">Annual Revenue Target (optional)</label>
|
|
<small>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?</small>
|
|
<input type="number" name="annual_revenue_target" id="annual_revenue_target" min="0" step="1" value="<?php echo esc_attr($data['annual_revenue_target'] ?? ''); ?>">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Training Venue Information Section -->
|
|
<div class="form-section">
|
|
<h3>Training Venue Information</h3>
|
|
<div class="form-row">
|
|
<label id="create_venue_label"><strong>Create Training Venue Profile? *</strong></label>
|
|
<small>Do you want to create a Training Venue Profile for your organization? This will be used when listing your training events.</small>
|
|
<div class="radio-group" role="radiogroup" aria-labelledby="create_venue_label">
|
|
<label><input type="radio" name="create_venue" value="Yes" <?php checked($data['create_venue'] ?? 'Yes', 'Yes'); ?> required> Yes</label>
|
|
<label><input type="radio" name="create_venue" value="No" <?php checked($data['create_venue'] ?? 'Yes', 'No'); ?>> No</label>
|
|
</div>
|
|
<?php if (isset($errors['create_venue'])) echo '<p class="error-message">' . esc_html($errors['create_venue']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div id="venue-details" style="<?php echo ($data['create_venue'] ?? 'Yes') === 'No' ? 'display:none;' : ''; ?>">
|
|
<div class="form-row">
|
|
<label for="venue_name"><strong>Venue Name *</strong></label>
|
|
<input type="text" name="venue_name" id="venue_name" value="<?php echo esc_attr($data['venue_name'] ?? ''); ?>" aria-describedby="venue_name_hint venue_name_error">
|
|
<small id="venue_name_hint">Defaults to "[Organization Name] of [City]"</small>
|
|
<?php if (isset($errors['venue_name'])) echo '<p class="error-message" id="venue_name_error">' . esc_html($errors['venue_name']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="venue_address"><strong>Street Address *</strong></label>
|
|
<input type="text" name="venue_address" id="venue_address" value="<?php echo esc_attr($data['venue_address'] ?? ''); ?>" aria-describedby="venue_address_error">
|
|
<?php if (isset($errors['venue_address'])) echo '<p class="error-message" id="venue_address_error">' . esc_html($errors['venue_address']) . '</p>'; ?>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="user_country"><strong>Country *</strong></label>
|
|
<select name="user_country" id="user_country" required aria-describedby="user_country_error">
|
|
<option value="">Select Country</option>
|
|
<option value="United States" <?php selected($data['user_country'] ?? '', 'United States'); ?>>United States</option>
|
|
<option value="Canada" <?php selected($data['user_country'] ?? '', 'Canada'); ?>>Canada</option>
|
|
<option value="" disabled>---</option>
|
|
<?php
|
|
$countries = $this->get_country_list();
|
|
foreach ($countries as $code => $name) {
|
|
if ($code !== 'US' && $code !== 'CA') {
|
|
echo '<option value="' . esc_attr($name) . '" ' . selected($data['user_country'] ?? '', $name, false) . '>' . esc_html($name) . '</option>';
|
|
}
|
|
}
|
|
?>
|
|
</select>
|
|
<?php if (isset($errors['user_country'])) echo '<p class="error-message" id="user_country_error">' . esc_html($errors['user_country']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="user_state"><strong>State/Province *</strong></label>
|
|
<select name="user_state" id="user_state" required aria-describedby="user_state_error">
|
|
<!-- Options loaded by JS -->
|
|
<option value="">Select State/Province</option>
|
|
<option value="Other" <?php selected($data['user_state'] ?? '', 'Other'); ?>>Other</option>
|
|
<?php
|
|
// Pre-populate selected state if available from transient
|
|
$selected_state = $data['user_state'] ?? '';
|
|
if (!empty($selected_state) && $selected_state !== 'Other') {
|
|
// Determine if it's a US state or CA province based on country or value itself
|
|
$is_us_state = array_key_exists($selected_state, $this->get_us_states());
|
|
$is_ca_province = array_key_exists($selected_state, $this->get_canadian_provinces());
|
|
if ($is_us_state || $is_ca_province) {
|
|
echo '<option value="' . esc_attr($selected_state) . '" selected>' . esc_html($selected_state) . '</option>';
|
|
}
|
|
}
|
|
?>
|
|
</select>
|
|
<input type="text" name="user_state_other" id="user_state_other"
|
|
value="<?php echo esc_attr($data['user_state_other'] ?? ''); ?>"
|
|
style="<?php echo (($data['user_state'] ?? '') === 'Other' && ($data['user_country'] ?? '') !== 'United States' && ($data['user_country'] ?? '') !== 'Canada') ? '' : 'display:none;'; ?> margin-top: 0.5rem;"
|
|
placeholder="Enter your state/province"
|
|
aria-describedby="user_state_other_error">
|
|
<?php if (isset($errors['user_state'])) echo '<p class="error-message" id="user_state_error">' . esc_html($errors['user_state']) . '</p>'; ?>
|
|
<?php if (isset($errors['user_state_other'])) echo '<p class="error-message" id="user_state_other_error">' . esc_html($errors['user_state_other']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="user_city"><strong>City *</strong></label>
|
|
<input type="text" name="user_city" id="user_city" value="<?php echo esc_attr($data['user_city'] ?? ''); ?>" required aria-describedby="user_city_error">
|
|
<?php if (isset($errors['user_city'])) echo '<p class="error-message" id="user_city_error">' . esc_html($errors['user_city']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="user_zip"><strong>Zip/Postal Code *</strong></label>
|
|
<input type="text" name="user_zip" id="user_zip" value="<?php echo esc_attr($data['user_zip'] ?? ''); ?>" required aria-describedby="user_zip_error">
|
|
<?php if (isset($errors['user_zip'])) echo '<p class="error-message" id="user_zip_error">' . esc_html($errors['user_zip']) . '</p>'; ?>
|
|
</div>
|
|
|
|
<div class="form-row form-row-half">
|
|
<div>
|
|
<label for="venue_phone">Venue Phone</label>
|
|
<input type="tel" name="venue_phone" id="venue_phone" value="<?php echo esc_attr($data['venue_phone'] ?? $data['business_phone'] ?? ''); ?>" aria-describedby="venue_phone_error">
|
|
<?php if (isset($errors['venue_phone'])) echo '<p class="error-message" id="venue_phone_error">' . esc_html($errors['venue_phone']) . '</p>'; ?>
|
|
</div>
|
|
<div>
|
|
<label for="venue_website">Venue Website</label>
|
|
<input type="url" name="venue_website" id="venue_website" value="<?php echo esc_attr($data['venue_website'] ?? $data['business_website'] ?? ''); ?>" aria-describedby="venue_website_error">
|
|
<?php if (isset($errors['venue_website'])) echo '<p class="error-message" id="venue_website_error">' . esc_html($errors['venue_website']) . '</p>'; ?>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- end venue-details -->
|
|
</div>
|
|
|
|
<div class="form-submit">
|
|
<input type="submit" name="hvac_register" value="Register">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Enqueue styles and scripts for the registration form
|
|
*/
|
|
public function enqueue_scripts() {
|
|
// Check multiple ways for registration page
|
|
global $post;
|
|
$current_path = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
|
$is_registration_page = (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'hvac_trainer_registration')) ||
|
|
is_page('trainer/registration') ||
|
|
is_page('trainer-registration') ||
|
|
$current_path === 'trainer/registration' ||
|
|
strpos($current_path, 'trainer/registration') !== false;
|
|
|
|
if ($is_registration_page) {
|
|
wp_enqueue_style(
|
|
'hvac-registration-style',
|
|
HVAC_PLUGIN_URL . 'assets/css/hvac-registration.css', // Ensure this CSS file exists and is styled
|
|
array(),
|
|
HVAC_PLUGIN_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'hvac-registration-js',
|
|
HVAC_PLUGIN_URL . 'assets/js/hvac-registration.js', // Ensure this JS file exists
|
|
array('jquery'),
|
|
HVAC_PLUGIN_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
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle profile image upload after user is created.
|
|
* Should be called from within create_trainer_account or similar context.
|
|
*
|
|
* @param int $user_id The ID of the user to attach the image to.
|
|
* @param array $file_data The $_FILES array entry for the uploaded image.
|
|
* @return int|false Attachment ID on success, false on failure.
|
|
*/
|
|
private function handle_profile_image_upload($user_id, $file_data) {
|
|
// Basic validation already done in process_registration_submission
|
|
if (!$user_id || empty($file_data) || !isset($file_data['tmp_name']) || $file_data['error'] !== UPLOAD_ERR_OK) {
|
|
|
|
return false;
|
|
}
|
|
|
|
// 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');
|
|
|
|
// Let WordPress handle the upload. It moves the file and creates attachment post.
|
|
// Pass the $_FILES array key ('profile_image' in this case)
|
|
$attachment_id = media_handle_upload('profile_image', 0); // 0 means don't attach to a post
|
|
|
|
if (is_wp_error($attachment_id)) {
|
|
// Handle upload error
|
|
// Optionally add this error to be displayed to the user via transient?
|
|
// For now, just fail silently in terms of user feedback, but log it.
|
|
return false;
|
|
} else {
|
|
// Store the attachment ID as user meta
|
|
update_user_meta($user_id, 'profile_image_id', $attachment_id);
|
|
|
|
return $attachment_id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle organization logo upload
|
|
*
|
|
* @param int $organizer_id The ID of the organizer post to attach the image to.
|
|
* @param array $file_data The $_FILES array entry for the uploaded image.
|
|
* @return int|false Attachment ID on success, false on failure.
|
|
*/
|
|
private function handle_org_logo_upload($organizer_id, $file_data) {
|
|
if (!$organizer_id || empty($file_data) || !isset($file_data['tmp_name']) || $file_data['error'] !== UPLOAD_ERR_OK) {
|
|
return false;
|
|
}
|
|
|
|
require_once(ABSPATH . 'wp-admin/includes/image.php');
|
|
require_once(ABSPATH . 'wp-admin/includes/file.php');
|
|
require_once(ABSPATH . 'wp-admin/includes/media.php');
|
|
|
|
// Set up the file for upload - need to temporarily set $_FILES for media_handle_upload
|
|
$_FILES['org_logo_temp'] = $file_data;
|
|
$attachment_id = media_handle_upload('org_logo_temp', $organizer_id);
|
|
unset($_FILES['org_logo_temp']);
|
|
|
|
if (is_wp_error($attachment_id)) {
|
|
return false;
|
|
} else {
|
|
// Set as featured image for the organizer
|
|
set_post_thumbnail($organizer_id, $attachment_id);
|
|
return $attachment_id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate registration form data
|
|
*
|
|
* @param array $data Submitted form data ($_POST).
|
|
* @return array Array of errors, empty if valid.
|
|
*/
|
|
public function validate_registration($data) {
|
|
$errors = array();
|
|
|
|
// Required field validation
|
|
$required_fields = [
|
|
'user_email' => 'Email',
|
|
'user_pass' => 'Password',
|
|
'confirm_password' => 'Confirm Password',
|
|
'first_name' => 'First Name',
|
|
'last_name' => 'Last Name',
|
|
'display_name' => 'Display Name',
|
|
'description' => 'Biographical Info',
|
|
'business_name' => 'Organization Name',
|
|
'business_phone' => 'Organization Phone',
|
|
'business_email' => 'Organization Email',
|
|
'business_description' => 'Organization Description',
|
|
'org_logo' => 'Organization Logo',
|
|
'create_venue' => 'Create Training Venue Profile selection',
|
|
'business_type' => 'Business Type',
|
|
'application_details' => 'Application Details',
|
|
'role' => 'Role',
|
|
];
|
|
|
|
foreach ($required_fields as $field => $label) {
|
|
// Special handling for file upload
|
|
if ($field === 'org_logo') {
|
|
if (!isset($_FILES['org_logo']) || $_FILES['org_logo']['error'] === UPLOAD_ERR_NO_FILE) {
|
|
$errors[$field] = $label . ' is required.';
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Use trim to catch spaces-only input
|
|
if (empty($data[$field]) || trim($data[$field]) === '') {
|
|
$errors[$field] = $label . ' is required.';
|
|
}
|
|
}
|
|
|
|
// Conditional venue fields validation
|
|
if (isset($data['create_venue']) && $data['create_venue'] === 'Yes') {
|
|
$venue_required_fields = [
|
|
'venue_name' => 'Venue Name',
|
|
'venue_address' => 'Street Address',
|
|
'user_country' => 'Country',
|
|
'user_state' => 'State/Province',
|
|
'user_city' => 'City',
|
|
'user_zip' => 'Zip/Postal Code',
|
|
];
|
|
|
|
foreach ($venue_required_fields as $field => $label) {
|
|
if (empty($data[$field]) || trim($data[$field]) === '') {
|
|
$errors[$field] = $label . ' is required for venue creation.';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Required checkbox groups
|
|
$required_checkboxes = [
|
|
'training_audience' => 'Training Audience',
|
|
'training_formats' => 'Training Formats',
|
|
'training_locations' => 'Training Locations',
|
|
'training_resources' => 'Training Resources',
|
|
];
|
|
foreach ($required_checkboxes as $field => $label) {
|
|
// Check if the key exists and is a non-empty array
|
|
if (empty($data[$field]) || !is_array($data[$field])) {
|
|
$errors[$field] = 'Please select at least one option for ' . $label . '.';
|
|
}
|
|
}
|
|
|
|
// Email validation
|
|
if (!empty($data['user_email']) && !is_email($data['user_email'])) {
|
|
$errors['user_email'] = 'Please enter a valid email address.';
|
|
}
|
|
if (!empty($data['business_email']) && !is_email($data['business_email'])) {
|
|
$errors['business_email'] = 'Please enter a valid business email address.';
|
|
}
|
|
|
|
// Email exists validation (only if email is valid)
|
|
if (empty($errors['user_email']) && !empty($data['user_email']) && email_exists($data['user_email'])) {
|
|
$errors['user_email'] = 'This email address is already registered.';
|
|
}
|
|
|
|
// Password validation
|
|
if (!empty($data['user_pass'])) {
|
|
if (strlen($data['user_pass']) < 8) {
|
|
$errors['user_pass'] = 'Password must be at least 8 characters long.';
|
|
} elseif (!preg_match('/[A-Z]/', $data['user_pass'])) {
|
|
$errors['user_pass'] = 'Password must contain at least one uppercase letter.';
|
|
} elseif (!preg_match('/[a-z]/', $data['user_pass'])) {
|
|
$errors['user_pass'] = 'Password must contain at least one lowercase letter.';
|
|
} elseif (!preg_match('/[0-9]/', $data['user_pass'])) {
|
|
$errors['user_pass'] = 'Password must contain at least one number.';
|
|
}
|
|
// Consider adding special character requirement if needed: !preg_match('/[\W_]/', $data['user_pass'])
|
|
}
|
|
|
|
// Confirm password validation (only if password itself is not empty)
|
|
if (!empty($data['user_pass']) && empty($errors['user_pass'])) {
|
|
if (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.';
|
|
}
|
|
}
|
|
|
|
// URL validation (optional fields)
|
|
if (!empty($data['user_url']) && !filter_var($data['user_url'], FILTER_VALIDATE_URL)) {
|
|
$errors['user_url'] = 'Please enter a valid URL for your personal website.';
|
|
}
|
|
if (!empty($data['user_linkedin']) && !filter_var($data['user_linkedin'], FILTER_VALIDATE_URL)) {
|
|
$errors['user_linkedin'] = 'Please enter a valid URL for your LinkedIn profile.';
|
|
}
|
|
if (!empty($data['business_website']) && !filter_var($data['business_website'], FILTER_VALIDATE_URL)) {
|
|
$errors['business_website'] = 'Please enter a valid URL for your business website.';
|
|
}
|
|
|
|
// State/Province 'Other' validation
|
|
if (!empty($data['user_country']) && isset($data['create_venue']) && $data['create_venue'] === 'Yes') {
|
|
if ($data['user_country'] !== 'United States' && $data['user_country'] !== 'Canada') {
|
|
// If country is not US/CA, state *must* be 'Other'
|
|
if (empty($data['user_state']) || $data['user_state'] !== 'Other') {
|
|
$errors['user_state'] = 'Please select "Other" for State/Province if your country is not US or Canada.';
|
|
} elseif (empty($data['user_state_other']) || trim($data['user_state_other']) === '') {
|
|
// If state is 'Other', the text input must not be empty
|
|
$errors['user_state_other'] = 'Please enter your state/province.';
|
|
}
|
|
} elseif (!empty($data['user_state'])) {
|
|
// If country is US/CA
|
|
if ($data['user_state'] === 'Other') {
|
|
// State cannot be 'Other' if country is US/CA
|
|
$errors['user_state'] = 'Please select your state/province from the list.';
|
|
} elseif (empty($errors['user_state'])) { // Only check 'Other' field if state itself is valid
|
|
// Ensure 'Other' text input is cleared if a valid state is selected
|
|
// This might be better handled by JS, but add server-side check just in case
|
|
if (!empty($data['user_state_other'])) {
|
|
// Maybe log a warning? Or just ignore it. Let's ignore for now.
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Role validation - ensure it's one of the allowed values
|
|
if (!empty($data['role'])) {
|
|
$allowed_roles = ['technician', 'installer', 'supervisor', 'manager', 'trainer', 'consultant', 'sales', 'engineer', 'owner', 'other'];
|
|
if (!in_array($data['role'], $allowed_roles)) {
|
|
$errors['role'] = 'Please select a valid role.';
|
|
}
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Create trainer account and associated data
|
|
*
|
|
* @param array $data Sanitized form data.
|
|
* @param array|null $profile_image_data The $_FILES entry for the profile image, if provided.
|
|
* @param array|null $org_logo_data The $_FILES entry for the organization logo, if provided.
|
|
* @return int|WP_Error User ID on success, WP_Error on failure.
|
|
*/
|
|
private function create_trainer_account($data, $profile_image_data = null, $org_logo_data = null) {
|
|
// Assume data is already somewhat validated by validate_registration
|
|
// Perform final sanitization here before insertion
|
|
$user_email = sanitize_email($data['user_email']);
|
|
$user_pass = $data['user_pass']; // wp_insert_user handles hashing
|
|
$first_name = sanitize_text_field($data['first_name']);
|
|
$last_name = sanitize_text_field($data['last_name']);
|
|
$display_name = sanitize_text_field($data['display_name']);
|
|
$user_url = !empty($data['user_url']) ? esc_url_raw($data['user_url']) : '';
|
|
$description = wp_kses_post($data['description']); // Allow some HTML
|
|
|
|
// Generate username from email (ensure uniqueness)
|
|
$username_base = sanitize_user(substr($user_email, 0, strpos($user_email, '@')), true);
|
|
if (empty($username_base)) { // Handle cases where email might be weird
|
|
$username_base = 'trainer';
|
|
}
|
|
$username = $username_base;
|
|
$counter = 1;
|
|
while (username_exists($username)) {
|
|
$username = $username_base . $counter;
|
|
$counter++;
|
|
if ($counter > 100) { // Safety break
|
|
return new WP_Error('username_generation', 'Could not generate a unique username.');
|
|
}
|
|
}
|
|
|
|
// User data array
|
|
$user_data = array(
|
|
'user_login' => $username,
|
|
'user_email' => $user_email,
|
|
'user_pass' => $user_pass,
|
|
'first_name' => $first_name,
|
|
'last_name' => $last_name,
|
|
'display_name' => $display_name,
|
|
'user_url' => $user_url,
|
|
'description' => $description,
|
|
'role' => 'hvac_trainer' // Assign custom role
|
|
);
|
|
|
|
// Insert the user
|
|
$user_id = wp_insert_user($user_data);
|
|
|
|
// Check for errors
|
|
if (is_wp_error($user_id)) {
|
|
return $user_id; // Return the WP_Error object
|
|
}
|
|
|
|
// --- Update User Meta ---
|
|
// Sanitize all meta values before updating
|
|
$meta_fields = [
|
|
'user_linkedin' => !empty($data['user_linkedin']) ? esc_url_raw($data['user_linkedin']) : '',
|
|
'personal_accreditation' => !empty($data['personal_accreditation']) ? sanitize_text_field($data['personal_accreditation']) : '',
|
|
'business_name' => sanitize_text_field($data['business_name']),
|
|
'business_phone' => sanitize_text_field($data['business_phone']),
|
|
'business_email' => sanitize_email($data['business_email']),
|
|
'business_website' => !empty($data['business_website']) ? esc_url_raw($data['business_website']) : '',
|
|
'business_description' => wp_kses_post($data['business_description']),
|
|
'org_headquarters_city' => !empty($data['org_headquarters_city']) ? sanitize_text_field($data['org_headquarters_city']) : '',
|
|
// Use the 'Other' field value if state was 'Other', otherwise use the selected state
|
|
'org_headquarters_state' => ($data['org_headquarters_state'] === 'Other' && !empty($data['org_headquarters_state_other']))
|
|
? sanitize_text_field($data['org_headquarters_state_other'])
|
|
: (!empty($data['org_headquarters_state']) ? sanitize_text_field($data['org_headquarters_state']) : ''),
|
|
'org_headquarters_country' => !empty($data['org_headquarters_country']) ? sanitize_text_field($data['org_headquarters_country']) : '',
|
|
'create_venue' => sanitize_text_field($data['create_venue']), // Should be 'Yes' or 'No'
|
|
'business_type' => sanitize_text_field($data['business_type']),
|
|
'training_audience' => (!empty($data['training_audience']) && is_array($data['training_audience'])) ? array_map('sanitize_text_field', $data['training_audience']) : [],
|
|
'training_formats' => (!empty($data['training_formats']) && is_array($data['training_formats'])) ? array_map('sanitize_text_field', $data['training_formats']) : [],
|
|
'training_locations' => (!empty($data['training_locations']) && is_array($data['training_locations'])) ? array_map('sanitize_text_field', $data['training_locations']) : [],
|
|
'training_resources' => (!empty($data['training_resources']) && is_array($data['training_resources'])) ? array_map('sanitize_text_field', $data['training_resources']) : [],
|
|
'application_details' => wp_kses_post($data['application_details']),
|
|
'annual_revenue_target' => !empty($data['annual_revenue_target']) ? intval($data['annual_revenue_target']) : '',
|
|
'role' => sanitize_text_field($data['role']),
|
|
'account_status' => 'pending' // Set initial status
|
|
];
|
|
|
|
// If venue creation is requested, store venue-specific data
|
|
if (isset($data['create_venue']) && $data['create_venue'] === 'Yes') {
|
|
$meta_fields['venue_name'] = !empty($data['venue_name']) ? sanitize_text_field($data['venue_name']) : '';
|
|
$meta_fields['venue_address'] = !empty($data['venue_address']) ? sanitize_text_field($data['venue_address']) : '';
|
|
$meta_fields['user_country'] = sanitize_text_field($data['user_country']);
|
|
// Use the 'Other' field value if state was 'Other', otherwise use the selected state
|
|
$meta_fields['user_state'] = ($data['user_state'] === 'Other' && isset($data['user_state_other'])) ? sanitize_text_field($data['user_state_other']) : sanitize_text_field($data['user_state']);
|
|
$meta_fields['user_city'] = sanitize_text_field($data['user_city']);
|
|
$meta_fields['user_zip'] = sanitize_text_field($data['user_zip']);
|
|
$meta_fields['venue_phone'] = !empty($data['venue_phone']) ? sanitize_text_field($data['venue_phone']) : '';
|
|
$meta_fields['venue_website'] = !empty($data['venue_website']) ? esc_url_raw($data['venue_website']) : '';
|
|
}
|
|
|
|
foreach ($meta_fields as $key => $value) {
|
|
update_user_meta($user_id, $key, $value);
|
|
}
|
|
|
|
// --- Handle Profile Image Upload ---
|
|
// Note: handle_profile_image_upload uses media_handle_upload which expects the key from $_FILES
|
|
if ($profile_image_data) {
|
|
|
|
// We don't need the return value here unless we want to report specific upload errors
|
|
$this->handle_profile_image_upload($user_id, $profile_image_data); // Pass the $_FILES entry
|
|
}
|
|
|
|
// --- Create Organizer Profile ---
|
|
|
|
$organizer_id = $this->create_organizer_profile($user_id, $meta_fields, $org_logo_data); // Pass sanitized meta fields
|
|
if ($organizer_id) {
|
|
|
|
update_user_meta($user_id, 'hvac_organizer_id', $organizer_id);
|
|
} else {
|
|
|
|
// Consider returning an error if this is critical
|
|
// return new WP_Error('organizer_creation', 'Failed to create the associated organizer profile.');
|
|
}
|
|
|
|
// --- Create Training Venue (if requested) ---
|
|
if (isset($meta_fields['create_venue']) && $meta_fields['create_venue'] === 'Yes') {
|
|
|
|
$venue_id = $this->create_training_venue($user_id, $meta_fields); // Pass sanitized meta fields
|
|
if ($venue_id) {
|
|
|
|
update_user_meta($user_id, 'hvac_venue_id', $venue_id);
|
|
} else {
|
|
|
|
// Consider returning an error if this is critical
|
|
// return new WP_Error('venue_creation', 'Failed to create the associated training venue.');
|
|
}
|
|
}
|
|
|
|
// --- Set Account Status to Pending ---
|
|
// This is already done via user meta, but could also involve custom capabilities or flags
|
|
// update_user_meta($user_id, 'account_status', 'pending'); // Redundant if set above
|
|
|
|
return $user_id; // Return user ID on success
|
|
}
|
|
|
|
/**
|
|
* Create or update an Organizer profile linked to the user using sanitized data.
|
|
*
|
|
* @param int $user_id The user ID.
|
|
* @param array $meta_data Array of sanitized user meta data.
|
|
* @param array|null $org_logo_data The $_FILES entry for the organization logo, if provided.
|
|
* @return int|false Organizer Post ID on success, false on failure.
|
|
*/
|
|
private function create_organizer_profile($user_id, $meta_data, $org_logo_data = null) {
|
|
if (!class_exists('Tribe__Events__Main') || !function_exists('tribe_create_organizer')) {
|
|
|
|
return false;
|
|
}
|
|
|
|
$organizer_data = array(
|
|
'Organizer' => $meta_data['business_name'], // Use sanitized business name
|
|
'Phone' => $meta_data['business_phone'],
|
|
'Website' => $meta_data['business_website'],
|
|
'Email' => $meta_data['business_email'],
|
|
'Description' => $meta_data['business_description'],
|
|
'post_status' => 'publish', // Publish organizer immediately
|
|
'post_author' => $user_id // Associate with the new user
|
|
);
|
|
|
|
// Check if an organizer already exists for this user
|
|
$existing_organizer_id = get_user_meta($user_id, 'hvac_organizer_id', true);
|
|
|
|
if ($existing_organizer_id && get_post_type($existing_organizer_id) === Tribe__Events__Main::ORGANIZER_POST_TYPE) {
|
|
// Update existing organizer
|
|
$organizer_data['ID'] = $existing_organizer_id;
|
|
$organizer_id = tribe_update_organizer($existing_organizer_id, $organizer_data);
|
|
|
|
} else {
|
|
// Create new organizer
|
|
$organizer_id = tribe_create_organizer($organizer_data);
|
|
|
|
}
|
|
|
|
if (is_wp_error($organizer_id)) {
|
|
return false;
|
|
} elseif (!$organizer_id || $organizer_id === 0) { // Check for 0 as well
|
|
|
|
return false;
|
|
}
|
|
|
|
// Store custom fields as post meta
|
|
if ($organizer_id) {
|
|
// Store headquarters location
|
|
if (!empty($meta_data['org_headquarters_city'])) {
|
|
update_post_meta($organizer_id, '_hvac_org_headquarters_city', $meta_data['org_headquarters_city']);
|
|
}
|
|
if (!empty($meta_data['org_headquarters_state'])) {
|
|
update_post_meta($organizer_id, '_hvac_org_headquarters_state', $meta_data['org_headquarters_state']);
|
|
}
|
|
if (!empty($meta_data['org_headquarters_country'])) {
|
|
update_post_meta($organizer_id, '_hvac_org_headquarters_country', $meta_data['org_headquarters_country']);
|
|
}
|
|
|
|
// Store training capabilities
|
|
update_post_meta($organizer_id, '_hvac_business_type', $meta_data['business_type']);
|
|
update_post_meta($organizer_id, '_hvac_training_audience', $meta_data['training_audience']);
|
|
update_post_meta($organizer_id, '_hvac_training_formats', $meta_data['training_formats']);
|
|
update_post_meta($organizer_id, '_hvac_training_locations', $meta_data['training_locations']);
|
|
update_post_meta($organizer_id, '_hvac_training_resources', $meta_data['training_resources']);
|
|
update_post_meta($organizer_id, '_hvac_annual_revenue_target', $meta_data['annual_revenue_target']);
|
|
|
|
// Handle logo upload
|
|
if ($org_logo_data) {
|
|
$this->handle_org_logo_upload($organizer_id, $org_logo_data);
|
|
}
|
|
}
|
|
|
|
return (int) $organizer_id;
|
|
}
|
|
|
|
/**
|
|
* Create or update a Venue profile linked to the user using sanitized data.
|
|
*
|
|
* @param int $user_id The user ID.
|
|
* @param array $meta_data Array of sanitized user meta data.
|
|
* @return int|false Venue Post ID on success, false on failure.
|
|
*/
|
|
private function create_training_venue($user_id, $meta_data) {
|
|
if (!class_exists('Tribe__Events__Main') || !function_exists('tribe_create_venue')) {
|
|
|
|
return false;
|
|
}
|
|
|
|
// Use the already processed state/province from meta
|
|
$state_province = $meta_data['user_state'];
|
|
|
|
// Determine venue name
|
|
$venue_name = !empty($meta_data['venue_name']) ? $meta_data['venue_name'] : $meta_data['business_name'] . ' of ' . $meta_data['user_city'];
|
|
|
|
$venue_data = array(
|
|
'Venue' => $venue_name,
|
|
'Country' => $meta_data['user_country'],
|
|
'Address' => $meta_data['venue_address'], // Now we have a specific address field
|
|
'City' => $meta_data['user_city'],
|
|
'StateProvince' => $state_province,
|
|
'State' => $state_province, // Also set State field
|
|
'Province' => $state_province, // Also set Province field
|
|
'Zip' => $meta_data['user_zip'],
|
|
'Phone' => !empty($meta_data['venue_phone']) ? $meta_data['venue_phone'] : $meta_data['business_phone'],
|
|
'Website' => !empty($meta_data['venue_website']) ? $meta_data['venue_website'] : $meta_data['business_website'],
|
|
'post_status' => 'publish', // Publish venue immediately
|
|
'post_author' => $user_id // Associate with the new user
|
|
);
|
|
|
|
// Check if a venue already exists for this user
|
|
$existing_venue_id = get_user_meta($user_id, 'hvac_venue_id', true);
|
|
|
|
if ($existing_venue_id && get_post_type($existing_venue_id) === Tribe__Events__Main::VENUE_POST_TYPE) {
|
|
// Update existing venue
|
|
$venue_data['ID'] = $existing_venue_id;
|
|
$venue_id = tribe_update_venue($existing_venue_id, $venue_data);
|
|
|
|
} else {
|
|
// Create new venue
|
|
$venue_id = tribe_create_venue($venue_data);
|
|
|
|
}
|
|
|
|
if (is_wp_error($venue_id)) {
|
|
return false;
|
|
} elseif (!$venue_id || $venue_id === 0) { // Check for 0 as well
|
|
|
|
return false;
|
|
}
|
|
|
|
return (int) $venue_id;
|
|
}
|
|
|
|
/**
|
|
* Send notification email to admin about new registration
|
|
*
|
|
* @param int $user_id The ID of the newly registered user.
|
|
* @param array $data The raw submitted form data (used for notification content).
|
|
*/
|
|
private function send_admin_notification($user_id, $data) {
|
|
$admin_email = get_option('admin_email');
|
|
if (!$admin_email) {
|
|
|
|
return;
|
|
}
|
|
|
|
$user_info = get_userdata($user_id);
|
|
if (!$user_info) {
|
|
|
|
return;
|
|
}
|
|
|
|
$subject = sprintf('%s - New HVAC Trainer Registration Pending Approval', get_bloginfo('name'));
|
|
|
|
// Use sanitized data for the email body where appropriate
|
|
$message = "A new HVAC trainer has registered and is awaiting approval.\n\n";
|
|
$message .= "Details:\n";
|
|
$message .= "Username: " . $user_info->user_login . "\n";
|
|
$message .= "Email: " . $user_info->user_email . "\n";
|
|
// Use the sanitized first/last name from user_info if available, fallback to data
|
|
$first_name = $user_info->first_name ?: sanitize_text_field($data['first_name']);
|
|
$last_name = $user_info->last_name ?: sanitize_text_field($data['last_name']);
|
|
$message .= "Name: " . $first_name . " " . $last_name . "\n";
|
|
$message .= "Organization Name: " . sanitize_text_field($data['business_name']) . "\n"; // Use raw data as meta might not be fully updated yet? Safer to use raw.
|
|
$message .= "Application Details:\n" . wp_kses_post($data['application_details']) . "\n\n"; // Use wp_kses_post for safety
|
|
|
|
// Add link to user profile in admin
|
|
$profile_link = admin_url('user-edit.php?user_id=' . $user_id);
|
|
$message .= "Approve/Deny User: " . $profile_link . "\n";
|
|
|
|
$headers = array('Content-Type: text/plain; charset=UTF-8');
|
|
|
|
if (wp_mail($admin_email, $subject, $message, $headers)) {
|
|
|
|
} else {
|
|
|
|
// Consider adding an error to the transient if email failure is critical?
|
|
// $errors['notification'] = 'Admin notification failed. Registration complete but please contact support.';
|
|
}
|
|
}
|
|
|
|
// TODO: Add send_user_pending_notification()
|
|
// TODO: Add send_user_approved_notification()
|
|
// TODO: Add send_user_denied_notification()
|
|
|
|
/**
|
|
* Renders the edit profile form shortcode output
|
|
*/
|
|
public function render_edit_profile_form() {
|
|
if (!is_user_logged_in()) {
|
|
return '<p>Please log in to edit your profile.</p>';
|
|
}
|
|
|
|
// We don't need to do anything here since the template file already handles the form rendering
|
|
// Just set up the shortcode to point to the template
|
|
ob_start();
|
|
include HVAC_PLUGIN_DIR . 'templates/template-edit-profile.php';
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Process profile update submission from the edit profile form
|
|
*/
|
|
public function process_profile_update() {
|
|
// Only logged-in users can update profiles
|
|
if (!is_user_logged_in()) {
|
|
wp_redirect(home_url('/community-login/'));
|
|
exit;
|
|
}
|
|
|
|
$user_id = get_current_user_id();
|
|
$user = get_userdata($user_id);
|
|
$errors = [];
|
|
$submitted_data = $_POST;
|
|
$profile_page_url = home_url('/trainer/profile/edit/');
|
|
|
|
// Verify nonce
|
|
if (!isset($_POST['hvac_profile_nonce']) || !wp_verify_nonce($_POST['hvac_profile_nonce'], 'hvac_update_profile')) {
|
|
$errors['nonce'] = 'Security check failed. Please try submitting the form again.';
|
|
|
|
$this->redirect_with_profile_errors($errors, $profile_page_url);
|
|
// No need for return/exit here, redirect_with_errors exits.
|
|
}
|
|
|
|
// File Upload Handling
|
|
$profile_image_data = null;
|
|
if (isset($_FILES['profile_image']) && $_FILES['profile_image']['error'] !== UPLOAD_ERR_NO_FILE) {
|
|
if ($_FILES['profile_image']['error'] === UPLOAD_ERR_OK) {
|
|
// Check if it's actually an uploaded file
|
|
if (!is_uploaded_file($_FILES['profile_image']['tmp_name'])) {
|
|
$errors['profile_image'] = 'File upload error (invalid temp file).';
|
|
|
|
} else {
|
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$mime_type = finfo_file($finfo, $_FILES['profile_image']['tmp_name']);
|
|
finfo_close($finfo);
|
|
|
|
if (!in_array($mime_type, $allowed_types)) {
|
|
$errors['profile_image'] = 'Invalid file type detected (' . esc_html($mime_type) . '). Please upload a JPG, PNG, or GIF.';
|
|
|
|
} else {
|
|
$profile_image_data = $_FILES['profile_image']; // Store the whole $_FILES entry
|
|
|
|
}
|
|
}
|
|
} else {
|
|
$errors['profile_image'] = 'There was an error uploading the profile image. Code: ' . $_FILES['profile_image']['error'];
|
|
|
|
}
|
|
}
|
|
|
|
// Validate form data
|
|
$validation_errors = $this->validate_profile_update($submitted_data, $user);
|
|
$errors = array_merge($errors, $validation_errors);
|
|
|
|
// Process if no errors
|
|
if (empty($errors)) {
|
|
|
|
$update_success = $this->update_user_profile($user_id, $submitted_data, $profile_image_data);
|
|
|
|
if (is_wp_error($update_success)) {
|
|
$errors['account'] = $update_success->get_error_message();
|
|
$this->redirect_with_profile_errors($errors, $profile_page_url);
|
|
} elseif ($update_success) {
|
|
|
|
// Redirect to the profile page with success message
|
|
wp_safe_redirect(add_query_arg('updated', '1', $profile_page_url));
|
|
exit;
|
|
} else {
|
|
$errors['account'] = 'An unknown error occurred during profile update. Please try again.';
|
|
|
|
$this->redirect_with_profile_errors($errors, $profile_page_url);
|
|
}
|
|
} else {
|
|
$this->redirect_with_profile_errors($errors, $profile_page_url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to store profile errors in transient and redirect back to the form page.
|
|
*
|
|
* @param array $errors Array of error messages.
|
|
* @param string $redirect_url The URL to redirect back to.
|
|
*/
|
|
private function redirect_with_profile_errors($errors, $redirect_url) {
|
|
$transient_id = uniqid(); // Generate unique ID for transient key
|
|
$transient_key = self::PROFILE_TRANSIENT_PREFIX . $transient_id;
|
|
$transient_data = [
|
|
'errors' => $errors,
|
|
];
|
|
// Store for 5 minutes
|
|
set_transient($transient_key, $transient_data, MINUTE_IN_SECONDS * 5);
|
|
|
|
// Add query arguments to the redirect URL
|
|
$redirect_url = add_query_arg([
|
|
'prof_error' => '1',
|
|
'tid' => $transient_id,
|
|
], $redirect_url);
|
|
|
|
wp_safe_redirect($redirect_url);
|
|
exit; // Stop execution after redirect
|
|
}
|
|
|
|
/**
|
|
* Validate profile update data
|
|
*
|
|
* @param array $data Submitted form data ($_POST).
|
|
* @param WP_User $user Current user object.
|
|
* @return array Array of errors, empty if valid.
|
|
*/
|
|
public function validate_profile_update($data, $user) {
|
|
|
|
$errors = array();
|
|
|
|
// Required field validation
|
|
$required_fields = [
|
|
'first_name' => 'First Name',
|
|
'last_name' => 'Last Name',
|
|
'display_name' => 'Display Name',
|
|
'user_email' => 'Email',
|
|
'description' => 'Biographical Info',
|
|
'business_name' => 'Business Name',
|
|
'business_phone' => 'Business Phone',
|
|
'business_email' => 'Business Email',
|
|
'business_description' => 'Business Description',
|
|
'user_country' => 'Country',
|
|
'user_state' => 'State/Province',
|
|
'user_city' => 'City',
|
|
'user_zip' => 'Zip/Postal Code',
|
|
'business_type' => 'Business Type',
|
|
'role' => 'Role',
|
|
];
|
|
|
|
foreach ($required_fields as $field => $label) {
|
|
// Use trim to catch spaces-only input
|
|
if (empty($data[$field]) || trim($data[$field]) === '') {
|
|
$errors[$field] = $label . ' is required.';
|
|
}
|
|
}
|
|
|
|
// Required checkbox groups
|
|
$required_checkboxes = [
|
|
'training_audience' => 'Training Audience',
|
|
'training_formats' => 'Training Formats',
|
|
'training_locations' => 'Training Locations',
|
|
'training_resources' => 'Training Resources',
|
|
];
|
|
foreach ($required_checkboxes as $field => $label) {
|
|
// Check if the key exists and is a non-empty array
|
|
if (empty($data[$field]) || !is_array($data[$field])) {
|
|
$errors[$field] = 'Please select at least one option for ' . $label . '.';
|
|
}
|
|
}
|
|
|
|
// Email validation
|
|
if (!empty($data['user_email']) && !is_email($data['user_email'])) {
|
|
$errors['user_email'] = 'Please enter a valid email address.';
|
|
}
|
|
if (!empty($data['business_email']) && !is_email($data['business_email'])) {
|
|
$errors['business_email'] = 'Please enter a valid business email address.';
|
|
}
|
|
|
|
// Email exists validation (only if email is changed and valid)
|
|
if (empty($errors['user_email']) && !empty($data['user_email']) && $data['user_email'] !== $user->user_email && email_exists($data['user_email'])) {
|
|
$errors['user_email'] = 'This email address is already registered to another account.';
|
|
}
|
|
|
|
// URL validation (optional fields)
|
|
if (!empty($data['user_url']) && !filter_var($data['user_url'], FILTER_VALIDATE_URL)) {
|
|
$errors['user_url'] = 'Please enter a valid URL for your personal website.';
|
|
}
|
|
if (!empty($data['user_linkedin']) && !filter_var($data['user_linkedin'], FILTER_VALIDATE_URL)) {
|
|
$errors['user_linkedin'] = 'Please enter a valid URL for your LinkedIn profile.';
|
|
}
|
|
if (!empty($data['business_website']) && !filter_var($data['business_website'], FILTER_VALIDATE_URL)) {
|
|
$errors['business_website'] = 'Please enter a valid URL for your business website.';
|
|
}
|
|
|
|
// State/Province 'Other' validation
|
|
if (!empty($data['user_country'])) {
|
|
if ($data['user_country'] !== 'United States' && $data['user_country'] !== 'Canada') {
|
|
// If country is not US/CA, state *must* be 'Other'
|
|
if (empty($data['user_state']) || $data['user_state'] !== 'Other') {
|
|
$errors['user_state'] = 'Please select "Other" for State/Province if your country is not US or Canada.';
|
|
} elseif (empty($data['user_state_other']) || trim($data['user_state_other']) === '') {
|
|
// If state is 'Other', the text input must not be empty
|
|
$errors['user_state_other'] = 'Please enter your state/province.';
|
|
}
|
|
} elseif (!empty($data['user_state'])) {
|
|
// If country is US/CA
|
|
if ($data['user_state'] === 'Other') {
|
|
// State cannot be 'Other' if country is US/CA
|
|
$errors['user_state'] = 'Please select your state/province from the list.';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Password validation (only if user is attempting to change password)
|
|
if (!empty($data['current_password']) || !empty($data['new_password']) || !empty($data['confirm_new_password'])) {
|
|
// Verify current password
|
|
if (empty($data['current_password'])) {
|
|
$errors['current_password'] = 'Please enter your current password.';
|
|
} elseif (!wp_check_password($data['current_password'], $user->user_pass, $user->ID)) {
|
|
$errors['current_password'] = 'Current password is incorrect.';
|
|
}
|
|
|
|
// Validate new password
|
|
if (empty($data['new_password'])) {
|
|
$errors['new_password'] = 'Please enter a new password.';
|
|
} elseif (strlen($data['new_password']) < 8) {
|
|
$errors['new_password'] = 'Password must be at least 8 characters long.';
|
|
} elseif (!preg_match('/[A-Z]/', $data['new_password'])) {
|
|
$errors['new_password'] = 'Password must contain at least one uppercase letter.';
|
|
} elseif (!preg_match('/[a-z]/', $data['new_password'])) {
|
|
$errors['new_password'] = 'Password must contain at least one lowercase letter.';
|
|
} elseif (!preg_match('/[0-9]/', $data['new_password'])) {
|
|
$errors['new_password'] = 'Password must contain at least one number.';
|
|
}
|
|
|
|
// Confirm new password
|
|
if (empty($data['confirm_new_password'])) {
|
|
$errors['confirm_new_password'] = 'Please confirm your new password.';
|
|
} elseif ($data['new_password'] !== $data['confirm_new_password']) {
|
|
$errors['confirm_new_password'] = 'New passwords do not match.';
|
|
}
|
|
}
|
|
|
|
// Role validation - ensure it's one of the allowed values
|
|
if (!empty($data['role'])) {
|
|
$allowed_roles = ['technician', 'installer', 'supervisor', 'manager', 'trainer', 'consultant', 'sales', 'engineer', 'owner', 'other'];
|
|
if (!in_array($data['role'], $allowed_roles)) {
|
|
$errors['role'] = 'Please select a valid role.';
|
|
}
|
|
}
|
|
|
|
// Certification fields validation (only if user can edit them)
|
|
if (current_user_can('administrator') || current_user_can('hvac_master_trainer')) {
|
|
// Certification type validation
|
|
if (!empty($data['certification_type'])) {
|
|
$allowed_cert_types = ['Certified measureQuick Trainer', 'Certified measureQuick Champion'];
|
|
if (!in_array($data['certification_type'], $allowed_cert_types)) {
|
|
$errors['certification_type'] = 'Please select a valid certification type.';
|
|
}
|
|
}
|
|
|
|
// Certification status validation
|
|
if (!empty($data['certification_status'])) {
|
|
$allowed_cert_statuses = ['Active', 'Expired', 'Pending', 'Disabled'];
|
|
if (!in_array($data['certification_status'], $allowed_cert_statuses)) {
|
|
$errors['certification_status'] = 'Please select a valid certification status.';
|
|
}
|
|
}
|
|
|
|
// Date certified validation
|
|
if (!empty($data['date_certified'])) {
|
|
$date = DateTime::createFromFormat('Y-m-d', $data['date_certified']);
|
|
if (!$date || $date->format('Y-m-d') !== $data['date_certified']) {
|
|
$errors['date_certified'] = 'Please enter a valid date in YYYY-MM-DD format.';
|
|
} elseif ($date > new DateTime()) {
|
|
$errors['date_certified'] = 'Certification date cannot be in the future.';
|
|
}
|
|
}
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Update the user profile with submitted data
|
|
*
|
|
* @param int $user_id The user ID to update.
|
|
* @param array $data The submitted form data.
|
|
* @param array|null $profile_image_data The profile image file data, if any.
|
|
* @return bool|WP_Error True on success, WP_Error on failure.
|
|
*/
|
|
private function update_user_profile($user_id, $data, $profile_image_data = null) {
|
|
// Sanitize and prepare user data for update
|
|
$userdata = array(
|
|
'ID' => $user_id,
|
|
'first_name' => sanitize_text_field($data['first_name']),
|
|
'last_name' => sanitize_text_field($data['last_name']),
|
|
'display_name' => sanitize_text_field($data['display_name']),
|
|
'user_email' => sanitize_email($data['user_email']),
|
|
'user_url' => !empty($data['user_url']) ? esc_url_raw($data['user_url']) : '',
|
|
'description' => wp_kses_post($data['description']),
|
|
);
|
|
|
|
// Handle password update if provided
|
|
if (!empty($data['new_password']) && !empty($data['current_password']) && !empty($data['confirm_new_password'])) {
|
|
$userdata['user_pass'] = $data['new_password']; // wp_update_user will hash the password
|
|
}
|
|
|
|
// Update user data
|
|
$update_result = wp_update_user($userdata);
|
|
|
|
if (is_wp_error($update_result)) {
|
|
return $update_result;
|
|
}
|
|
|
|
// Update user meta
|
|
$meta_fields = [
|
|
'user_linkedin' => !empty($data['user_linkedin']) ? esc_url_raw($data['user_linkedin']) : '',
|
|
'personal_accreditation' => !empty($data['personal_accreditation']) ? sanitize_text_field($data['personal_accreditation']) : '',
|
|
'business_name' => sanitize_text_field($data['business_name']),
|
|
'business_phone' => sanitize_text_field($data['business_phone']),
|
|
'business_email' => sanitize_email($data['business_email']),
|
|
'business_website' => !empty($data['business_website']) ? esc_url_raw($data['business_website']) : '',
|
|
'business_description' => wp_kses_post($data['business_description']),
|
|
'user_country' => sanitize_text_field($data['user_country']),
|
|
'user_state' => ($data['user_state'] === 'Other' && isset($data['user_state_other'])) ? sanitize_text_field($data['user_state_other']) : sanitize_text_field($data['user_state']),
|
|
'user_city' => sanitize_text_field($data['user_city']),
|
|
'user_zip' => sanitize_text_field($data['user_zip']),
|
|
'business_type' => sanitize_text_field($data['business_type']),
|
|
'training_audience' => (!empty($data['training_audience']) && is_array($data['training_audience'])) ? array_map('sanitize_text_field', $data['training_audience']) : [],
|
|
'training_formats' => (!empty($data['training_formats']) && is_array($data['training_formats'])) ? array_map('sanitize_text_field', $data['training_formats']) : [],
|
|
'training_locations' => (!empty($data['training_locations']) && is_array($data['training_locations'])) ? array_map('sanitize_text_field', $data['training_locations']) : [],
|
|
'training_resources' => (!empty($data['training_resources']) && is_array($data['training_resources'])) ? array_map('sanitize_text_field', $data['training_resources']) : [],
|
|
'annual_revenue_target' => !empty($data['annual_revenue_target']) ? intval($data['annual_revenue_target']) : '',
|
|
'role' => sanitize_text_field($data['role']),
|
|
];
|
|
|
|
// Add certification fields if user has permission to edit them
|
|
if (current_user_can('administrator') || current_user_can('hvac_master_trainer')) {
|
|
$meta_fields['date_certified'] = sanitize_text_field($data['date_certified']);
|
|
$meta_fields['certification_type'] = sanitize_text_field($data['certification_type']);
|
|
$meta_fields['certification_status'] = sanitize_text_field($data['certification_status']);
|
|
}
|
|
|
|
foreach ($meta_fields as $key => $value) {
|
|
update_user_meta($user_id, $key, $value);
|
|
}
|
|
|
|
// Handle profile image upload if provided
|
|
if ($profile_image_data) {
|
|
|
|
// We don't need the return value here unless we want to report specific upload errors
|
|
$this->handle_profile_image_upload($user_id, $profile_image_data);
|
|
}
|
|
|
|
// Update organizer profile if it exists
|
|
$organizer_id = get_user_meta($user_id, 'hvac_organizer_id', true);
|
|
if ($organizer_id && get_post_type($organizer_id) === Tribe__Events__Main::ORGANIZER_POST_TYPE) {
|
|
|
|
$this->create_organizer_profile($user_id, $meta_fields);
|
|
}
|
|
|
|
// Update venue profile if it exists
|
|
$venue_id = get_user_meta($user_id, 'hvac_venue_id', true);
|
|
if ($venue_id && get_post_type($venue_id) === Tribe__Events__Main::VENUE_POST_TYPE) {
|
|
|
|
$this->create_training_venue($user_id, $meta_fields);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get list of countries (simplified)
|
|
*/
|
|
private function get_country_list() {
|
|
// In a real application, use a more comprehensive list or library
|
|
return array(
|
|
'US' => 'United States',
|
|
'CA' => 'Canada',
|
|
// Add more countries as needed
|
|
'GB' => 'United Kingdom',
|
|
'AU' => 'Australia',
|
|
// ...
|
|
);
|
|
}
|
|
/**
|
|
* Get list of US states
|
|
*/
|
|
private function get_us_states() {
|
|
// Use state abbreviations as keys if preferred by JS/validation
|
|
return array(
|
|
'AL' => 'Alabama', 'AK' => 'Alaska', 'AZ' => 'Arizona', 'AR' => 'Arkansas', 'CA' => 'California',
|
|
'CO' => 'Colorado', 'CT' => 'Connecticut', 'DE' => 'Delaware', 'DC' => 'District of Columbia', 'FL' => 'Florida',
|
|
'GA' => 'Georgia', 'HI' => 'Hawaii', 'ID' => 'Idaho', 'IL' => 'Illinois', 'IN' => 'Indiana',
|
|
'IA' => 'Iowa', 'KS' => 'Kansas', 'KY' => 'Kentucky', 'LA' => 'Louisiana', 'ME' => 'Maine',
|
|
'MD' => 'Maryland', 'MA' => 'Massachusetts', 'MI' => 'Michigan', 'MN' => 'Minnesota', 'MS' => 'Mississippi',
|
|
'MO' => 'Missouri', 'MT' => 'Montana', 'NE' => 'Nebraska', 'NV' => 'Nevada', 'NH' => 'New Hampshire',
|
|
'NJ' => 'New Jersey', 'NM' => 'New Mexico', 'NY' => 'New York', 'NC' => 'North Carolina', 'ND' => 'North Dakota',
|
|
'OH' => 'Ohio', 'OK' => 'Oklahoma', 'OR' => 'Oregon', 'PA' => 'Pennsylvania', 'RI' => 'Rhode Island',
|
|
'SC' => 'South Carolina', 'SD' => 'South Dakota', 'TN' => 'Tennessee', 'TX' => 'Texas', 'UT' => 'Utah',
|
|
'VT' => 'Vermont', 'VA' => 'Virginia', 'WA' => 'Washington', 'WV' => 'West Virginia', 'WI' => 'Wisconsin',
|
|
'WY' => 'Wyoming'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get list of Canadian provinces
|
|
*/
|
|
private function get_canadian_provinces() {
|
|
// Use province abbreviations as keys if preferred by JS/validation
|
|
return array(
|
|
'AB' => 'Alberta', 'BC' => 'British Columbia', 'MB' => 'Manitoba', 'NB' => 'New Brunswick',
|
|
'NL' => 'Newfoundland and Labrador', 'NS' => 'Nova Scotia', 'ON' => 'Ontario', 'PE' => 'Prince Edward Island',
|
|
'QC' => 'Quebec', 'SK' => 'Saskatchewan', 'NT' => 'Northwest Territories', 'NU' => 'Nunavut', 'YT' => 'Yukon'
|
|
);
|
|
}
|
|
|
|
} // End class HVAC_Registration
|