✅ HVAC_Event_Form_Builder Implementation: - Native WordPress event form builder extending HVAC_Form_Builder - Complete datetime field types (start/end dates, timezone, all-day) - Comprehensive venue field group (name, address, capacity, coordinates) - Organizer field group (name, email, phone, website) with validation - HVAC-specific fields (trainer requirements, certifications, equipment) - Featured image upload support with security validation - WordPress-native security integration (nonces, sanitization) - Comprehensive form validation and error handling 🏗️ Architecture Improvements: - Extract HVAC_Singleton_Trait to standalone file for better organization - Add proper file loading order in HVAC_Plugin class - Include core security framework and form builder dependencies 🧪 Testing Infrastructure: - Native event test template for Phase 1A validation - Staging deployment and testing completed successfully - All form fields render and validate correctly 🎯 Strategic Progress: - Phase 1A complete: Native form foundation established - Eliminates dependency on problematic TEC Community Events forms - Provides foundation for Phase 1B tribe_events post creation 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
680 lines
No EOL
21 KiB
PHP
680 lines
No EOL
21 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* HVAC Event Form Builder
|
|
*
|
|
* Extended form builder for native WordPress event management
|
|
* Replaces TEC Community Events forms with comprehensive field control
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @subpackage Includes
|
|
* @since 3.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Event_Form_Builder
|
|
*
|
|
* Extends HVAC_Form_Builder with event-specific field types and functionality
|
|
*/
|
|
class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
|
|
|
/**
|
|
* Event-specific field defaults
|
|
*
|
|
* @var array
|
|
*/
|
|
private array $event_field_defaults = [
|
|
'datetime-local' => [
|
|
'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('/<option value="([^"]*)"[^>]*>([^<]*)<\/option>/', $zones, $matches)) {
|
|
foreach ($matches[1] as $index => $value) {
|
|
$timezone_options[$value] = $matches[2][$index];
|
|
}
|
|
}
|
|
|
|
return $timezone_options;
|
|
}
|
|
|
|
/**
|
|
* Get trainer requirement options
|
|
*
|
|
* @return array Trainer requirement options
|
|
*/
|
|
private function get_trainer_requirement_options(): array {
|
|
return [
|
|
'' => 'No specific requirement',
|
|
'certified_trainer' => 'Certified HVAC Trainer',
|
|
'master_trainer' => 'Master Trainer',
|
|
'industry_expert' => 'Industry Expert',
|
|
'manufacturer_rep' => 'Manufacturer Representative',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get certification level options
|
|
*
|
|
* @return array Certification level options
|
|
*/
|
|
private function get_certification_level_options(): array {
|
|
return [
|
|
'basic' => 'Basic HVAC',
|
|
'intermediate' => 'Intermediate HVAC',
|
|
'advanced' => 'Advanced HVAC',
|
|
'commercial' => 'Commercial Systems',
|
|
'residential' => 'Residential Systems',
|
|
'refrigeration' => 'Refrigeration',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create complete event creation form
|
|
*
|
|
* @return self Configured form builder
|
|
*/
|
|
public function create_event_form(): self {
|
|
// Basic event information
|
|
$this->add_field([
|
|
'type' => 'text',
|
|
'name' => 'event_title',
|
|
'label' => 'Event Title',
|
|
'required' => true,
|
|
'class' => 'hvac-event-title',
|
|
'placeholder' => 'Enter event title',
|
|
]);
|
|
|
|
$this->add_field([
|
|
'type' => 'textarea',
|
|
'name' => 'event_description',
|
|
'label' => 'Event Description',
|
|
'required' => true,
|
|
'class' => 'hvac-event-description',
|
|
'placeholder' => 'Describe the event in detail',
|
|
'validate' => ['min_length' => 50],
|
|
]);
|
|
|
|
$this->add_field([
|
|
'type' => 'textarea',
|
|
'name' => 'event_excerpt',
|
|
'label' => 'Event Summary',
|
|
'required' => false,
|
|
'class' => 'hvac-event-excerpt',
|
|
'placeholder' => 'Brief summary for event listings',
|
|
'validate' => ['max_length' => 300],
|
|
]);
|
|
|
|
// DateTime fields
|
|
$this->add_datetime_field([
|
|
'name' => 'event_start_date',
|
|
'label' => 'Start Date & Time',
|
|
'required' => true,
|
|
]);
|
|
|
|
$this->add_datetime_field([
|
|
'name' => 'event_end_date',
|
|
'label' => 'End Date & Time',
|
|
'required' => true,
|
|
]);
|
|
|
|
$this->add_all_day_field();
|
|
$this->add_timezone_field();
|
|
|
|
// Event URL
|
|
$this->add_field([
|
|
'type' => 'url',
|
|
'name' => 'event_url',
|
|
'label' => 'Event Website',
|
|
'required' => false,
|
|
'class' => 'hvac-event-url',
|
|
'placeholder' => 'https://example.com/event-info',
|
|
'validate' => ['url'],
|
|
]);
|
|
|
|
// Venue and organizer information
|
|
$this->add_venue_group();
|
|
$this->add_organizer_group();
|
|
|
|
// HVAC-specific fields
|
|
$this->add_hvac_fields();
|
|
|
|
// Featured image
|
|
$this->add_featured_image_field();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Create event editing form with pre-populated data
|
|
*
|
|
* @param int $event_id Event post ID
|
|
* @return self Configured form builder
|
|
*/
|
|
public function create_edit_form(int $event_id): self {
|
|
// Create the form structure
|
|
$this->create_event_form();
|
|
|
|
// Pre-populate with event data
|
|
$event_data = $this->get_event_data($event_id);
|
|
if (!empty($event_data)) {
|
|
$this->set_data($event_data);
|
|
}
|
|
|
|
// Add event ID as hidden field
|
|
$this->add_field([
|
|
'type' => 'hidden',
|
|
'name' => 'event_id',
|
|
'value' => (string)$event_id,
|
|
]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get event data for form pre-population
|
|
*
|
|
* @param int $event_id Event post ID
|
|
* @return array Event data
|
|
*/
|
|
private function get_event_data(int $event_id): array {
|
|
$post = get_post($event_id);
|
|
if (!$post || $post->post_type !== 'tribe_events') {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
'event_title' => $post->post_title,
|
|
'event_description' => $post->post_content,
|
|
'event_excerpt' => $post->post_excerpt,
|
|
'event_start_date' => get_post_meta($event_id, '_EventStartDate', true),
|
|
'event_end_date' => get_post_meta($event_id, '_EventEndDate', true),
|
|
'event_timezone' => get_post_meta($event_id, '_EventTimezone', true),
|
|
'event_url' => get_post_meta($event_id, '_EventURL', true),
|
|
// Add more meta field mappings as needed
|
|
];
|
|
}
|
|
} |