feat: complete Phase 2B template system enhancements

 Enhanced Template Selector:
- Grouped templates by category with descriptions
- Template preview modal with field data display
- Apply template functionality with AJAX loading
- Enhanced UI with preview icons and better UX

 Save as Template Functionality:
- Complete save template dialog with validation
- Template name, description, and category fields
- Public/private template sharing options
- AJAX integration with error handling and success feedback

 Progressive Disclosure:
- Advanced options toggle with smooth animations
- Fields marked as advanced (capacity, cost, timezone)
- Local storage for user preference persistence
- Staggered reveal animations for better UX

 Enhanced Auto-save:
- Intelligent auto-save with field-type specific delays
- Draft recovery with age information and user confirmation
- Error handling with fallback to essential fields only
- Visual feedback with status indicator and animations
- Auto-save on page visibility change

 AJAX Infrastructure:
- Template preview handler (hvac_get_template_preview)
- Template loading handler (hvac_load_template_data)
- Template saving handler (hvac_save_template)
- Comprehensive error handling and security validation

🎨 UI/UX Enhancements:
- Modern modal dialogs with backdrop overlays
- Responsive design for mobile devices
- Smooth animations and transitions
- Status indicators with rotating save icons
- Comprehensive styling for all new components

🚀 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ben 2025-09-25 09:06:03 -03:00
parent ac8c09a294
commit 09a15f874c
5 changed files with 1856 additions and 54 deletions

View file

@ -21,7 +21,10 @@
"WebFetch(domain:upskill-staging.measurequick.com)",
"Bash(scripts/:*)",
"Bash(./scripts/:*)",
"Bash(php -l:*)"
"Bash(php -l:*)",
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e scp -o StrictHostKeyChecking=no 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 scp -o StrictHostKeyChecking=no templates/page-create-event.php roodev@146.190.76.204:/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/templates/)",
"mcp__playwright__browser_navigate"
],
"deny": [],
"ask": [],

View file

@ -0,0 +1,492 @@
# AI-Assisted Event Submission Feature Specification
## Executive Summary
This document specifies an AI-powered feature to reduce friction for HVAC trainers submitting events to a WordPress calendar system. The solution uses a single Anthropic Claude API call to parse unstructured event information and auto-populate form fields, dramatically simplifying the submission process for non-technical users.
## Project Context
### Current State
- **Platform**: WordPress site hosted on Cloudways
- **Base Plugin**: The Events Calendar (TEC) by Modern Tribe
- **Custom Plugin**: Existing custom plugin built on top of TEC for HVAC trainer event submissions
- **User Base**: HVAC trainers who are often not tech-savvy and have limited time
- **Problem**: High friction in manual form entry leading to incomplete or missing event submissions
### Technical Constraints
- **Hosting**: Cloudways shared hosting environment
- Limited server-side processing capabilities
- No ability to install additional server software
- Standard PHP/MySQL stack
- HTTPS request capabilities
- **Must maintain compatibility** with The Events Calendar plugin data structures
- **Cannot add additional WordPress plugins** - solution must be implemented within existing custom plugin
## Functional Requirements
### Core Feature: AI-Assisted Event Form Population
#### User Flow
1. User navigates to the create event page
2. User sees an "Auto-populate with AI" button/option
3. Upon clicking, a modal dialog appears with:
- Clear instructions for the user
- Single rich text input field
- Submit button
4. User provides one of three input types:
- **Option A**: URL to a webpage containing event details
- **Option B**: Copy/pasted text from email/document
- **Option C**: Brief description with key details
5. System processes input showing:
- Loading spinner
- Status messages during processing
6. System populates form fields automatically
7. System displays confidence/completeness matrix
8. User reviews and adjusts populated fields before final submission
#### Input Acceptance Criteria
**URL Input**
- Accept any valid HTTP/HTTPS URL
- Handle various event page formats (EventBrite, custom sites, social media event pages)
- Extract information even from non-standard page structures
**Pasted Text Input**
- Accept unformatted text from emails, PDFs, documents
- Handle various formatting styles and structures
- Parse information regardless of order or format
**Brief Description Input**
- Accept natural language descriptions
- Extract partial information when complete details aren't provided
- Make intelligent assumptions about missing data
#### Output Requirements
**Required Fields to Extract**
- Event title
- Date and time
- Location/venue
- Description
- Organizer
- Cost/pricing
- Registration URL (if available)
**Data Validation Requirements**
- Check against existing venues to avoid duplicates
- Check against existing organizers to avoid duplicates
- Validate date/time formats
- Ensure required fields are populated
**Confidence Reporting**
Display a matrix showing:
- **Completeness**: Whether each field was found (High/Medium/Low/N/A)
- **Confidence**: How confident the AI is in the extracted data (0.0-1.0)
## Technical Architecture
### Simplified Single-API Approach
#### Architecture Decision
Use a single Anthropic Claude API call with web search capabilities enabled, eliminating the need for:
- Separate web scraping services
- Multiple NLP processing steps
- Complex orchestration logic
- Background job processing
- Additional server infrastructure
#### Key Components
1. **Frontend (JavaScript)**
- Modal interface for input collection
- AJAX communication with WordPress backend
- Form field population logic
- Confidence matrix display
2. **Backend (PHP/WordPress)**
- Single AJAX endpoint for AI processing
- API key management
- Request/response handling
- Data sanitization and validation
3. **External Service**
- Anthropic Claude API (claude-3-5-sonnet-20241022 or latest)
- Web search/browsing capabilities enabled
### Implementation Patterns
#### Pattern 1: Stateless Request Processing
- **Do**: Make each request completely self-contained
- **Do**: Include all context needed in single prompt
- **Do**: Return complete results in one response
- **Don't**: Maintain conversation state between requests
- **Don't**: Make multiple API calls for single submission
#### Pattern 2: Defensive Data Handling
```php
// Always validate and sanitize
$user_input = sanitize_textarea_field($_POST['input'] ?? '');
if (empty($user_input)) {
wp_send_json_error(['message' => 'No input provided']);
wp_die();
}
```
#### Pattern 3: Structured Output Enforcement
- **Do**: Request JSON output explicitly in prompt
- **Do**: Provide exact schema in prompt
- **Do**: Include example of expected output
- **Don't**: Parse free-text responses
- **Don't**: Rely on consistent formatting without schema
#### Pattern 4: Graceful Degradation
- **Do**: Provide fallback for API failures
- **Do**: Allow manual form filling if AI fails
- **Do**: Cache successful extractions
- **Don't**: Block user progress on AI failure
- **Don't**: Require AI for form submission
### Anti-Patterns to Avoid
#### Anti-Pattern 1: Client-Side API Keys
**Never** expose API keys in JavaScript or make direct API calls from browser
#### Anti-Pattern 2: Synchronous Long-Running Requests
**Avoid** blocking UI during API calls; always use async patterns with loading states
#### Anti-Pattern 3: Over-Engineering
**Don't** build complex parsing systems when Claude can handle it in one call
#### Anti-Pattern 4: Rigid Field Matching
**Don't** require exact field matches; use fuzzy matching for venues/organizers
## Prompt Engineering Strategy
### Core Prompt Structure
```text
You are an AI assistant specialized in extracting event information from various sources.
Your task is to extract structured event data for an HVAC training calendar.
[CONTEXT]
- These are professional training events for HVAC technicians
- Events may be in-person or virtual
- Common organizers include: {list_of_existing_organizers}
- Common venues include: {list_of_existing_venues}
[INPUT]
{user_provides_one_of_these}
- URL: Please retrieve and analyze the webpage at: {url}
- Text: Parse the following event information: {pasted_text}
- Description: Extract event details from: {brief_description}
[EXTRACTION RULES]
1. Match venues/organizers to existing ones when similarity > 80%
2. Infer missing information when reasonable (mark as low confidence)
3. Handle relative dates (convert "next Tuesday" to actual date)
4. Extract all URLs found in source material
5. Standardize time to 24-hour format
6. If multiple events found, extract only the first/primary one
[OUTPUT SCHEMA]
Return ONLY a valid JSON object with this exact structure:
{json_schema_here}
[IMPORTANT]
- Return ONLY the JSON object, no explanatory text
- Use null for missing fields rather than empty strings
- Include confidence scores for each field
```
### Prompt Optimization Techniques
#### 1. Few-Shot Examples
Include 2-3 examples of successful extractions in the system prompt to improve consistency.
#### 2. Role Definition
Clearly define Claude's role as an "event information extraction specialist" to improve focus.
#### 3. Explicit Constraints
- Specify exact date format (ISO 8601)
- Define confidence score ranges (0.0-1.0)
- List acceptable values for enums
#### 4. Context Injection
Always include:
- Current date/time for relative date resolution
- List of existing venues/organizers
- Common patterns in your specific domain
### Handling Edge Cases
#### Edge Case 1: Multiple Events
**Strategy**: Extract only the first/most prominent event
**Prompt Addition**: "If multiple events are found, extract only the primary/first event"
#### Edge Case 2: Ambiguous Dates
**Strategy**: Request current date context
**Implementation**: Always include `Today is {current_date}` in prompt
#### Edge Case 3: Missing Critical Information
**Strategy**: Return partial data with low confidence
**Validation**: Check for minimum required fields before population
## Input/Output Validation
### Input Validation Strategy
#### Pre-Processing Validation
```javascript
function validateInput(input) {
// Check for minimum length
if (input.length < 10) {
return { valid: false, error: 'Input too short' };
}
// Detect input type
const type = detectInputType(input);
// URL validation
if (type === 'url') {
try {
new URL(input);
} catch {
return { valid: false, error: 'Invalid URL format' };
}
}
// Size limits (prevent token overflow)
if (input.length > 50000) {
return { valid: false, error: 'Input too large' };
}
return { valid: true, type };
}
```
#### Input Sanitization
- Strip potentially harmful HTML/scripts
- Normalize whitespace and line breaks
- Remove non-printable characters
- Truncate to maximum acceptable length
### Output Validation Strategy
#### Schema Validation
```php
function validate_ai_response($response) {
$required_fields = ['title', 'date'];
$schema = [
'title' => 'string',
'date' => 'date',
'venue' => 'string|null',
'confidence' => 'array',
'completeness' => 'array'
];
// Check JSON structure
if (!is_array($response)) {
throw new ValidationException('Invalid JSON response');
}
// Validate required fields
foreach ($required_fields as $field) {
if (empty($response[$field])) {
throw new ValidationException("Missing required field: {$field}");
}
}
// Type checking and sanitization
// ... implementation details
return $sanitized_response;
}
```
#### Confidence Threshold Handling
- **High Confidence (>0.8)**: Auto-populate without warning
- **Medium Confidence (0.5-0.8)**: Populate with visual indicator
- **Low Confidence (<0.5)**: Populate but highlight for review
- **No Confidence (null)**: Leave field empty
#### Duplicate Detection
```php
function check_duplicate_event($event_data) {
// Check for same date + similar title
$similar_events = get_posts([
'post_type' => 'tribe_events',
'meta_key' => '_EventStartDate',
'meta_value' => $event_data['date'],
'meta_compare' => '='
]);
foreach ($similar_events as $existing) {
$similarity = similar_text(
strtolower($existing->post_title),
strtolower($event_data['title']),
$percent
);
if ($percent > 75) {
return [
'is_duplicate' => true,
'existing_id' => $existing->ID,
'similarity' => $percent
];
}
}
return ['is_duplicate' => false];
}
```
## Error Handling and Recovery
### API Error Handling
#### Rate Limiting
- Implement exponential backoff
- Cache successful responses for 24 hours
- Show user-friendly message when limit reached
#### Network Failures
- Timeout after 30 seconds
- Provide option to retry
- Fall back to manual entry
#### Invalid Responses
- Log malformed responses for debugging
- Show generic error to user
- Provide option to try different input
### User Communication
#### Status Messages
1. "Analyzing your input..." (0-2 seconds)
2. "Extracting event information..." (2-10 seconds)
3. "Validating extracted data..." (10-15 seconds)
4. "Populating form fields..." (15+ seconds)
#### Error Messages
- Be specific but not technical
- Provide actionable next steps
- Never expose internal errors or API keys
## Security Considerations
### API Key Management
- Store in wp-config.php or environment variables
- Never commit to version control
- Rotate keys periodically
- Use WordPress nonce for AJAX calls
### Input Security
- Sanitize all user inputs
- Prevent SQL injection via parameterized queries
- Escape output when displaying
- Limit request frequency per user
### Data Privacy
- Don't log sensitive event information
- Clear temporary data after processing
- Comply with GDPR/privacy requirements
- Allow users to opt-out of AI features
## Performance Optimization
### Caching Strategy
- Cache AI responses for identical inputs (24-hour TTL)
- Cache venue/organizer lists (1-hour TTL)
- Use WordPress transients for temporary data
### Request Optimization
- Minimize prompt size while maintaining clarity
- Pre-process input to remove redundant information
- Batch similar requests when possible
### Frontend Optimization
- Lazy-load AI modal components
- Debounce input validation
- Use optimistic UI updates
## Testing Strategy
### Test Scenarios
1. **URL Input**: Test with EventBrite, Facebook Events, custom sites
2. **Text Input**: Test with various email formats, PDF copies
3. **Description Input**: Test with minimal and detailed descriptions
4. **Edge Cases**: Test with non-English content, past events, recurring events
5. **Failure Cases**: Test with invalid URLs, gibberish input, timeout scenarios
### Validation Testing
- Ensure all required fields are validated
- Test confidence score accuracy
- Verify duplicate detection works correctly
- Test venue/organizer matching logic
## Implementation Checklist
### Phase 1: Core Infrastructure
- [ ] Set up Anthropic API integration
- [ ] Create WordPress AJAX endpoint
- [ ] Implement basic input validation
- [ ] Build JSON response parser
### Phase 2: UI Implementation
- [ ] Create modal interface
- [ ] Implement form field population
- [ ] Add loading states and progress indicators
- [ ] Build confidence matrix display
### Phase 3: Data Processing
- [ ] Implement venue/organizer matching
- [ ] Add duplicate detection
- [ ] Create validation pipeline
- [ ] Add error handling and recovery
### Phase 4: Optimization
- [ ] Implement caching layer
- [ ] Add request rate limiting
- [ ] Optimize prompt for accuracy
- [ ] Performance testing and tuning
### Phase 5: Polish and Testing
- [ ] Comprehensive error messages
- [ ] User documentation
- [ ] Edge case handling
- [ ] Security audit
## Success Metrics
### Technical Metrics
- API response time < 10 seconds (95th percentile)
- Extraction accuracy > 85% for standard inputs
- Zero security vulnerabilities
- 99.9% uptime for feature availability
### User Metrics
- 50% reduction in time to submit events
- 75% of submissions use AI assistance
- < 5% error rate requiring manual intervention
- User satisfaction score > 4/5
## Appendix: The Events Calendar Integration
### Relevant Post Types
- `tribe_events` - Main event post type
- `tribe_venue` - Venue post type
- `tribe_organizer` - Organizer post type
### Key Meta Fields
- `_EventStartDate` - Event start date/time
- `_EventEndDate` - Event end date/time
- `_EventVenueID` - Reference to venue post
- `_EventOrganizerID` - Reference to organizer post
- `_EventCost` - Event cost
- `_EventURL` - Registration/info URL
### Integration Points
- Use TEC's built-in functions when available
- Respect TEC's data validation rules
- Maintain compatibility with TEC updates
- Leverage TEC's duplicate detection if available

View file

@ -190,9 +190,19 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
$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;
@ -220,23 +230,55 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
$templates = $this->template_manager->get_templates();
}
// Prepare template options
$template_options = ['0' => '-- Select a Template --'];
// Group templates by category for enhanced UI
$templates_by_category = [];
foreach ($templates as $template) {
$template_options[$template['id']] = esc_html($template['name']) .
' (' . ucfirst($template['category']) . ')';
$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) : '');
}
}
$template_field = array_merge($this->event_field_defaults['template-selector'], [
'name' => 'event_template',
'label' => 'Use Template',
'options' => $template_options,
'description' => 'Select a template to pre-fill form fields',
'wrapper_class' => 'form-row template-selector-row',
'description' => 'Select a template to pre-fill form fields. Templates are organized by category.',
'wrapper_class' => 'form-row template-selector-row enhanced-selector',
'data_attributes' => [
'templates' => json_encode($templates),
'enable_preview' => 'true'
]
]);
$this->add_field($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;
}
@ -401,14 +443,73 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
'value' => 'Save as Template',
'class' => 'button button-secondary hvac-save-template',
'wrapper_class' => 'form-row template-actions',
'onclick' => 'hvacSaveAsTemplate(event)',
'onclick' => 'hvacShowSaveTemplateDialog(event)',
];
$this->add_field($save_template_field);
// Add save template dialog
$save_dialog_field = [
'type' => 'custom',
'name' => 'save_template_dialog',
'custom_html' => $this->render_save_template_dialog(),
'wrapper_class' => 'form-row template-dialog-row',
];
$this->add_field($save_dialog_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 = '<div class="hvac-advanced-options-toggle">';
$html .= '<button type="button" class="toggle-advanced-options" onclick="hvacToggleAdvancedOptions()">';
$html .= '<span class="dashicons dashicons-arrow-down-alt2 toggle-icon"></span>';
$html .= '<span class="toggle-text">Show Advanced Options</span>';
$html .= '</button>';
$html .= '<small class="toggle-description">Additional settings for power users</small>';
$html .= '</div>';
return $html;
}
/**
* Load template data into form
*
@ -499,6 +600,97 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
}
}
/**
* Render template preview area
*
* @return string Preview HTML
*/
private function render_template_preview_area(): string {
$html = '<div class="hvac-template-preview" id="hvac-template-preview" style="display: none;">';
$html .= '<div class="preview-header">';
$html .= '<h4><span class="dashicons dashicons-visibility"></span> Template Preview</h4>';
$html .= '<button type="button" class="preview-close" onclick="hvacCloseTemplatePreview()">&times;</button>';
$html .= '</div>';
$html .= '<div class="preview-content">';
$html .= '<div class="preview-info">';
$html .= '<p class="template-name"><strong></strong></p>';
$html .= '<p class="template-description"></p>';
$html .= '<p class="template-category">Category: <span></span></p>';
$html .= '</div>';
$html .= '<div class="preview-fields">';
$html .= '<h5>Pre-filled Fields:</h5>';
$html .= '<ul class="field-list"></ul>';
$html .= '</div>';
$html .= '<div class="preview-actions">';
$html .= '<button type="button" class="button button-primary" onclick="hvacApplyTemplate()">Apply This Template</button>';
$html .= '<button type="button" class="button" onclick="hvacCloseTemplatePreview()">Cancel</button>';
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* Render save template dialog
*
* @return string Dialog HTML
*/
private function render_save_template_dialog(): string {
$html = '<div class="hvac-save-template-dialog" id="hvac-save-template-dialog" style="display: none;">';
$html .= '<div class="dialog-header">';
$html .= '<h4><span class="dashicons dashicons-saved"></span> Save as Template</h4>';
$html .= '<button type="button" class="dialog-close" onclick="hvacCloseSaveDialog()">&times;</button>';
$html .= '</div>';
$html .= '<div class="dialog-content">';
// Template name field
$html .= '<div class="form-group">';
$html .= '<label for="template-name"><strong>Template Name *</strong></label>';
$html .= '<input type="text" id="template-name" name="template_name" required maxlength="100" ';
$html .= 'placeholder="Enter template name..." class="template-input">';
$html .= '</div>';
// Template description field
$html .= '<div class="form-group">';
$html .= '<label for="template-description"><strong>Description</strong></label>';
$html .= '<textarea id="template-description" name="template_description" rows="3" maxlength="500" ';
$html .= 'placeholder="Brief description of this template..." class="template-input"></textarea>';
$html .= '</div>';
// Template category field
$html .= '<div class="form-group">';
$html .= '<label for="template-category"><strong>Category *</strong></label>';
$html .= '<select id="template-category" name="template_category" required class="template-input">';
$html .= '<option value="">Select Category...</option>';
$html .= '<option value="general">General</option>';
$html .= '<option value="training">Training</option>';
$html .= '<option value="workshop">Workshop</option>';
$html .= '<option value="certification">Certification</option>';
$html .= '<option value="conference">Conference</option>';
$html .= '<option value="webinar">Webinar</option>';
$html .= '</select>';
$html .= '</div>';
// Public checkbox
$html .= '<div class="form-group checkbox-group">';
$html .= '<label for="template-public">';
$html .= '<input type="checkbox" id="template-public" name="template_public" value="1">';
$html .= ' Make this template available to other trainers';
$html .= '</label>';
$html .= '<small class="description">If unchecked, only you will be able to use this template</small>';
$html .= '</div>';
$html .= '</div>';
$html .= '<div class="dialog-actions">';
$html .= '<button type="button" class="button button-primary" onclick="hvacSaveAsTemplate()">Save Template</button>';
$html .= '<button type="button" class="button" onclick="hvacCloseSaveDialog()">Cancel</button>';
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* Get timezone options for select field
*

View file

@ -49,6 +49,7 @@ class HVAC_Shortcodes {
private function __construct() {
$this->define_shortcodes();
$this->register_shortcodes();
$this->register_ajax_handlers();
}
/**
@ -860,4 +861,175 @@ class HVAC_Shortcodes {
$profile_manager = HVAC_Trainer_Profile_Manager::get_instance();
return $profile_manager->render_profile_edit($atts);
}
/**
* Register AJAX handlers
*/
private function register_ajax_handlers() {
// Template preview AJAX handler
add_action('wp_ajax_hvac_get_template_preview', array($this, 'ajax_get_template_preview'));
add_action('wp_ajax_nopriv_hvac_get_template_preview', array($this, 'ajax_get_template_preview'));
// Template loading AJAX handler
add_action('wp_ajax_hvac_load_template_data', array($this, 'ajax_load_template_data'));
add_action('wp_ajax_nopriv_hvac_load_template_data', array($this, 'ajax_load_template_data'));
// Template saving AJAX handler
add_action('wp_ajax_hvac_save_template', array($this, 'ajax_save_template'));
add_action('wp_ajax_nopriv_hvac_save_template', array($this, 'ajax_save_template'));
}
/**
* AJAX handler for getting template preview data
*/
public function ajax_get_template_preview() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_event_form_nonce')) {
wp_send_json_error('Security check failed');
return;
}
$template_id = sanitize_text_field($_POST['template_id'] ?? '');
if (empty($template_id)) {
wp_send_json_error('No template ID provided');
return;
}
// Get template manager instance
if (!class_exists('HVAC_Event_Template_Manager')) {
wp_send_json_error('Template manager not available');
return;
}
$template_manager = HVAC_Event_Template_Manager::instance();
$template = $template_manager->get_template($template_id);
if (!$template) {
wp_send_json_error('Template not found');
return;
}
// Return template data
wp_send_json_success($template);
}
/**
* AJAX handler for loading template data into form
*/
public function ajax_load_template_data() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_event_form_nonce')) {
wp_send_json_error('Security check failed');
return;
}
$template_id = sanitize_text_field($_POST['template_id'] ?? '');
if (empty($template_id)) {
wp_send_json_error('No template ID provided');
return;
}
// Get template manager instance
if (!class_exists('HVAC_Event_Template_Manager')) {
wp_send_json_error('Template manager not available');
return;
}
$template_manager = HVAC_Event_Template_Manager::instance();
$template = $template_manager->get_template($template_id);
if (!$template || empty($template['field_data'])) {
wp_send_json_error('Template data not found');
return;
}
// Return field data
wp_send_json_success($template['field_data']);
}
/**
* AJAX handler for saving new template
*/
public function ajax_save_template() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_event_form_nonce')) {
wp_send_json_error('Security check failed');
return;
}
// Check user permissions
if (!is_user_logged_in()) {
wp_send_json_error('Authentication required');
return;
}
$user = wp_get_current_user();
if (!array_intersect(['hvac_trainer', 'hvac_master_trainer'], $user->roles)) {
wp_send_json_error('Insufficient permissions');
return;
}
// Validate and sanitize input
$template_name = sanitize_text_field($_POST['template_name'] ?? '');
$template_description = sanitize_textarea_field($_POST['template_description'] ?? '');
$template_category = sanitize_text_field($_POST['template_category'] ?? '');
$is_public = (bool) ($_POST['is_public'] ?? false);
$field_data = $_POST['field_data'] ?? [];
if (empty($template_name)) {
wp_send_json_error('Template name is required');
return;
}
if (empty($template_category)) {
wp_send_json_error('Template category is required');
return;
}
// Sanitize field data
$sanitized_field_data = [];
foreach ($field_data as $key => $value) {
$sanitized_key = sanitize_key($key);
if (is_array($value)) {
$sanitized_field_data[$sanitized_key] = array_map('sanitize_text_field', $value);
} else {
$sanitized_field_data[$sanitized_key] = sanitize_text_field($value);
}
}
// Get template manager instance
if (!class_exists('HVAC_Event_Template_Manager')) {
wp_send_json_error('Template manager not available');
return;
}
$template_manager = HVAC_Event_Template_Manager::instance();
// Prepare template data
$template_data = [
'name' => $template_name,
'description' => $template_description,
'category' => $template_category,
'is_public' => $is_public,
'field_data' => $sanitized_field_data,
'created_by' => $user->ID,
'meta_data' => [
'source' => 'event_form',
'created_at' => current_time('mysql'),
'user_ip' => $_SERVER['REMOTE_ADDR'] ?? '',
]
];
// Create the template
$result = $template_manager->create_template($template_data);
if ($result['success']) {
wp_send_json_success([
'message' => 'Template saved successfully',
'template_id' => $result['template_id'] ?? null
]);
} else {
wp_send_json_error($result['error'] ?? 'Failed to save template');
}
}
}

File diff suppressed because it is too large Load diff