'_EventStartDate', 'event_end_date' => '_EventEndDate', 'event_timezone' => '_EventTimezone', 'event_url' => '_EventURL', 'venue_name' => '_VenueName', 'venue_address' => '_VenueAddress', 'venue_city' => '_VenueCity', 'venue_state' => '_VenueState', 'venue_zip' => '_VenueZip', 'venue_capacity' => '_VenueCapacity', 'organizer_name' => '_OrganizerName', 'organizer_email' => '_OrganizerEmail', 'organizer_phone' => '_OrganizerPhone', 'organizer_website' => '_OrganizerWebsite', ]; /** * HVAC-specific meta field mapping * * @var array */ private array $hvac_meta_mapping = [ 'trainer_requirements' => '_hvac_trainer_requirements', 'certification_levels' => '_hvac_certification_levels', 'equipment_needed' => '_hvac_equipment_needed', 'prerequisites' => '_hvac_prerequisites', ]; /** * Required TEC meta fields with default values * * @var array */ private array $required_tec_meta = [ '_EventOrigin' => 'hvac-community-events', '_EventShowMap' => '1', '_EventShowMapLink' => '1', '_tribe_default_ticket_provider' => 'TEC\\Tickets\\Commerce\\Module', '_tribe_ticket_capacity' => '0', '_tribe_ticket_version' => '5.25.1.1', ]; /** * Constructor */ private function __construct() { $this->init_hooks(); } /** * Initialize WordPress hooks */ private function init_hooks(): void { // Handle form submissions add_action('template_redirect', [$this, 'handle_event_form_submission']); // Handle AJAX submissions (for future enhancement) add_action('wp_ajax_hvac_create_event', [$this, 'ajax_create_event']); add_action('wp_ajax_hvac_update_event', [$this, 'ajax_update_event']); } /** * Handle event form submission */ public function handle_event_form_submission(): void { // Only process POST requests with our nonce if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['hvac_event_form_nonce'])) { return; } // Verify nonce for security if (!HVAC_Security::verify_nonce($_POST['hvac_event_form_nonce'], 'hvac_event_form')) { wp_die(__('Security check failed. Please refresh the page and try again.', 'hvac-community-events')); return; } // Check user permissions if (!HVAC_Security::check_capability('edit_posts')) { wp_die(__('You do not have permission to create events.', 'hvac-community-events')); return; } try { // Create form builder instance for validation $event_form = new HVAC_Event_Form_Builder('hvac_event_form'); $event_form->create_event_form(); // Validate submitted data $validation_errors = $event_form->validate($_POST); if (!empty($validation_errors)) { // Store errors in session for display $this->store_form_errors($validation_errors); $this->store_form_data($_POST); return; } // Sanitize data $sanitized_data = $event_form->sanitize($_POST); // Determine if this is create or update $event_id = isset($sanitized_data['event_id']) ? absint($sanitized_data['event_id']) : 0; if ($event_id > 0) { // Update existing event $result = $this->update_event($event_id, $sanitized_data); } else { // Create new event $result = $this->create_event($sanitized_data); } if (is_wp_error($result)) { $this->store_form_errors(['general' => $result->get_error_message()]); $this->store_form_data($_POST); return; } // Success - redirect to prevent resubmission $redirect_url = add_query_arg(['event_created' => '1', 'event_id' => $result], wp_get_referer()); wp_safe_redirect($redirect_url); exit; } catch (Exception $e) { HVAC_Logger::error('Event form submission failed', 'Event_Post_Handler', [ 'error' => $e->getMessage(), 'user_id' => get_current_user_id(), 'post_data' => wp_json_encode($_POST) ]); $this->store_form_errors(['general' => 'An error occurred while processing your event. Please try again.']); $this->store_form_data($_POST); } } /** * Create new tribe_events post * * @param array $data Sanitized form data * @return int|WP_Error Post ID on success, WP_Error on failure */ public function create_event(array $data) { // Prepare post data $post_data = [ 'post_type' => 'tribe_events', 'post_title' => $data['event_title'] ?? '', 'post_content' => $data['event_description'] ?? '', 'post_excerpt' => $data['event_excerpt'] ?? '', 'post_status' => $this->get_post_status_for_user(), 'post_author' => get_current_user_id(), 'meta_input' => $this->prepare_meta_fields($data), ]; // Create the post $event_id = wp_insert_post($post_data, true); if (is_wp_error($event_id)) { return $event_id; } // Handle featured image upload $this->handle_featured_image_upload($event_id, $data); // Handle venue creation/assignment $venue_id = $this->handle_venue_data($data); if ($venue_id) { update_post_meta($event_id, '_EventVenueID', $venue_id); } // Handle organizer creation/assignment $organizer_id = $this->handle_organizer_data($data); if ($organizer_id) { update_post_meta($event_id, '_EventOrganizerID', $organizer_id); } // Calculate and store additional TEC meta fields $this->calculate_tec_meta_fields($event_id, $data); // Log successful creation HVAC_Logger::info('Event created successfully', 'Event_Post_Handler', [ 'event_id' => $event_id, 'user_id' => get_current_user_id(), 'event_title' => $data['event_title'] ?? 'Unknown' ]); return $event_id; } /** * Update existing tribe_events post * * @param int $event_id Event post ID * @param array $data Sanitized form data * @return int|WP_Error Post ID on success, WP_Error on failure */ public function update_event(int $event_id, array $data) { // Verify user can edit this event if (!current_user_can('edit_post', $event_id)) { return new WP_Error('permission_denied', 'You do not have permission to edit this event.'); } // Prepare post data $post_data = [ 'ID' => $event_id, 'post_title' => $data['event_title'] ?? '', 'post_content' => $data['event_description'] ?? '', 'post_excerpt' => $data['event_excerpt'] ?? '', ]; // Update the post $result = wp_update_post($post_data, true); if (is_wp_error($result)) { return $result; } // Update meta fields $meta_fields = $this->prepare_meta_fields($data); foreach ($meta_fields as $key => $value) { update_post_meta($event_id, $key, $value); } // Handle featured image upload $this->handle_featured_image_upload($event_id, $data); // Update venue $venue_id = $this->handle_venue_data($data); if ($venue_id) { update_post_meta($event_id, '_EventVenueID', $venue_id); } // Update organizer $organizer_id = $this->handle_organizer_data($data); if ($organizer_id) { update_post_meta($event_id, '_EventOrganizerID', $organizer_id); } // Recalculate TEC meta fields $this->calculate_tec_meta_fields($event_id, $data); // Log successful update HVAC_Logger::info('Event updated successfully', 'Event_Post_Handler', [ 'event_id' => $event_id, 'user_id' => get_current_user_id(), 'event_title' => $data['event_title'] ?? 'Unknown' ]); return $event_id; } /** * Prepare meta fields for post creation/update * * @param array $data Sanitized form data * @return array Meta fields array */ private function prepare_meta_fields(array $data): array { $meta_fields = []; // Map TEC meta fields foreach ($this->tec_meta_mapping as $form_field => $meta_key) { if (isset($data[$form_field]) && $data[$form_field] !== '') { $meta_fields[$meta_key] = $data[$form_field]; } } // Map HVAC-specific meta fields foreach ($this->hvac_meta_mapping as $form_field => $meta_key) { if (isset($data[$form_field]) && $data[$form_field] !== '') { $meta_fields[$meta_key] = $data[$form_field]; } } // Add required TEC meta fields with defaults $meta_fields = array_merge($meta_fields, $this->required_tec_meta); // Handle special datetime processing if (isset($data['event_start_date'])) { $meta_fields['_EventStartDate'] = $this->format_datetime_for_tec($data['event_start_date'], $data['event_timezone'] ?? ''); $meta_fields['_EventStartDateUTC'] = $this->convert_to_utc($data['event_start_date'], $data['event_timezone'] ?? ''); } if (isset($data['event_end_date'])) { $meta_fields['_EventEndDate'] = $this->format_datetime_for_tec($data['event_end_date'], $data['event_timezone'] ?? ''); $meta_fields['_EventEndDateUTC'] = $this->convert_to_utc($data['event_end_date'], $data['event_timezone'] ?? ''); } // Handle timezone if (isset($data['event_timezone'])) { $meta_fields['_EventTimezone'] = $data['event_timezone']; $meta_fields['_EventTimezoneAbbr'] = $this->get_timezone_abbreviation($data['event_timezone']); } return $meta_fields; } /** * Calculate and store additional TEC meta fields * * @param int $event_id Event post ID * @param array $data Sanitized form data */ private function calculate_tec_meta_fields(int $event_id, array $data): void { // Calculate event duration if (isset($data['event_start_date']) && isset($data['event_end_date'])) { $start_time = strtotime($data['event_start_date']); $end_time = strtotime($data['event_end_date']); $duration = $end_time - $start_time; update_post_meta($event_id, '_EventDuration', $duration); } // Update modified fields timestamp update_post_meta($event_id, '_tribe_modified_fields', [ '_EventStartDate' => time(), '_EventEndDate' => time(), '_EventVenueID' => time(), '_EventOrganizerID' => time(), '_EventURL' => time(), '_EventTimezone' => time(), '_EventOrigin' => time(), ]); // Mark as non-duplicated event update_post_meta($event_id, '_EventOccurrencesCount', 1); update_post_meta($event_id, '_edit_last', get_current_user_id()); } /** * Handle venue data - create or update venue post * * @param array $data Sanitized form data * @return int|null Venue post ID or null */ private function handle_venue_data(array $data): ?int { if (empty($data['venue_name'])) { return null; } // Check if venue already exists $existing_venue = get_posts([ 'post_type' => 'tribe_venue', 'post_title' => $data['venue_name'], 'posts_per_page' => 1, 'post_status' => 'publish' ]); if (!empty($existing_venue)) { return $existing_venue[0]->ID; } // Create new venue $venue_data = [ 'post_type' => 'tribe_venue', 'post_title' => $data['venue_name'], 'post_status' => 'publish', 'meta_input' => [ '_VenueAddress' => $data['venue_address'] ?? '', '_VenueCity' => $data['venue_city'] ?? '', '_VenueState' => $data['venue_state'] ?? '', '_VenueZip' => $data['venue_zip'] ?? '', '_VenueCapacity' => $data['venue_capacity'] ?? '', ] ]; // Add coordinates if available if (!empty($data['venue_latitude']) && !empty($data['venue_longitude'])) { $venue_data['meta_input']['_VenueLat'] = $data['venue_latitude']; $venue_data['meta_input']['_VenueLng'] = $data['venue_longitude']; } $venue_id = wp_insert_post($venue_data); return is_wp_error($venue_id) ? null : $venue_id; } /** * Handle organizer data - create or update organizer post * * @param array $data Sanitized form data * @return int|null Organizer post ID or null */ private function handle_organizer_data(array $data): ?int { if (empty($data['organizer_name'])) { return null; } // Check if organizer already exists $existing_organizer = get_posts([ 'post_type' => 'tribe_organizer', 'post_title' => $data['organizer_name'], 'posts_per_page' => 1, 'post_status' => 'publish' ]); if (!empty($existing_organizer)) { return $existing_organizer[0]->ID; } // Create new organizer $organizer_data = [ 'post_type' => 'tribe_organizer', 'post_title' => $data['organizer_name'], 'post_status' => 'publish', 'meta_input' => [ '_OrganizerEmail' => $data['organizer_email'] ?? '', '_OrganizerPhone' => $data['organizer_phone'] ?? '', '_OrganizerWebsite' => $data['organizer_website'] ?? '', ] ]; $organizer_id = wp_insert_post($organizer_data); return is_wp_error($organizer_id) ? null : $organizer_id; } /** * Handle featured image upload * * @param int $event_id Event post ID * @param array $data Form data */ private function handle_featured_image_upload(int $event_id, array $data): void { if (empty($_FILES['event_featured_image']['name'])) { return; } // WordPress file upload handling require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); $file = $_FILES['event_featured_image']; // Validate file type $allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; if (!in_array($file['type'], $allowed_types)) { return; } // Handle upload $attachment_id = media_handle_upload('event_featured_image', $event_id); if (!is_wp_error($attachment_id)) { set_post_thumbnail($event_id, $attachment_id); } } /** * Get appropriate post status for current user * * @return string Post status */ private function get_post_status_for_user(): string { $user = wp_get_current_user(); // Master trainers can publish directly if (in_array('hvac_master_trainer', $user->roles)) { return 'publish'; } // Regular trainers create drafts for approval if (in_array('hvac_trainer', $user->roles)) { return 'draft'; } // Administrators can publish if (in_array('administrator', $user->roles)) { return 'publish'; } // Default to draft return 'draft'; } /** * Format datetime for TEC compatibility * * @param string $datetime Datetime string * @param string $timezone Timezone string * @return string Formatted datetime */ private function format_datetime_for_tec(string $datetime, string $timezone): string { if (empty($datetime)) { return ''; } // TEC expects 'Y-m-d H:i:s' format $timestamp = strtotime($datetime); return date('Y-m-d H:i:s', $timestamp); } /** * Convert datetime to UTC for TEC * * @param string $datetime Datetime string * @param string $timezone Timezone string * @return string UTC datetime */ private function convert_to_utc(string $datetime, string $timezone): string { if (empty($datetime) || empty($timezone)) { return ''; } try { $dt = new DateTime($datetime, new DateTimeZone($timezone)); $dt->setTimezone(new DateTimeZone('UTC')); return $dt->format('Y-m-d H:i:s'); } catch (Exception $e) { return ''; } } /** * Get timezone abbreviation * * @param string $timezone Timezone string * @return string Timezone abbreviation */ private function get_timezone_abbreviation(string $timezone): string { try { $dt = new DateTime('now', new DateTimeZone($timezone)); return $dt->format('T'); } catch (Exception $e) { return 'UTC'; } } /** * Store form errors in session/transient * * @param array $errors Form errors */ private function store_form_errors(array $errors): void { set_transient('hvac_form_errors_' . get_current_user_id(), $errors, 300); // 5 minutes } /** * Store form data in session/transient for re-population * * @param array $data Form data */ private function store_form_data(array $data): void { // Remove sensitive data before storing unset($data['hvac_event_form_nonce']); set_transient('hvac_form_data_' . get_current_user_id(), $data, 300); // 5 minutes } /** * Get stored form errors * * @return array Form errors */ public function get_stored_errors(): array { $errors = get_transient('hvac_form_errors_' . get_current_user_id()); delete_transient('hvac_form_errors_' . get_current_user_id()); return is_array($errors) ? $errors : []; } /** * Get stored form data * * @return array Form data */ public function get_stored_data(): array { $data = get_transient('hvac_form_data_' . get_current_user_id()); delete_transient('hvac_form_data_' . get_current_user_id()); return is_array($data) ? $data : []; } /** * AJAX handler for event creation */ public function ajax_create_event(): void { // Verify nonce if (!HVAC_Security::verify_nonce($_POST['nonce'] ?? '', 'hvac_create_event')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Check permissions if (!HVAC_Security::check_capability('edit_posts')) { wp_send_json_error(['message' => 'Permission denied']); return; } try { $result = $this->create_event($_POST); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()]); return; } wp_send_json_success([ 'message' => 'Event created successfully', 'event_id' => $result, 'edit_url' => get_edit_post_link($result) ]); } catch (Exception $e) { wp_send_json_error(['message' => 'An error occurred while creating the event']); } } /** * AJAX handler for event updates */ public function ajax_update_event(): void { // Verify nonce if (!HVAC_Security::verify_nonce($_POST['nonce'] ?? '', 'hvac_update_event')) { wp_send_json_error(['message' => 'Security check failed']); return; } $event_id = absint($_POST['event_id'] ?? 0); if (!$event_id) { wp_send_json_error(['message' => 'Invalid event ID']); return; } try { $result = $this->update_event($event_id, $_POST); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()]); return; } wp_send_json_success([ 'message' => 'Event updated successfully', 'event_id' => $result, 'edit_url' => get_edit_post_link($result) ]); } catch (Exception $e) { wp_send_json_error(['message' => 'An error occurred while updating the event']); } } }