feat: implement comprehensive featured image system for events, organizers, and venues

- Add featured image field to main event creation form with WordPress media uploader
- Implement featured image upload in organizer and venue creation modals
- Update AJAX handlers to process and validate featured image attachments
- Add comprehensive media upload UI with preview and removal functionality
- Include proper permission validation for administrator, trainer, and master trainer roles
- Create authoritative documentation for complete event creation page functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ben 2025-09-26 20:24:31 -03:00
parent c3806f01c3
commit 91873c6a9c
4 changed files with 1002 additions and 6 deletions

View file

@ -47,6 +47,17 @@
this.closeModal();
}
});
// Media upload handlers
$(document).on('click', '.hvac-modal .select-image-btn', (e) => {
e.preventDefault();
this.openMediaUploader(e.target);
});
$(document).on('click', '.hvac-modal .remove-image', (e) => {
e.preventDefault();
this.removeImage(e.target);
});
}
createModalContainer() {
@ -104,7 +115,8 @@
{ name: 'organizer_name', label: 'Organizer Name', type: 'text', required: true },
{ name: 'organizer_email', label: 'Email', type: 'email', required: false },
{ name: 'organizer_website', label: 'Website', type: 'url', required: false },
{ name: 'organizer_phone', label: 'Phone', type: 'tel', required: false }
{ name: 'organizer_phone', label: 'Phone', type: 'tel', required: false },
{ name: 'organizer_featured_image', label: 'Featured Image', type: 'media', required: false }
],
action: 'hvac_create_organizer'
},
@ -127,7 +139,8 @@
{ name: 'venue_zip', label: 'Zip/Postal Code', type: 'text', required: false },
{ name: 'venue_country', label: 'Country', type: 'text', required: false },
{ name: 'venue_website', label: 'Website', type: 'url', required: false },
{ name: 'venue_phone', label: 'Phone', type: 'tel', required: false }
{ name: 'venue_phone', label: 'Phone', type: 'tel', required: false },
{ name: 'venue_featured_image', label: 'Featured Image', type: 'media', required: false }
],
action: 'hvac_create_venue'
}
@ -184,6 +197,33 @@
`;
}
if (field.type === 'media') {
return `
<div class="hvac-form-field hvac-media-field" data-field-name="${field.name}">
<label>${field.label}${requiredMark}</label>
<div class="media-upload-container">
<div class="image-preview-container" style="margin-bottom: 10px;">
<div class="image-preview" style="display: none; position: relative; max-width: 200px;">
<img src="" alt="Preview" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">
<button type="button" class="remove-image" style="position: absolute; top: 5px; right: 5px; background: #d63638; color: white; border: none; border-radius: 50%; width: 20px; height: 20px; cursor: pointer; font-size: 12px;">×</button>
</div>
</div>
<div class="upload-controls">
<button type="button" class="select-image-btn hvac-btn hvac-btn-secondary">
<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>
Select Image
</button>
<input type="hidden" name="${field.name}" class="image-id-input" value="">
<input type="hidden" name="${field.name}_url" class="image-url-input" value="">
</div>
<p class="description" style="margin-top: 5px; font-size: 12px; color: #666;">
Recommended: 300x300 pixels or larger
</p>
</div>
</div>
`;
}
return `
<div class="hvac-form-field">
<label for="${field.name}">${field.label}${requiredMark}</label>
@ -284,6 +324,68 @@
}, 5000);
}
openMediaUploader(button) {
const $button = $(button);
const $fieldContainer = $button.closest('.hvac-media-field');
const $imagePreview = $fieldContainer.find('.image-preview');
const $previewImg = $fieldContainer.find('.image-preview img');
const $imageIdInput = $fieldContainer.find('.image-id-input');
const $imageUrlInput = $fieldContainer.find('.image-url-input');
// Create WordPress media frame
const mediaUploader = wp.media({
title: 'Select Image',
button: {
text: 'Select Image'
},
multiple: false,
library: {
type: 'image'
}
});
// When an image is selected
mediaUploader.on('select', () => {
const attachment = mediaUploader.state().get('selection').first().toJSON();
// Update hidden inputs
$imageIdInput.val(attachment.id);
$imageUrlInput.val(attachment.url);
// Update preview
$previewImg.attr('src', attachment.url);
$previewImg.attr('alt', attachment.alt || attachment.title || 'Selected image');
$imagePreview.show();
// Update button text
$button.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Change Image');
});
// Open the media modal
mediaUploader.open();
}
removeImage(button) {
const $button = $(button);
const $fieldContainer = $button.closest('.hvac-media-field');
const $imagePreview = $fieldContainer.find('.image-preview');
const $previewImg = $fieldContainer.find('.image-preview img');
const $imageIdInput = $fieldContainer.find('.image-id-input');
const $imageUrlInput = $fieldContainer.find('.image-url-input');
const $selectBtn = $fieldContainer.find('.select-image-btn');
// Clear inputs
$imageIdInput.val('');
$imageUrlInput.val('');
// Hide preview
$imagePreview.hide();
$previewImg.attr('src', '');
// Reset button text
$selectBtn.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Select Image');
}
capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}

View file

@ -0,0 +1,739 @@
# HVAC Community Events - Event Creation Page Documentation
**Version:** 3.2.0
**Last Updated:** January 2025
**Status:** Production Ready
## Overview
The HVAC Community Events plugin provides a comprehensive event creation system designed specifically for HVAC training organizations. This document serves as the authoritative reference for the event creation page functionality, architecture, and implementation details.
## Table of Contents
1. [System Architecture](#system-architecture)
2. [User Interface Components](#user-interface-components)
3. [Form Fields Reference](#form-fields-reference)
4. [Dynamic Features](#dynamic-features)
5. [Role-Based Access Control](#role-based-access-control)
6. [API Endpoints](#api-endpoints)
7. [Security Implementation](#security-implementation)
8. [Template System](#template-system)
9. [Integration Points](#integration-points)
10. [Performance Considerations](#performance-considerations)
11. [Troubleshooting Guide](#troubleshooting-guide)
---
## System Architecture
### Core Components
The event creation system is built on a modular architecture with the following primary components:
#### 1. **HVAC_Event_Form_Builder**
- **File:** `includes/class-hvac-event-form-builder.php`
- **Purpose:** Central form generation and validation engine
- **Pattern:** Singleton with fluent interface
- **Responsibilities:**
- Form field rendering and validation
- Progressive disclosure management
- Template integration
- Media upload handling
- Security implementation
#### 2. **HVAC_Ajax_Handlers**
- **File:** `includes/class-hvac-ajax-handlers.php`
- **Purpose:** Server-side AJAX request processing
- **Pattern:** Singleton with dependency injection
- **Responsibilities:**
- AI-powered event population
- Dynamic search functionality
- Entity creation (venues, organizers, categories)
- Security validation and nonce verification
#### 3. **Frontend JavaScript Components**
- **AI Assistant:** `assets/js/hvac-ai-assist.js`
- **Searchable Selectors:** `assets/js/hvac-searchable-selectors.js`
- **Modal Forms:** `assets/js/hvac-modal-forms.js`
- **Purpose:** Rich user interface interactions
- **Pattern:** ES6 classes with event delegation
#### 4. **Template System**
- **File:** `templates/page-tec-create-event.php`
- **Purpose:** WordPress-native page template
- **Integration:** The Events Calendar compatibility
- **Features:** Responsive design, accessibility compliance
---
## User Interface Components
### Layout Structure
```
Event Creation Page
├── Header Section
│ ├── Page Title
│ ├── Breadcrumb Navigation
│ └── User Context Information
├── Main Form Container
│ ├── Basic Event Fields
│ ├── Featured Image Upload
│ ├── DateTime Management
│ ├── Venue Selection
│ ├── Organizer Management
│ ├── Category Assignment
│ └── Advanced Options (Collapsible)
├── AI Assistant Panel
│ ├── Input Methods (URL, Text, Manual)
│ ├── Processing Indicators
│ └── Confidence Metrics
└── Footer Actions
├── Save Draft
├── Preview
└── Publish Event
```
### Progressive Disclosure
The interface implements progressive disclosure to reduce cognitive load:
- **Basic Fields:** Always visible, essential information
- **Advanced Options:** Collapsible section for power users
- **Modal Forms:** Overlay interfaces for entity creation
- **AI Assistant:** Contextual panel that appears when needed
---
## Form Fields Reference
### Basic Event Information
#### Event Title
- **Type:** Text input
- **Validation:** 3-200 characters, required
- **Sanitization:** `sanitize_text_field()`
- **Purpose:** Primary event identifier
#### Event Description
- **Type:** WordPress TinyMCE rich text editor
- **Features:**
- Custom toolbar: Format, Bold, Italic, Lists, Links, Alignment
- Markdown-to-HTML conversion support
- Paste cleanup for external content
- **Validation:** HTML content filtering with `wp_kses`
- **Purpose:** Detailed event information
#### Featured Image
- **Type:** WordPress Media Uploader
- **Constraints:** Image files only
- **Recommended:** 1200x630 pixels
- **Storage:** WordPress attachment with post thumbnail relationship
- **Purpose:** Visual representation for event listings
### Date and Time Management
#### Start Date & Time
- **Type:** HTML5 `datetime-local` input
- **Validation:** Must be future date
- **Format:** WordPress-compatible datetime
- **Integration:** The Events Calendar meta fields
#### End Date & Time
- **Type:** HTML5 `datetime-local` input
- **Validation:** Must be after start date
- **Default:** 2 hours after start time
- **Integration:** TEC `_EventEndDate` meta field
#### Timezone
- **Type:** Dropdown select
- **Options:** WordPress timezone list
- **Default:** Site timezone setting
- **Storage:** `_EventTimezone` meta field
- **Advanced:** Hidden by default, revealed in advanced options
### Venue Management
#### Venue Selection
- **Type:** Searchable single-select
- **Search:** Real-time AJAX with 2+ character minimum
- **Features:**
- Autocomplete with venue address display
- "Add New" modal for inline venue creation
- Address validation and formatting
- **Integration:** TEC `_EventVenueID` relationship
#### Venue Creation Modal
- **Fields:**
- Venue Name (required)
- Address, City, State, Zip, Country
- Website, Phone
- Featured Image
- **Permissions:** Trainers, Master Trainers, Administrators
- **Validation:** Address normalization, phone formatting
### Organizer Management
#### Organizer Selection
- **Type:** Searchable multi-select
- **Limit:** Maximum 3 organizers per event
- **Search:** Real-time AJAX with contact information display
- **Features:**
- Selected item display with removal
- "Add New" modal for inline organizer creation
- Email and phone display in search results
- **Integration:** TEC `_EventOrganizerID` array
#### Organizer Creation Modal
- **Fields:**
- Organizer Name (required)
- Email, Website, Phone
- Featured Image
- **Permissions:** Trainers, Master Trainers, Administrators
- **Validation:** Email format, URL validation
### Category Assignment
#### Category Selection
- **Type:** Searchable multi-select
- **Limit:** Maximum 3 categories per event
- **Source:** WordPress `tribe_events_cat` taxonomy
- **Features:**
- Hierarchical category display
- Description tooltips
- Role-based creation permissions
- **Integration:** WordPress taxonomy relationship
#### Category Creation Modal
- **Fields:**
- Category Name (required)
- Description
- **Permissions:** Master Trainers only
- **Validation:** Taxonomy name sanitization
### Advanced Fields
#### Event Capacity
- **Type:** Number input
- **Range:** 1-10,000 attendees
- **Purpose:** Registration limit setting
- **Integration:** TEC capacity management
- **Advanced:** Hidden by default
#### Event Cost
- **Type:** Number input with decimal support
- **Format:** Currency display
- **Purpose:** Event pricing information
- **Integration:** TEC cost meta fields
- **Advanced:** Hidden by default
---
## Dynamic Features
### AI Assistant System
#### Input Methods
**1. URL Processing**
- **Supported Platforms:** EventBrite, Facebook Events, Meetup, General URLs
- **Process:**
1. URL validation and platform detection
2. Content extraction with timeout handling (60 seconds)
3. Data parsing and confidence scoring
4. Form field population with validation
- **Rate Limiting:** 10 requests per hour per user
**2. Text Extraction**
- **Purpose:** Process existing event descriptions or marketing copy
- **Features:**
- Smart field detection (title, date, location extraction)
- Markdown processing with list formatting
- Content cleanup and normalization
- **Validation:** 10+ character minimum
**3. Manual Description**
- **Purpose:** AI-generated content from user prompts
- **Features:**
- Context-aware content generation
- Professional formatting
- Industry-specific terminology
- **Output:** Structured markdown converted to HTML
#### Processing Flow
```mermaid
graph TD
A[User Input] --> B[Input Validation]
B --> C[AI Processing Request]
C --> D[Progress Indication]
D --> E[Response Processing]
E --> F[Confidence Analysis]
F --> G[Form Population]
G --> H[User Validation]
```
#### Confidence Scoring
The AI system provides confidence levels for each populated field:
- **High (90-100%):** Green indicator, auto-accept
- **Medium (70-89%):** Yellow indicator, user review recommended
- **Low (50-69%):** Orange indicator, manual verification required
- **Very Low (<50%):** Red indicator, suggests manual entry
### Searchable Selectors
#### Real-time Search Implementation
**Debounced AJAX Requests:**
- **Trigger:** 2+ characters entered
- **Delay:** 300ms debounce
- **Caching:** Client-side result caching for 5 minutes
- **Error Handling:** Graceful degradation with fallback options
**Search Results Display:**
- **Venue Results:** Name, full address, phone
- **Organizer Results:** Name, email, organization
- **Category Results:** Name, description, event count
#### Selection Management
**Multi-select Behavior (Organizers, Categories):**
- Visual selected item display with removal buttons
- Duplicate prevention
- Maximum selection enforcement
- Keyboard navigation support
**Single-select Behavior (Venues):**
- Replacement selection model
- Clear selection option
- Visual state management
### Modal Creation Forms
#### WordPress Media Integration
**Featured Image Upload:**
- **Interface:** Native WordPress Media Library
- **Features:**
- Image preview with removal option
- File type validation
- Size recommendations
- Alt text management
- **Storage:** WordPress attachment system
#### Form Validation
**Client-side Validation:**
- Real-time field validation
- Visual error indicators
- Progressive enhancement
**Server-side Validation:**
- Comprehensive input sanitization
- Business rule enforcement
- Security validation
---
## Role-Based Access Control
### Permission Levels
#### Unauthenticated Users
- **Access:** Redirected to login page
- **Message:** Clear authentication requirement
#### Standard WordPress Users
- **Access:** Denied with capability check
- **Requirement:** Must have HVAC-specific roles
#### HVAC Trainers
- **Permissions:**
- Create and edit events
- Create organizers and venues
- Use AI assistant features
- Access basic template system
- **Restrictions:**
- Cannot create categories
- Limited template management
#### HVAC Master Trainers
- **Permissions:**
- All trainer permissions
- Create and manage categories
- Full template system access
- Advanced configuration options
- User management capabilities
#### Administrators
- **Permissions:**
- Complete system access
- Plugin configuration
- Role management
- System maintenance
### Security Implementation
#### Nonce Verification
```php
// Example nonce verification
$security_check = HVAC_Ajax_Security::verify_ajax_request(
'create_event',
HVAC_Ajax_Security::NONCE_GENERAL,
array('hvac_trainer', 'hvac_master_trainer'),
false
);
```
#### Input Sanitization
- **Text Fields:** `sanitize_text_field()`
- **Email Fields:** `sanitize_email()`
- **URL Fields:** `esc_url_raw()`
- **HTML Content:** `wp_kses()` with allowed tags
- **Number Fields:** `absint()` or `floatval()`
---
## API Endpoints
### Core AJAX Handlers
#### Event Population
- **Endpoint:** `hvac_ai_populate_event`
- **Method:** POST
- **Purpose:** AI-powered event data extraction
- **Parameters:**
- `input_text` (string): URL or text content
- `input_type` (enum): 'url', 'text', 'description'
- `nonce` (string): Security token
- **Response:** JSON with populated field data and confidence scores
#### Search Endpoints
**Organizer Search:**
- **Endpoint:** `hvac_search_organizers`
- **Parameters:** `search` (string, 2+ chars)
- **Response:** Array of organizer objects with contact info
**Venue Search:**
- **Endpoint:** `hvac_search_venues`
- **Parameters:** `search` (string, 2+ chars)
- **Response:** Array of venue objects with address info
**Category Search:**
- **Endpoint:** `hvac_search_categories`
- **Parameters:** `search` (string, 2+ chars)
- **Response:** Array of category objects with descriptions
#### Entity Creation
**Create Organizer:**
- **Endpoint:** `hvac_create_organizer`
- **Required:** `organizer_name`
- **Optional:** `organizer_email`, `organizer_website`, `organizer_phone`, `organizer_featured_image`
- **Response:** Created organizer object
**Create Venue:**
- **Endpoint:** `hvac_create_venue`
- **Required:** `venue_name`
- **Optional:** Address fields, contact info, featured image
- **Response:** Created venue object
**Create Category:**
- **Endpoint:** `hvac_create_category`
- **Required:** `category_name`
- **Optional:** `category_description`
- **Permissions:** Master Trainers only
- **Response:** Created category object
### Response Format
#### Success Response
```json
{
"success": true,
"data": {
"id": 123,
"title": "Created Item Name",
"subtitle": "Additional information",
"confidence": 85
}
}
```
#### Error Response
```json
{
"success": false,
"data": {
"message": "Error description",
"code": "error_type",
"field_errors": {
"field_name": "Specific field error"
}
}
}
```
---
## Security Implementation
### Authentication & Authorization
#### WordPress Integration
- **User Authentication:** WordPress session management
- **Capability Checking:** Custom capabilities with role mapping
- **Nonce System:** Action-specific tokens with expiration
#### HVAC_Ajax_Security Class
```php
class HVAC_Ajax_Security {
const NONCE_GENERAL = 'hvac_general';
const NONCE_AI = 'hvac_ai';
public static function verify_ajax_request($action, $nonce_action, $required_roles, $check_capabilities) {
// Comprehensive security validation
}
}
```
### Input Validation
#### Validation Rules
```php
$input_rules = array(
'event_title' => array(
'type' => 'text',
'required' => true,
'min_length' => 3,
'max_length' => 200
),
'event_description' => array(
'type' => 'html',
'required' => false,
'allowed_tags' => 'p,br,strong,em,ul,ol,li,h2,h3,h4,h5,h6'
)
);
```
#### XSS Prevention
- **Output Escaping:** All user content escaped with appropriate functions
- **HTML Filtering:** Allowed tag whitelist with attribute sanitization
- **JavaScript Safety:** JSON data properly escaped for client consumption
### Rate Limiting
#### AI Endpoint Protection
- **Rate:** 10 requests per hour per user
- **Storage:** WordPress transients
- **Reset:** Hourly cleanup with WP Cron
- **Error Handling:** Clear rate limit messages
---
## Template System
### Template Architecture
#### Template Storage
- **Format:** JSON with metadata
- **Location:** WordPress options table
- **Versioning:** Timestamp-based versioning
- **Backup:** Automatic backup on modification
#### Template Categories
- **General:** Basic event templates
- **Training:** Technical training sessions
- **Workshop:** Hands-on workshops
- **Certification:** Certification programs
- **Conference:** Large-scale events
### Template Management
#### Template Creation
```javascript
// Save current form as template
const templateData = {
name: 'Template Name',
category: 'training',
fields: {
event_title: 'Template Title',
event_description: 'Template Description',
// ... other fields
},
metadata: {
created_by: userId,
created_date: timestamp,
usage_count: 0
}
};
```
#### Template Application
- **Field Mapping:** Intelligent field matching
- **Conflict Resolution:** User confirmation for overrides
- **Partial Application:** Selective field application
- **Confidence Scoring:** Template fit analysis
---
## Integration Points
### The Events Calendar (TEC)
#### Post Type Integration
- **Events:** `tribe_events` post type
- **Venues:** `tribe_venue` post type
- **Organizers:** `tribe_organizer` post type
- **Categories:** `tribe_events_cat` taxonomy
#### Meta Field Mapping
```php
// TEC meta field integration
$tec_meta = array(
'_EventStartDate' => $start_datetime,
'_EventEndDate' => $end_datetime,
'_EventTimezone' => $timezone,
'_EventVenueID' => $venue_id,
'_EventOrganizerID' => $organizer_ids,
'_EventCost' => $event_cost,
'_EventCapacity' => $event_capacity
);
```
### WordPress Core
#### Media Library
- **Featured Images:** Post thumbnail system
- **File Handling:** WordPress upload system
- **Security:** File type validation
#### User Management
- **Roles:** Custom HVAC roles
- **Capabilities:** Fine-grained permissions
- **Session Handling:** WordPress authentication
#### Taxonomy System
- **Categories:** Native WordPress taxonomy
- **Hierarchical:** Support for nested categories
- **Meta:** Additional category metadata
---
## Performance Considerations
### Frontend Optimization
#### JavaScript Loading
- **Conditional Loading:** Scripts only on event creation page
- **Dependency Management:** Proper WordPress enqueueing
- **Minification:** Production asset minification
- **Caching:** Browser caching headers
#### AJAX Optimization
- **Request Debouncing:** Reduced server requests
- **Response Caching:** Client-side result caching
- **Pagination:** Large result set pagination
- **Compression:** Gzip response compression
### Backend Optimization
#### Database Queries
- **Prepared Statements:** SQL injection prevention
- **Query Optimization:** Efficient database queries
- **Indexing:** Proper database indexing
- **Caching:** WordPress object caching
#### Memory Management
- **Object Lifecycle:** Proper object destruction
- **Image Processing:** Optimized image handling
- **Garbage Collection:** PHP memory management
---
## Troubleshooting Guide
### Common Issues
#### AI Assistant Not Working
**Symptoms:** AI requests fail or timeout
**Causes:**
- API rate limiting exceeded
- Network connectivity issues
- Invalid input format
**Solutions:**
1. Check rate limit status
2. Verify network connectivity
3. Validate input format
4. Check error logs
#### Search Not Returning Results
**Symptoms:** Empty search results despite existing data
**Causes:**
- Insufficient search term length
- Database connectivity issues
- Permission problems
**Solutions:**
1. Ensure 2+ character search terms
2. Verify database connection
3. Check user permissions
4. Clear search cache
#### Modal Forms Not Opening
**Symptoms:** "Add New" buttons not working
**Causes:**
- JavaScript errors
- Permission restrictions
- Missing dependencies
**Solutions:**
1. Check browser console for errors
2. Verify user role permissions
3. Ensure WordPress media scripts loaded
4. Clear browser cache
### Error Logging
#### WordPress Debug Integration
```php
// Enable debug logging
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
// Log custom events
error_log('HVAC Event Creation: ' . $message);
```
#### Custom Error Tracking
- **Error Categorization:** System, user, validation errors
- **Context Capture:** User, action, environment data
- **Notification System:** Admin notifications for critical errors
### Performance Monitoring
#### Key Metrics
- **Page Load Time:** Target < 2 seconds
- **AJAX Response Time:** Target < 500ms
- **Database Queries:** Monitor N+1 queries
- **Memory Usage:** Monitor PHP memory consumption
#### Monitoring Tools
- **WordPress Debug Bar:** Development debugging
- **Query Monitor:** Database query analysis
- **Server Monitoring:** Application performance monitoring
---
## Conclusion
The HVAC Community Events plugin provides a comprehensive, secure, and user-friendly event creation system. This documentation serves as the authoritative reference for developers, administrators, and users working with the system.
For additional support or feature requests, please refer to the project repository or contact the development team.
---
**Document Version:** 1.0
**Next Review:** July 2025
**Maintained By:** HVAC Development Team

View file

@ -1287,7 +1287,7 @@ class HVAC_Ajax_Handlers {
$security_check = HVAC_Ajax_Security::verify_ajax_request(
'create_organizer',
HVAC_Ajax_Security::NONCE_GENERAL,
array('hvac_trainer', 'hvac_master_trainer'),
array('administrator', 'hvac_trainer', 'hvac_master_trainer'),
false
);
@ -1319,6 +1319,10 @@ class HVAC_Ajax_Handlers {
'type' => 'text',
'required' => false,
'max_length' => 20
),
'organizer_featured_image' => array(
'type' => 'text',
'required' => false
)
);
@ -1361,6 +1365,14 @@ class HVAC_Ajax_Handlers {
update_post_meta($organizer_id, '_OrganizerPhone', $params['organizer_phone']);
}
// Set featured image if provided
if (!empty($params['organizer_featured_image'])) {
$image_id = absint($params['organizer_featured_image']);
if ($image_id && wp_attachment_is_image($image_id)) {
set_post_thumbnail($organizer_id, $image_id);
}
}
// Return created organizer data
wp_send_json_success(array(
'id' => $organizer_id,
@ -1456,7 +1468,7 @@ class HVAC_Ajax_Handlers {
$security_check = HVAC_Ajax_Security::verify_ajax_request(
'create_venue',
HVAC_Ajax_Security::NONCE_GENERAL,
array('hvac_trainer', 'hvac_master_trainer'),
array('administrator', 'hvac_trainer', 'hvac_master_trainer'),
false
);
@ -1509,6 +1521,10 @@ class HVAC_Ajax_Handlers {
'type' => 'text',
'required' => false,
'max_length' => 20
),
'venue_featured_image' => array(
'type' => 'text',
'required' => false
)
);
@ -1557,6 +1573,14 @@ class HVAC_Ajax_Handlers {
}
}
// Set featured image if provided
if (!empty($params['venue_featured_image'])) {
$image_id = absint($params['venue_featured_image']);
if ($image_id && wp_attachment_is_image($image_id)) {
set_post_thumbnail($venue_id, $image_id);
}
}
// Build subtitle for display
$subtitle_parts = array_filter(array(
$params['venue_address'],

View file

@ -172,6 +172,9 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
// Basic event fields
$this->add_basic_event_fields();
// Featured image field
$this->add_featured_image_field();
/**
* Action hook for TEC ticketing integration
*
@ -323,6 +326,23 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
return $this;
}
/**
* Add featured image field for event
*/
public function add_featured_image_field(): self {
$featured_image_field = [
'type' => 'custom',
'name' => 'event_featured_image',
'label' => 'Featured Image',
'custom_html' => $this->render_media_upload_field('event_featured_image', 'Select Event Image'),
'wrapper_class' => 'form-row featured-image-field'
];
$this->add_field($featured_image_field);
return $this;
}
/**
* Add datetime fields for event scheduling
*/
@ -1475,7 +1495,9 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
*/
private function render_searchable_organizer_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);
$can_create = in_array('administrator', $current_user->roles) ||
in_array('hvac_trainer', $current_user->roles) ||
in_array('hvac_master_trainer', $current_user->roles);
return <<<HTML
<div class="form-row organizer-selector-wrapper">
@ -1569,7 +1591,9 @@ HTML;
*/
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);
$can_create = in_array('administrator', $current_user->roles) ||
in_array('hvac_trainer', $current_user->roles) ||
in_array('hvac_master_trainer', $current_user->roles);
return <<<HTML
<div class="form-row venue-selector-wrapper">
@ -1710,4 +1734,111 @@ HTML;
<?php
return ob_get_clean();
}
/**
* Render media upload field for featured images
*
* @param string $field_name The input field name
* @param string $button_text The button text
* @return string
*/
private function render_media_upload_field(string $field_name, string $button_text = 'Select Image'): string {
ob_start();
?>
<div class="media-upload-field" data-field-name="<?php echo esc_attr($field_name); ?>">
<div class="image-preview-container" style="margin-bottom: 10px;">
<div class="image-preview" style="display: none; position: relative; max-width: 300px;">
<img src="" alt="Preview" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">
<button type="button" class="remove-image" style="position: absolute; top: 5px; right: 5px; background: #d63638; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 12px;">×</button>
</div>
</div>
<div class="upload-controls">
<button type="button" class="select-image-btn button button-secondary">
<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>
<?php echo esc_html($button_text); ?>
</button>
<input type="hidden" name="<?php echo esc_attr($field_name); ?>" class="image-id-input" value="">
<input type="hidden" name="<?php echo esc_attr($field_name); ?>_url" class="image-url-input" value="">
</div>
<p class="description" style="margin-top: 5px;">
Recommended size: 1200x630 pixels for optimal display across devices.
</p>
</div>
<script>
jQuery(document).ready(function($) {
// Initialize media upload for this field
var fieldContainer = $('.media-upload-field[data-field-name="<?php echo esc_js($field_name); ?>"]');
var selectBtn = fieldContainer.find('.select-image-btn');
var removeBtn = fieldContainer.find('.remove-image');
var imagePreview = fieldContainer.find('.image-preview');
var previewImg = fieldContainer.find('.image-preview img');
var imageIdInput = fieldContainer.find('.image-id-input');
var imageUrlInput = fieldContainer.find('.image-url-input');
// WordPress media uploader
var mediaUploader;
selectBtn.on('click', function(e) {
e.preventDefault();
// If the uploader object has already been created, reopen the dialog
if (mediaUploader) {
mediaUploader.open();
return;
}
// Create the media frame
mediaUploader = wp.media({
title: '<?php echo esc_js($button_text); ?>',
button: {
text: 'Select Image'
},
multiple: false,
library: {
type: 'image'
}
});
// When an image is selected, run a callback
mediaUploader.on('select', function() {
var attachment = mediaUploader.state().get('selection').first().toJSON();
// Update hidden inputs
imageIdInput.val(attachment.id);
imageUrlInput.val(attachment.url);
// Update preview
previewImg.attr('src', attachment.url);
previewImg.attr('alt', attachment.alt || attachment.title || 'Selected image');
imagePreview.show();
// Update button text
selectBtn.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Change Image');
});
// Open the uploader dialog
mediaUploader.open();
});
// Remove image
removeBtn.on('click', function(e) {
e.preventDefault();
// Clear inputs
imageIdInput.val('');
imageUrlInput.val('');
// Hide preview
imagePreview.hide();
previewImg.attr('src', '');
// Reset button text
selectBtn.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span><?php echo esc_js($button_text); ?>');
});
});
</script>
<?php
return ob_get_clean();
}
}