diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 59d30f6c..8e31fa70 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -62,7 +62,17 @@ "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp plugin get the-events-calendar-community-events --field=status\")", "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp rewrite list | grep -i community\")", "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user set-role devadmin administrator\")", - "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user set-role ben@measurequick.com administrator\")" + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user set-role ben@measurequick.com administrator\")", + "mcp__zen__planner", + "Bash(git checkout:*)", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post list --post_type=tribe_events --posts_per_page=1 --format=json | jq ''.[0]'' 2>/dev/null\")", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post meta list 5737 --format=table\")", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post get 5731 --field=post_type\")", + "Bash(scripts/deploy.sh:*)", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post create --post_type=page --post_title=''Native Event Test'' --post_name=''native-event-test'' --post_status=publish --meta_input=''{\"\"_wp_page_template\"\":\"\"page-native-event-test.php\"\"}'' --format=ids\")", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e scp -o StrictHostKeyChecking=no /home/ben/dev/upskill-event-manager/templates/page-native-event-test.php roodev@146.190.76.204:/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/templates/)", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post get 6394 --format=table\")", + "Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post meta list 6394 --format=table\")" ], "deny": [], "ask": [], diff --git a/includes/class-hvac-event-post-handler.php b/includes/class-hvac-event-post-handler.php new file mode 100644 index 00000000..3a1af182 --- /dev/null +++ b/includes/class-hvac-event-post-handler.php @@ -0,0 +1,671 @@ + '_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']); + } + } +} \ No newline at end of file diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php index f34df8c1..79325c30 100644 --- a/includes/class-hvac-plugin.php +++ b/includes/class-hvac-plugin.php @@ -205,6 +205,9 @@ final class HVAC_Plugin { // Native event form builder (Phase 1A - Native WordPress Events) require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-event-form-builder.php'; + // Native event post handler (Phase 1B - tribe_events creation) + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-event-post-handler.php'; + // Unified Event Management System (replaces 8+ fragmented implementations) require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-event-manager.php'; diff --git a/templates/page-native-event-test.php b/templates/page-native-event-test.php index a2ba7703..d697d236 100644 --- a/templates/page-native-event-test.php +++ b/templates/page-native-event-test.php @@ -146,11 +146,16 @@ get_header(); // Sanitize data $sanitized_data = $event_form->sanitize($_POST); - // Process the form (this would normally create the event) - $success_message = 'Event form validation successful! Data has been sanitized and is ready for processing.'; - $form_submitted = true; + // Phase 1B: Create tribe_events post using native WordPress + $post_handler = HVAC_Event_Post_Handler::instance(); + $event_id = $post_handler->create_event($sanitized_data); - // In Phase 1B, this is where we would create the tribe_events post + if (!is_wp_error($event_id)) { + $success_message = "Event created successfully! Event ID: {$event_id}. Native WordPress event system is working."; + $form_submitted = true; + } else { + $form_errors['general'] = 'Event creation failed: ' . $event_id->get_error_message(); + } } else { $event_form->set_errors($form_errors); @@ -212,8 +217,9 @@ get_header(); ?>
-

Phase 1A Implementation Status:

+

Native WordPress Event System Implementation Status: