[
'type' => 'datetime-local',
'class' => 'hvac-datetime-field',
'validate' => ['datetime'],
'sanitize' => 'datetime',
],
'event-title' => [
'type' => 'text',
'class' => 'hvac-event-title',
'validate' => ['min_length' => 3, 'max_length' => 200],
'sanitize' => 'text',
],
'event-description' => [
'type' => 'textarea',
'class' => 'hvac-event-description',
'validate' => ['max_length' => 2000],
'sanitize' => 'textarea',
],
'venue-select' => [
'type' => 'select',
'class' => 'hvac-venue-select',
'options' => [],
'validate' => [],
'sanitize' => 'int',
],
'organizer-select' => [
'type' => 'select',
'class' => 'hvac-organizer-select',
'options' => [],
'validate' => [],
'sanitize' => 'int',
],
'capacity' => [
'type' => 'number',
'class' => 'hvac-capacity-field',
'validate' => ['min_value' => 1, 'max_value' => 10000],
'sanitize' => 'int',
],
'cost' => [
'type' => 'number',
'step' => '0.01',
'class' => 'hvac-cost-field',
'validate' => ['min_value' => 0],
'sanitize' => 'float',
],
'template-selector' => [
'type' => 'template-select',
'class' => 'hvac-template-selector',
'options' => [],
'validate' => [],
'sanitize' => 'text',
],
];
/**
* Cache instance for performance optimization (optional)
*
* @var mixed
*/
private $cache = null;
/**
* Constructor
*
* @param string $nonce_action Nonce action for form security
* @param bool $enable_templates Whether to enable template functionality
*/
public function __construct(string $nonce_action, bool $enable_templates = true) {
parent::__construct($nonce_action);
$this->template_manager = HVAC_Event_Template_Manager::instance();
$this->template_mode_enabled = $enable_templates;
// Initialize cache if available
$this->cache = class_exists('HVAC_Event_Cache') ? HVAC_Event_Cache::instance() : null;
if (!$this->cache && defined('WP_DEBUG') && WP_DEBUG) {
error_log('HVAC Event Cache unavailable - performance may be impacted');
}
$this->init_event_form_hooks();
}
/**
* Initialize event form specific hooks
*/
private function init_event_form_hooks(): void {
if ($this->template_mode_enabled) {
add_action('wp_enqueue_scripts', [$this, 'enqueue_template_assets']);
add_action('wp_ajax_hvac_load_template_data', [$this, 'ajax_load_template_data']);
add_action('wp_ajax_hvac_save_as_template', [$this, 'ajax_save_as_template']);
}
}
/**
* Create complete event form with template integration
*
* @param array $config Form configuration options
* @return self
*/
public function create_event_form(array $config = []): self {
$defaults = [
'include_template_selector' => $this->template_mode_enabled,
'include_venue_fields' => true,
'include_organizer_fields' => true,
'include_cost_fields' => true,
'include_capacity_fields' => true,
'include_datetime_fields' => true,
'template_categories' => ['general', 'training', 'workshop'],
];
$config = array_merge($defaults, $config);
// Add template selector first if enabled
if ($config['include_template_selector']) {
$this->add_template_selector($config['template_categories']);
}
// Basic event fields
$this->add_basic_event_fields();
/**
* Action hook for TEC ticketing integration
*
* Allows other components to add fields after basic event fields
* are rendered but before optional field groups like datetime fields.
*
* @param HVAC_Event_Form_Builder $form_builder Current form instance
* @since 3.2.0 (Phase 2B - TEC Integration)
*/
do_action('hvac_event_form_after_basic_fields', $this);
// Optional field groups
if ($config['include_datetime_fields']) {
$this->add_datetime_fields();
}
if ($config['include_venue_fields']) {
$this->add_venue_fields();
}
if ($config['include_organizer_fields']) {
$this->add_organizer_fields();
}
// Add categories field - new feature for enhanced categorization
$this->add_categories_fields();
if ($config['include_capacity_fields']) {
$this->add_capacity_field();
}
if ($config['include_cost_fields']) {
$this->add_cost_fields();
}
// Add progressive disclosure toggle
$this->add_progressive_disclosure();
// Mark certain fields as advanced
$this->mark_field_as_advanced('event_capacity')
->mark_field_as_advanced('event_cost')
->mark_field_as_advanced('event_timezone');
// Template actions if enabled
if ($this->template_mode_enabled) {
$this->add_template_actions();
// Mark template actions as advanced
$this->mark_field_as_advanced('save_as_template');
}
return $this;
}
/**
* Add template selector field
*
* @param array $categories Template categories to include
*/
public function add_template_selector(array $categories = []): self {
if (!$this->template_mode_enabled) {
return $this;
}
// Get available templates
$filters = [];
if (!empty($categories)) {
$templates = [];
foreach ($categories as $category) {
$category_templates = $this->template_manager->get_templates(['category' => $category]);
$templates = array_merge($templates, $category_templates);
}
} else {
$templates = $this->template_manager->get_templates();
}
// Group templates by category for enhanced UI
$templates_by_category = [];
foreach ($templates as $template) {
$category = $template['category'] ?? 'general';
if (!isset($templates_by_category[$category])) {
$templates_by_category[$category] = [];
}
$templates_by_category[$category][] = $template;
}
// Prepare enhanced template options with categories
$template_options = ['0' => '-- Select a Template --'];
foreach ($templates_by_category as $category => $category_templates) {
$template_options['optgroup_' . $category] = [
'label' => ucfirst($category) . ' Templates',
'options' => []
];
foreach ($category_templates as $template) {
$template_options['optgroup_' . $category]['options'][$template['id']] =
esc_html($template['name']) .
(!empty($template['description']) ? ' - ' . wp_trim_words($template['description'], 8) : '');
}
}
// Create accordion-style template selector
$accordion_template_field = [
'type' => 'custom',
'name' => 'accordion_template_selector',
'custom_html' => $this->render_accordion_template_selector($template_options, $templates),
'wrapper_class' => 'form-row template-selector-row',
];
$this->add_field($accordion_template_field);
// Add template preview area
$preview_field = [
'type' => 'custom',
'name' => 'template_preview',
'custom_html' => $this->render_template_preview_area(),
'wrapper_class' => 'form-row template-preview-row',
];
$this->add_field($preview_field);
return $this;
}
/**
* Add basic event fields
*/
public function add_basic_event_fields(): self {
// Event title
$title_field = array_merge($this->event_field_defaults['event-title'], [
'name' => 'event_title',
'label' => 'Event Title',
'placeholder' => 'Enter event title...',
'required' => true,
]);
// Event description with WordPress rich text editor
$description_field = [
'type' => 'custom',
'name' => 'event_description',
'custom_html' => $this->render_wp_editor_field(),
'wrapper_class' => 'form-row event-description-field'
];
// Description field uses rich text editor above
$this->add_field($title_field);
$this->add_field($description_field);
return $this;
}
/**
* Add datetime fields for event scheduling
*/
public function add_datetime_fields(): self {
// DateTime section grouping - same row on desktop, columns on mobile
$this->add_field([
'type' => 'custom',
'name' => 'datetime_row_group',
'custom_html' => '
',
'wrapper_class' => ''
]);
// Start date/time
$start_datetime_field = array_merge($this->event_field_defaults['datetime-local'], [
'name' => 'event_start_datetime',
'label' => 'Start Date & Time',
'required' => true,
'wrapper_class' => 'form-row-half datetime-field start-datetime',
]);
// End date/time
$end_datetime_field = array_merge($this->event_field_defaults['datetime-local'], [
'name' => 'event_end_datetime',
'label' => 'End Date & Time',
'required' => true,
'wrapper_class' => 'form-row-half datetime-field end-datetime',
]);
// Timezone
$timezone_field = [
'type' => 'select',
'name' => 'event_timezone',
'label' => 'Timezone',
'options' => $this->get_timezone_options(),
'value' => wp_timezone_string(),
'class' => 'hvac-timezone-select',
'wrapper_class' => 'form-row timezone-row',
];
$this->add_field($start_datetime_field);
$this->add_field($end_datetime_field);
// Close the datetime row group
$this->add_field([
'type' => 'custom',
'name' => 'datetime_row_group_end',
'custom_html' => '
',
'wrapper_class' => ''
]);
$this->add_field($timezone_field);
return $this;
}
/**
* Add venue selection and management fields
*/
public function add_venue_fields(): self {
// Dynamic single-select venue selector with autocomplete and modal creation
$venue_field = [
'type' => 'custom',
'name' => 'event_venue',
'custom_html' => $this->render_searchable_venue_selector(),
'wrapper_class' => 'form-row venue-field',
];
$this->add_field($venue_field);
return $this;
}
/**
* Add organizer selection and management fields
*/
public function add_organizer_fields(): self {
// Dynamic multi-select organizer selector with autocomplete
$organizer_field = [
'type' => 'custom',
'name' => 'event_organizer',
'custom_html' => $this->render_searchable_organizer_selector(),
'wrapper_class' => 'form-row organizer-field',
];
$this->add_field($organizer_field);
return $this;
}
/**
* Add categories field with multi-select search functionality
*/
public function add_categories_fields(): self {
// Dynamic multi-select category selector with autocomplete (limited for trainers)
$categories_field = [
'type' => 'custom',
'name' => 'event_categories',
'custom_html' => $this->render_searchable_category_selector(),
'wrapper_class' => 'form-row categories-field',
];
$this->add_field($categories_field);
return $this;
}
/**
* Add capacity field
*/
public function add_capacity_field(): self {
$capacity_field = array_merge($this->event_field_defaults['capacity'], [
'name' => 'event_capacity',
'label' => 'Capacity',
'placeholder' => 'Maximum attendees',
'min' => 1,
'max' => 10000,
'wrapper_class' => 'form-row capacity-row',
]);
$this->add_field($capacity_field);
return $this;
}
/**
* Add cost-related fields
*/
public function add_cost_fields(): self {
$cost_field = array_merge($this->event_field_defaults['cost'], [
'name' => 'event_cost',
'label' => 'Event Cost ($)',
'placeholder' => '0.00',
'min' => 0,
'wrapper_class' => 'form-row cost-row',
]);
$this->add_field($cost_field);
return $this;
}
/**
* Add template action buttons
*/
public function add_template_actions(): self {
if (!$this->template_mode_enabled) {
return $this;
}
// Save as template button
$save_template_field = [
'type' => 'button',
'name' => 'save_as_template',
'label' => '',
'value' => 'Save as Template',
'class' => 'button button-secondary hvac-save-template',
'wrapper_class' => 'form-row template-actions',
'onclick' => 'hvacShowSaveTemplateDialog(event)',
];
$this->add_field($save_template_field);
return $this;
}
/**
* Add progressive disclosure section
*/
public function add_progressive_disclosure(): self {
// Advanced options toggle
$advanced_toggle_field = [
'type' => 'custom',
'name' => 'advanced_options_toggle',
'custom_html' => $this->render_advanced_options_toggle(),
'wrapper_class' => 'form-row advanced-toggle-row',
];
$this->add_field($advanced_toggle_field);
return $this;
}
/**
* Mark field as advanced (for progressive disclosure)
*/
public function mark_field_as_advanced(string $field_name): self {
foreach ($this->fields as &$field) {
if ($field['name'] === $field_name) {
$field['wrapper_class'] = ($field['wrapper_class'] ?? '') . ' advanced-field';
$field['data_attributes'] = array_merge($field['data_attributes'] ?? [], [
'advanced' => 'true'
]);
break;
}
}
return $this;
}
/**
* Render advanced options toggle button
*/
private function render_advanced_options_toggle(): string {
$html = '';
$html .= '';
$html .= 'Additional settings for power users';
$html .= '
';
return $html;
}
/**
* Load template data into form
*
* @param string $template_id Template ID to load
* @return bool Success status
*/
public function load_template(string $template_id): bool {
if (!$this->template_mode_enabled) {
return false;
}
$template = $this->template_manager->get_template($template_id);
if (!$template) {
return false;
}
$this->current_template = $template;
// Set form data from template
if (!empty($template['field_data'])) {
$this->set_data($template['field_data']);
}
// Apply template-specific validation rules
if (!empty($template['validation_rules'])) {
$this->apply_template_validation_rules($template['validation_rules']);
}
return true;
}
/**
* Save current form configuration as template
*
* @param array $template_config Template configuration
* @return array Result with success status
*/
public function save_as_template(array $template_config): array {
if (!$this->template_mode_enabled) {
return [
'success' => false,
'error' => __('Template functionality is not enabled', 'hvac-community-events')
];
}
// Get current form data
$current_data = $this->get_current_form_data();
// Prepare template data
$template_data = [
'name' => sanitize_text_field($template_config['name']),
'description' => sanitize_textarea_field($template_config['description']),
'category' => sanitize_text_field($template_config['category']),
'is_public' => (bool) ($template_config['is_public'] ?? false),
'field_data' => $current_data,
'meta_data' => $template_config['meta_data'] ?? [],
];
return $this->template_manager->create_template($template_data);
}
/**
* Get current form data from fields
*
* @return array Current form data
*/
private function get_current_form_data(): array {
$data = [];
foreach ($this->fields as $field) {
if (isset($_POST[$field['name']])) {
$data[$field['name']] = $this->sanitize_field_value($field, $_POST[$field['name']]);
}
}
return $data;
}
/**
* Sanitize field value based on field type
*
* @param array $field Field configuration
* @param mixed $value Field value to sanitize
* @return mixed Sanitized value
*/
private function sanitize_field_value(array $field, $value) {
$sanitize_type = $field['sanitize'] ?? 'text';
return match($sanitize_type) {
'text' => sanitize_text_field($value),
'textarea' => sanitize_textarea_field($value),
'int' => absint($value),
'float' => floatval($value),
'datetime' => sanitize_text_field($value),
'url' => esc_url_raw($value),
'email' => sanitize_email($value),
default => sanitize_text_field($value)
};
}
/**
* Apply template validation rules to form fields
*
* @param array $validation_rules Template validation rules
*/
private function apply_template_validation_rules(array $validation_rules): void {
// Apply additional validation rules from template
foreach ($validation_rules as $field_name => $rules) {
// Find field and update validation rules
foreach ($this->fields as &$field) {
if ($field['name'] === $field_name) {
$field['validate'] = array_merge($field['validate'], $rules);
break;
}
}
}
}
/**
* Render accordion-style template selector
*
* @param array $template_options Template options array
* @param array $templates Raw templates data
* @return string Accordion HTML
*/
private function render_accordion_template_selector(array $template_options, array $templates): string {
$html = '';
$html .= '';
$html .= '
';
// Build select options HTML
$html .= '
';
$html .= '
Select a template to pre-fill form fields with common settings.
';
$html .= '
';
$html .= '
';
return $html;
}
/**
* Render template preview area
*
* @return string Preview HTML
*/
private function render_template_preview_area(): string {
$html = '';
$html .= '';
$html .= '
';
$html .= '
';
$html .= '
';
$html .= '
';
$html .= '
Category:
';
$html .= '
';
$html .= '
';
$html .= '
Pre-filled Fields:
';
$html .= '
';
$html .= '
';
$html .= '
';
$html .= '';
$html .= '';
$html .= '
';
$html .= '
';
$html .= '
';
return $html;
}
/**
* Render save template dialog
*
* @return string Dialog HTML
*/
private function render_save_template_dialog(): string {
$html = '';
$html .= '';
$html .= '
';
// Template name field
$html .= '
HTML;
}
/**
* Render searchable category selector with multi-select (no create for trainers)
*/
private function render_searchable_category_selector(): string {
$current_user = wp_get_current_user();
$can_create = in_array('hvac_master_trainer', $current_user->roles); // Only master trainers can create categories
return <<
Loading...
No categories found
{$this->render_create_new_button('category', $can_create)}
Select up to 3 categories for this event.
HTML;
}
/**
* Render searchable venue selector with single-select and "Add New" functionality
*/
private function render_searchable_venue_selector(): string {
$current_user = wp_get_current_user();
$can_create = in_array('hvac_trainer', $current_user->roles) || in_array('hvac_master_trainer', $current_user->roles);
return <<
Loading...
No venues found
{$this->render_create_new_button('venue', $can_create)}
Select a venue for this event. You can search by name or address.
HTML;
}
/**
* Render "Create New" button with role-based permissions
*/
private function render_create_new_button(string $type, bool $can_create): string {
if (!$can_create) {
if ($type === 'category') {
return 'Only Master Trainers can create new categories
';
}
return '';
}
$label = ucfirst($type);
return <<
HTML;
}
/**
* Render WordPress rich text editor field
*
* @return string HTML for WordPress editor
*/
private function render_wp_editor_field(): string {
ob_start();
?>
'event_description',
'textarea_rows' => 10,
'media_buttons' => false,
'teeny' => false,
'tinymce' => [
'toolbar1' => 'formatselect,bold,italic,underline,strikethrough,|,bullist,numlist,|,link,unlink,|,blockquote,hr,|,alignleft,aligncenter,alignright,|,undo,redo',
'toolbar2' => '',
'block_formats' => 'Paragraph=p;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre',
'forced_root_block' => 'p',
'force_p_newlines' => true,
'remove_redundant_brs' => true,
'convert_urls' => false,
'paste_as_text' => false,
'paste_auto_cleanup_on_paste' => true,
'paste_remove_spans' => true,
'paste_remove_styles' => true,
'paste_strip_class_attributes' => 'all',
'valid_elements' => 'p,br,strong,em,ul,ol,li,h2,h3,h4,h5,h6,blockquote,a[href|title],hr',
'valid_children' => '+p[strong|em|a|br],+ul[li],+ol[li],+li[strong|em|a|br|p]'
],
'quicktags' => [
'buttons' => 'strong,em,ul,ol,li,link,close'
]
];
wp_editor('', 'event_description', $editor_settings);
?>
Use the editor above to format your event description with headings, lists, and formatting.