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:
parent
c3806f01c3
commit
91873c6a9c
4 changed files with 1002 additions and 6 deletions
|
|
@ -47,6 +47,17 @@
|
||||||
this.closeModal();
|
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() {
|
createModalContainer() {
|
||||||
|
|
@ -104,7 +115,8 @@
|
||||||
{ name: 'organizer_name', label: 'Organizer Name', type: 'text', required: true },
|
{ name: 'organizer_name', label: 'Organizer Name', type: 'text', required: true },
|
||||||
{ name: 'organizer_email', label: 'Email', type: 'email', required: false },
|
{ name: 'organizer_email', label: 'Email', type: 'email', required: false },
|
||||||
{ name: 'organizer_website', label: 'Website', type: 'url', 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'
|
action: 'hvac_create_organizer'
|
||||||
},
|
},
|
||||||
|
|
@ -127,7 +139,8 @@
|
||||||
{ name: 'venue_zip', label: 'Zip/Postal Code', type: 'text', required: false },
|
{ name: 'venue_zip', label: 'Zip/Postal Code', type: 'text', required: false },
|
||||||
{ name: 'venue_country', label: 'Country', type: 'text', required: false },
|
{ name: 'venue_country', label: 'Country', type: 'text', required: false },
|
||||||
{ name: 'venue_website', label: 'Website', type: 'url', 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'
|
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 `
|
return `
|
||||||
<div class="hvac-form-field">
|
<div class="hvac-form-field">
|
||||||
<label for="${field.name}">${field.label}${requiredMark}</label>
|
<label for="${field.name}">${field.label}${requiredMark}</label>
|
||||||
|
|
@ -284,6 +324,68 @@
|
||||||
}, 5000);
|
}, 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) {
|
capitalizeFirst(str) {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
739
docs/EVENT-CREATION-PAGE-DOCUMENTATION.md
Normal file
739
docs/EVENT-CREATION-PAGE-DOCUMENTATION.md
Normal 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
|
||||||
|
|
@ -1287,7 +1287,7 @@ class HVAC_Ajax_Handlers {
|
||||||
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
||||||
'create_organizer',
|
'create_organizer',
|
||||||
HVAC_Ajax_Security::NONCE_GENERAL,
|
HVAC_Ajax_Security::NONCE_GENERAL,
|
||||||
array('hvac_trainer', 'hvac_master_trainer'),
|
array('administrator', 'hvac_trainer', 'hvac_master_trainer'),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1319,6 +1319,10 @@ class HVAC_Ajax_Handlers {
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'max_length' => 20
|
'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']);
|
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
|
// Return created organizer data
|
||||||
wp_send_json_success(array(
|
wp_send_json_success(array(
|
||||||
'id' => $organizer_id,
|
'id' => $organizer_id,
|
||||||
|
|
@ -1456,7 +1468,7 @@ class HVAC_Ajax_Handlers {
|
||||||
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
||||||
'create_venue',
|
'create_venue',
|
||||||
HVAC_Ajax_Security::NONCE_GENERAL,
|
HVAC_Ajax_Security::NONCE_GENERAL,
|
||||||
array('hvac_trainer', 'hvac_master_trainer'),
|
array('administrator', 'hvac_trainer', 'hvac_master_trainer'),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1509,6 +1521,10 @@ class HVAC_Ajax_Handlers {
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'max_length' => 20
|
'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
|
// Build subtitle for display
|
||||||
$subtitle_parts = array_filter(array(
|
$subtitle_parts = array_filter(array(
|
||||||
$params['venue_address'],
|
$params['venue_address'],
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,9 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
||||||
// Basic event fields
|
// Basic event fields
|
||||||
$this->add_basic_event_fields();
|
$this->add_basic_event_fields();
|
||||||
|
|
||||||
|
// Featured image field
|
||||||
|
$this->add_featured_image_field();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action hook for TEC ticketing integration
|
* Action hook for TEC ticketing integration
|
||||||
*
|
*
|
||||||
|
|
@ -323,6 +326,23 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
||||||
return $this;
|
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
|
* 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 {
|
private function render_searchable_organizer_selector(): string {
|
||||||
$current_user = wp_get_current_user();
|
$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
|
return <<<HTML
|
||||||
<div class="form-row organizer-selector-wrapper">
|
<div class="form-row organizer-selector-wrapper">
|
||||||
|
|
@ -1569,7 +1591,9 @@ HTML;
|
||||||
*/
|
*/
|
||||||
private function render_searchable_venue_selector(): string {
|
private function render_searchable_venue_selector(): string {
|
||||||
$current_user = wp_get_current_user();
|
$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
|
return <<<HTML
|
||||||
<div class="form-row venue-selector-wrapper">
|
<div class="form-row venue-selector-wrapper">
|
||||||
|
|
@ -1710,4 +1734,111 @@ HTML;
|
||||||
<?php
|
<?php
|
||||||
return ob_get_clean();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue