[ 'type' => 'datetime-local', 'class' => 'hvac-datetime-field', 'validate' => ['datetime'], 'sanitize' => 'datetime', ], 'venue-group' => [ 'type' => 'venue-group', 'class' => 'hvac-venue-group', 'wrapper_class' => 'form-group venue-group', ], 'organizer-group' => [ 'type' => 'organizer-group', 'class' => 'hvac-organizer-group', 'wrapper_class' => 'form-group organizer-group', ], ]; /** * WordPress timezone list for event timezone selection * * @var array|null */ private ?array $timezone_list = null; /** * Constructor with promoted property. * * @param string $nonce_action Nonce action for the form */ public function __construct(string $nonce_action = 'hvac_event_form') { parent::__construct($nonce_action); $this->init_event_specific_features(); } /** * Initialize event-specific features */ private function init_event_specific_features(): void { // Set form enctype for file uploads (featured images) $this->set_attributes(['enctype' => 'multipart/form-data']); // Load timezone list $this->timezone_list = $this->get_wordpress_timezones(); } /** * Add event datetime field * * @param array $field_config Field configuration * @return self */ public function add_datetime_field(array $field_config): self { $defaults = array_merge($this->event_field_defaults['datetime-local'], [ 'required' => true, 'description' => 'Select date and time for the event', ]); $field = wp_parse_args($field_config, $defaults); // Ensure proper ID and name if (empty($field['id']) && !empty($field['name'])) { $field['id'] = sanitize_html_class($field['name']); } return $this->add_field($field); } /** * Add event timezone selection field * * @param array $field_config Field configuration * @return self */ public function add_timezone_field(array $field_config = []): self { $defaults = [ 'type' => 'select', 'name' => 'event_timezone', 'label' => 'Event Timezone', 'options' => $this->timezone_list ?? [], 'value' => wp_timezone_string(), 'required' => true, 'class' => 'hvac-timezone-field', 'description' => 'Select the timezone for this event', ]; $field = wp_parse_args($field_config, $defaults); return $this->add_field($field); } /** * Add all-day event toggle * * @param array $field_config Field configuration * @return self */ public function add_all_day_field(array $field_config = []): self { $defaults = [ 'type' => 'checkbox', 'name' => 'event_all_day', 'label' => 'All Day Event', 'value' => '1', 'class' => 'hvac-all-day-field', 'description' => 'Check if this is an all-day event', ]; $field = wp_parse_args($field_config, $defaults); return $this->add_field($field); } /** * Add venue field group * * @param array $field_config Field configuration * @return self */ public function add_venue_group(array $field_config = []): self { $venue_fields = [ [ 'type' => 'text', 'name' => 'venue_name', 'label' => 'Venue Name', 'required' => true, 'class' => 'hvac-venue-name', 'placeholder' => 'Enter venue name', ], [ 'type' => 'textarea', 'name' => 'venue_address', 'label' => 'Address', 'required' => false, 'class' => 'hvac-venue-address', 'placeholder' => 'Street address', 'validate' => ['max_length' => 500], ], [ 'type' => 'text', 'name' => 'venue_city', 'label' => 'City', 'required' => false, 'class' => 'hvac-venue-city', ], [ 'type' => 'text', 'name' => 'venue_state', 'label' => 'State/Province', 'required' => false, 'class' => 'hvac-venue-state', ], [ 'type' => 'text', 'name' => 'venue_zip', 'label' => 'Postal Code', 'required' => false, 'class' => 'hvac-venue-zip', 'validate' => ['pattern' => '/^[A-Za-z0-9\s\-]{3,10}$/'], ], [ 'type' => 'number', 'name' => 'venue_capacity', 'label' => 'Venue Capacity', 'required' => false, 'class' => 'hvac-venue-capacity', 'validate' => ['min' => 1, 'max' => 10000], 'description' => 'Maximum number of attendees (optional)', ], [ 'type' => 'hidden', 'name' => 'venue_latitude', 'class' => 'hvac-venue-lat', ], [ 'type' => 'hidden', 'name' => 'venue_longitude', 'class' => 'hvac-venue-lng', ], ]; // Add all venue fields foreach ($venue_fields as $field) { $this->add_field($field); } return $this; } /** * Add organizer field group * * @param array $field_config Field configuration * @return self */ public function add_organizer_group(array $field_config = []): self { $organizer_fields = [ [ 'type' => 'text', 'name' => 'organizer_name', 'label' => 'Organizer Name', 'required' => true, 'class' => 'hvac-organizer-name', 'placeholder' => 'Event organizer name', ], [ 'type' => 'email', 'name' => 'organizer_email', 'label' => 'Organizer Email', 'required' => false, 'class' => 'hvac-organizer-email', 'validate' => ['email'], 'sanitize' => 'email', ], [ 'type' => 'tel', 'name' => 'organizer_phone', 'label' => 'Organizer Phone', 'required' => false, 'class' => 'hvac-organizer-phone', 'validate' => ['pattern' => '/^[\+]?[1-9][\d]{0,15}$/'], ], [ 'type' => 'url', 'name' => 'organizer_website', 'label' => 'Organizer Website', 'required' => false, 'class' => 'hvac-organizer-website', 'validate' => ['url'], 'sanitize' => 'url', 'placeholder' => 'https://example.com', ], ]; // Add all organizer fields foreach ($organizer_fields as $field) { $this->add_field($field); } return $this; } /** * Add HVAC-specific event fields * * @return self */ public function add_hvac_fields(): self { $hvac_fields = [ [ 'type' => 'select', 'name' => 'trainer_requirements', 'label' => 'Trainer Requirements', 'options' => $this->get_trainer_requirement_options(), 'required' => false, 'class' => 'hvac-trainer-requirements', 'description' => 'What type of trainer is required for this event?', ], [ 'type' => 'checkbox', 'name' => 'certification_levels', 'label' => 'Certification Levels', 'options' => $this->get_certification_level_options(), 'required' => false, 'class' => 'hvac-certification-levels', 'description' => 'Which certification levels does this event support?', ], [ 'type' => 'textarea', 'name' => 'equipment_needed', 'label' => 'Equipment Needed', 'required' => false, 'class' => 'hvac-equipment-needed', 'placeholder' => 'List any specific equipment attendees should bring', 'validate' => ['max_length' => 1000], ], [ 'type' => 'textarea', 'name' => 'prerequisites', 'label' => 'Prerequisites', 'required' => false, 'class' => 'hvac-prerequisites', 'placeholder' => 'Any required knowledge or certifications for attendees', 'validate' => ['max_length' => 1000], ], ]; // Add all HVAC-specific fields foreach ($hvac_fields as $field) { $this->add_field($field); } return $this; } /** * Add featured image upload field * * @param array $field_config Field configuration * @return self */ public function add_featured_image_field(array $field_config = []): self { $defaults = [ 'type' => 'file', 'name' => 'event_featured_image', 'label' => 'Featured Image', 'required' => false, 'class' => 'hvac-featured-image', 'description' => 'Upload a featured image for this event (JPG, PNG, WebP)', 'validate' => ['file_type' => ['jpg', 'jpeg', 'png', 'webp']], ]; $field = wp_parse_args($field_config, $defaults); return $this->add_field($field); } /** * Enhanced validation for event-specific fields * * @param array $data Form data to validate * @return array Validation errors */ public function validate(array $data): array { $errors = parent::validate($data); // Event datetime validation $errors = array_merge($errors, $this->validate_event_datetime($data)); // Venue validation $errors = array_merge($errors, $this->validate_venue_data($data)); // HVAC-specific validation $errors = array_merge($errors, $this->validate_hvac_fields($data)); return $errors; } /** * Validate event datetime fields * * @param array $data Form data * @return array Validation errors */ private function validate_event_datetime(array $data): array { $errors = []; 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']); if ($start_time === false) { $errors['event_start_date'] = 'Invalid start date format.'; } elseif ($start_time < time()) { $errors['event_start_date'] = 'Start date cannot be in the past.'; } if ($end_time === false) { $errors['event_end_date'] = 'Invalid end date format.'; } elseif ($start_time !== false && $end_time !== false && $end_time <= $start_time) { $errors['event_end_date'] = 'End date must be after start date.'; } } return $errors; } /** * Validate venue data * * @param array $data Form data * @return array Validation errors */ private function validate_venue_data(array $data): array { $errors = []; // Venue capacity validation if (!empty($data['venue_capacity'])) { $capacity = intval($data['venue_capacity']); if ($capacity < 1) { $errors['venue_capacity'] = 'Venue capacity must be at least 1.'; } elseif ($capacity > 10000) { $errors['venue_capacity'] = 'Venue capacity cannot exceed 10,000.'; } } return $errors; } /** * Validate HVAC-specific fields * * @param array $data Form data * @return array Validation errors */ private function validate_hvac_fields(array $data): array { $errors = []; // Validate trainer requirements against available options if (!empty($data['trainer_requirements'])) { $valid_requirements = array_keys($this->get_trainer_requirement_options()); if (!in_array($data['trainer_requirements'], $valid_requirements)) { $errors['trainer_requirements'] = 'Invalid trainer requirement selected.'; } } return $errors; } /** * Enhanced sanitization for event fields * * @param array $data Raw form data * @return array Sanitized data */ public function sanitize(array $data): array { $sanitized = parent::sanitize($data); // Event-specific sanitization $event_fields = [ 'event_start_date' => 'datetime', 'event_end_date' => 'datetime', 'event_timezone' => 'text', 'venue_name' => 'text', 'venue_address' => 'textarea', 'venue_city' => 'text', 'venue_state' => 'text', 'venue_zip' => 'text', 'venue_capacity' => 'int', 'organizer_name' => 'text', 'organizer_email' => 'email', 'organizer_phone' => 'text', 'organizer_website' => 'url', 'trainer_requirements' => 'text', 'equipment_needed' => 'textarea', 'prerequisites' => 'textarea', ]; foreach ($event_fields as $field => $sanitize_type) { if (isset($data[$field])) { switch ($sanitize_type) { case 'datetime': $sanitized[$field] = $this->sanitize_datetime($data[$field]); break; case 'int': $sanitized[$field] = absint($data[$field]); break; case 'email': $sanitized[$field] = sanitize_email($data[$field]); break; case 'url': $sanitized[$field] = esc_url_raw($data[$field]); break; case 'textarea': $sanitized[$field] = sanitize_textarea_field($data[$field]); break; default: $sanitized[$field] = sanitize_text_field($data[$field]); } } } return $sanitized; } /** * Sanitize datetime input * * @param string $datetime Raw datetime input * @return string Sanitized datetime */ private function sanitize_datetime(string $datetime): string { $timestamp = strtotime($datetime); return $timestamp !== false ? date('Y-m-d\TH:i', $timestamp) : ''; } /** * Get WordPress timezone options * * @return array Timezone options */ private function get_wordpress_timezones(): array { $zones = wp_timezone_choice('UTC'); $timezone_options = []; // Parse the HTML select options into key-value pairs if (preg_match_all('/