feat: Implement Training Leads system and restructure navigation menu
- Add comprehensive Training Leads system for HVAC trainers * New /trainer/training-leads/ page with tabular contact submission display * HVAC_Training_Leads class with AJAX status updates and filtering * Empty state messaging and profile sharing CTA * Database integration with existing contact forms system - Restructure trainer navigation menu for better UX * Rename "Customize" to "Profile" with logical groupings * Move "Logout" under "Profile" submenu * Change "Personal Profile" to "Trainer Profile" * Add "Training Leads" under Profile section * Update help menu to show only question mark icon positioned far right - Enhance documentation system * Fix /trainer/documentation/ page styling and navigation integration * Update content to reflect current platform features * Add Training Leads documentation and navigation guide * Implement proper WordPress template structure - Update user management * Change joe@upskillhvac.com display name to "Joe Medosch" * Assign Joe as author of measureQuick headquarters venue * Assign Joe as author of measureQuick and Upskill HVAC organizers 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2446997385
commit
705e6b563c
31 changed files with 8163 additions and 168 deletions
|
|
@ -179,5 +179,9 @@ For detailed information on any topic, refer to the comprehensive documentation
|
||||||
- **Role Field and Certification System Implementation (2025-08-01)**: Added comprehensive user role field to registration, profile display, and profile edit with 10 role options (technician, installer, supervisor, manager, trainer, consultant, sales representative, engineer, business owner, other). Implemented advanced certification tracking system with three meta fields: date_certified (date picker), certification_type (dropdown with "Certified measureQuick Trainer" and "Certified measureQuick Champion"), and certification_status (status badges for Active/Expired/Pending/Disabled). Features sophisticated role-based access control where regular trainers see read-only certification fields while administrators and master trainers have full edit access. All 25 users automatically migrated with appropriate default values during plugin activation. System includes professional CSS styling with color-coded status badges, comprehensive server-side validation, and complete E2E test coverage. Documentation updated with access control patterns and API reference.
|
- **Role Field and Certification System Implementation (2025-08-01)**: Added comprehensive user role field to registration, profile display, and profile edit with 10 role options (technician, installer, supervisor, manager, trainer, consultant, sales representative, engineer, business owner, other). Implemented advanced certification tracking system with three meta fields: date_certified (date picker), certification_type (dropdown with "Certified measureQuick Trainer" and "Certified measureQuick Champion"), and certification_status (status badges for Active/Expired/Pending/Disabled). Features sophisticated role-based access control where regular trainers see read-only certification fields while administrators and master trainers have full edit access. All 25 users automatically migrated with appropriate default values during plugin activation. System includes professional CSS styling with color-coded status badges, comprehensive server-side validation, and complete E2E test coverage. Documentation updated with access control patterns and API reference.
|
||||||
- **Enhanced CSV Import System Implementation (2025-08-04)**: Resolved critical issue where trainer profiles were missing comprehensive information from CSV_Trainers_Import_1Aug2025.csv file. Root cause: existing import system used hardcoded data instead of reading actual CSV file containing 19 fields of trainer information. Solution: Created complete enhanced CSV import system (includes/enhanced-csv-import-from-file.php) that reads actual CSV file and imports all available fields including phone numbers, company websites, certification details, business types, and training audiences. System features: processes 43 trainer records, integrates with WordPress taxonomy system for business_type and training_audience classifications, handles comma-separated multi-value fields, automatically creates venues and organizers based on CSV flags, comprehensive error handling and progress tracking. Updated AJAX handler in class-hvac-geocoding-ajax.php for seamless integration with master trainer interface. Testing results: 43 rows processed, 43 profiles updated with enhanced data, proper taxonomy assignments, automatic venue/organizer creation, zero errors. System now provides complete professional profiles with contact information, certification tracking, business categorization, and event management integration. All components deployed to staging and verified working.
|
- **Enhanced CSV Import System Implementation (2025-08-04)**: Resolved critical issue where trainer profiles were missing comprehensive information from CSV_Trainers_Import_1Aug2025.csv file. Root cause: existing import system used hardcoded data instead of reading actual CSV file containing 19 fields of trainer information. Solution: Created complete enhanced CSV import system (includes/enhanced-csv-import-from-file.php) that reads actual CSV file and imports all available fields including phone numbers, company websites, certification details, business types, and training audiences. System features: processes 43 trainer records, integrates with WordPress taxonomy system for business_type and training_audience classifications, handles comma-separated multi-value fields, automatically creates venues and organizers based on CSV flags, comprehensive error handling and progress tracking. Updated AJAX handler in class-hvac-geocoding-ajax.php for seamless integration with master trainer interface. Testing results: 43 rows processed, 43 profiles updated with enhanced data, proper taxonomy assignments, automatic venue/organizer creation, zero errors. System now provides complete professional profiles with contact information, certification tracking, business categorization, and event management integration. All components deployed to staging and verified working.
|
||||||
- **Certificate Pages Template System Fix (2025-08-01)**: Resolved critical issue where certificate pages (/trainer/certificate-reports/, /trainer/generate-certificates/) were completely bypassing WordPress template system, showing only bare shortcode content without theme headers, navigation, or styling. Root cause: load_custom_templates() method in class-hvac-community-events.php was loading content-only templates from templates/certificates/ instead of proper page templates. Solution: Updated template paths to use templates/page-certificate-reports.php and templates/page-generate-certificates.php with full WordPress integration. Fixed duplicate breadcrumbs by adding aggressive Astra theme breadcrumb disable filters in HVAC_Astra_Integration class. Resolved missing navigation menu by removing problematic HVAC_NAV_RENDERED constant checks in page templates. Certificate pages now display with complete theme integration: proper headers/footers, single set of breadcrumbs, full navigation menu, and consistent styling. All fixes deployed to staging and verified working.
|
- **Certificate Pages Template System Fix (2025-08-01)**: Resolved critical issue where certificate pages (/trainer/certificate-reports/, /trainer/generate-certificates/) were completely bypassing WordPress template system, showing only bare shortcode content without theme headers, navigation, or styling. Root cause: load_custom_templates() method in class-hvac-community-events.php was loading content-only templates from templates/certificates/ instead of proper page templates. Solution: Updated template paths to use templates/page-certificate-reports.php and templates/page-generate-certificates.php with full WordPress integration. Fixed duplicate breadcrumbs by adding aggressive Astra theme breadcrumb disable filters in HVAC_Astra_Integration class. Resolved missing navigation menu by removing problematic HVAC_NAV_RENDERED constant checks in page templates. Certificate pages now display with complete theme integration: proper headers/footers, single set of breadcrumbs, full navigation menu, and consistent styling. All fixes deployed to staging and verified working.
|
||||||
|
- **MapGeo Certification Color Field Known Bug (2025-08-05)**: DOCUMENTED BUG: MapGeo plugin fails to render markers when certification_color field is mapped to "Fill Colour" in Other Map Fields configuration. Issue occurs despite all 53 trainer profiles having valid hex color values (#f19a42 for Champions, #5077bb for Trainers, #f0f7e8 for others). Root cause unknown - likely MapGeo plugin compatibility issue with custom post meta fields or specific hex color format requirements. Workaround: Remove certification_color from MapGeo Fill Colour mapping to restore marker visibility. Markers display correctly without color customization. Future investigation needed to determine MapGeo's expected color field format or alternative integration method for trainer certification-based marker styling.
|
||||||
|
- **Welcome Popup System Implementation (2025-08-05)**: Implemented comprehensive Welcome Popup system for new HVAC trainer onboarding with 4-card interactive carousel showcasing platform features. System includes account status filtering (shows only to approved/active/inactive users, blocks pending/disabled), WCAG 2.1 AA accessibility compliance, Astra theme integration, and responsive design. Fixed critical overlapping navigation elements issue where dots/arrows covered footer content, making buttons unclickable. Final version (v1.0.6) features proper carousel height calculations (460px desktop, 380px tablet, 350px mobile), enhanced navigation spacing (20px 0 50px), and z-index layering (footer z-index: 10, elements z-index: 11). Includes dismissal management, WordPress security standards (nonce verification, capability checks), and comprehensive documentation. Deployed to staging and production-ready. Key files: includes/class-hvac-welcome-popup.php, assets/css/hvac-welcome-popup.css, assets/js/hvac-welcome-popup.js, docs/WELCOME-POPUP-SYSTEM.md.
|
||||||
|
- **Training Leads and Navigation Menu Restructure (2025-08-05)**: Implemented comprehensive Training Leads system for HVAC trainers with contact form submission management. Created new "Training Leads" page (/trainer/training-leads/) with HVAC_Training_Leads class (includes/class-hvac-training-leads.php) and dedicated template (templates/page-trainer-training-leads.php). Features tabular display of contact submissions with Date, Name, Email, Phone, City, State/Province, and Message columns, AJAX-powered status updates, empty state messaging, and CTA for profile sharing. Restructured trainer navigation menu: renamed "Customize" to "Profile", moved "Logout" under "Profile" submenu, changed "Personal Profile" to "Trainer Profile", added "Training Leads" under Profile section. Updated help menu to display only question mark icon (no text) positioned to far right using CSS flexbox (margin-left: auto, order: 999). Enhanced documentation page with complete WordPress integration, proper navigation/breadcrumbs, and updated content reflecting current platform features including Training Leads system. All components deployed to staging with comprehensive E2E test coverage verification. Navigation changes improve UX by grouping related features under logical sections and providing quick access to new lead management functionality.
|
||||||
|
- **Joe Medosch Account Updates (2025-08-05)**: Updated user joe@upskillhvac.com (ID: 20) display name from "Joe UpskillHVAC" to "Joe Medosch". Assigned Joe as author of key organizational assets: measureQuick headquarters venue (ID: 4869), both measureQuick organizer entries (IDs: 5429, 1667), and Upskill HVAC organizer (ID: 1647). All changes verified on staging server. Joe now properly credited in system as the lead contact for primary organizational entities, ensuring accurate attribution for training events and venue management. Updates maintain data integrity while reflecting correct organizational leadership structure.
|
||||||
|
|
||||||
[... rest of the existing content remains unchanged ...]
|
[... rest of the existing content remains unchanged ...]
|
||||||
|
|
@ -682,6 +682,137 @@ Retrieves current geocoding coverage and statistics.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Trainer Profile Sharing API
|
||||||
|
|
||||||
|
### HVAC_QR_Generator Class
|
||||||
|
|
||||||
|
Handles QR code generation and profile sharing functionality.
|
||||||
|
|
||||||
|
```php
|
||||||
|
class HVAC_QR_Generator {
|
||||||
|
/**
|
||||||
|
* Generate QR code URL using QR Server API
|
||||||
|
* @param string $data Data to encode in QR code
|
||||||
|
* @param int $size QR code size in pixels (default: 200)
|
||||||
|
* @param string $error_correction Error correction level (L, M, Q, H)
|
||||||
|
* @return string QR code image URL
|
||||||
|
*/
|
||||||
|
public function generate_qr_url($data, $size = 200, $error_correction = 'M')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code for trainer profile
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @param int $size QR code size in pixels
|
||||||
|
* @return string|false QR code URL or false on error
|
||||||
|
*/
|
||||||
|
public function generate_trainer_profile_qr($profile_id, $size = 200)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get shareable trainer profile URL
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @return string|false Profile URL or false on error
|
||||||
|
*/
|
||||||
|
public function get_trainer_profile_share_url($profile_id)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get trainer profile data for sharing
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @return array|false Profile data or false on error
|
||||||
|
*/
|
||||||
|
public function get_trainer_share_data($profile_id)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate profile card HTML for sharing
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @param array $options Display options
|
||||||
|
* @return string|false HTML content or false on error
|
||||||
|
*/
|
||||||
|
public function generate_profile_card_html($profile_id, $options = [])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Profile Sharing AJAX Endpoint
|
||||||
|
|
||||||
|
#### `hvac_get_profile_share_data`
|
||||||
|
|
||||||
|
**Description:** Retrieve trainer profile sharing data including QR code URL
|
||||||
|
|
||||||
|
**Method:** POST
|
||||||
|
**Permissions:** Any logged-in user
|
||||||
|
**Nonce:** `hvac_profile_sharing`
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```javascript
|
||||||
|
jQuery.post(hvac_sharing.ajax_url, {
|
||||||
|
action: 'hvac_get_profile_share_data',
|
||||||
|
profile_id: 5840,
|
||||||
|
nonce: hvac_sharing.nonce
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"profile_id": 5840,
|
||||||
|
"user_id": 123,
|
||||||
|
"trainer_name": "John Smith",
|
||||||
|
"business_name": "HVAC Pro Services",
|
||||||
|
"trainer_city": "Atlanta",
|
||||||
|
"trainer_state": "GA",
|
||||||
|
"certification_type": "Certified measureQuick Trainer",
|
||||||
|
"profile_image": "https://example.com/uploads/profile.jpg",
|
||||||
|
"share_url": "https://example.com/find-a-trainer/profile/5840/",
|
||||||
|
"qr_code_url": "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https%3A//example.com/find-a-trainer/profile/5840/&ecc=M"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**QR Code API Details:**
|
||||||
|
- **Service:** QR Server API (https://api.qrserver.com/v1/create-qr-code/)
|
||||||
|
- **Format:** PNG image
|
||||||
|
- **Default Size:** 200x200 pixels
|
||||||
|
- **Error Correction:** M (Medium, ~15% damage recovery)
|
||||||
|
- **Encoding:** URL-encoded profile share URL
|
||||||
|
|
||||||
|
**Share URL Structure:**
|
||||||
|
- **Pattern:** `/find-a-trainer/profile/{profile_id}/`
|
||||||
|
- **Rewrite Rule:** `^find-a-trainer/profile/([0-9]+)/?$`
|
||||||
|
- **Query Var:** `trainer_profile_id`
|
||||||
|
- **Requirements:** Must include trailing slash for WordPress rewrite rules
|
||||||
|
|
||||||
|
**JavaScript Integration:**
|
||||||
|
```javascript
|
||||||
|
// Profile sharing object
|
||||||
|
var ProfileSharing = {
|
||||||
|
// Open share modal
|
||||||
|
openShareModal: function(profileId) {},
|
||||||
|
|
||||||
|
// Load share data via AJAX
|
||||||
|
loadShareData: function(profileId) {},
|
||||||
|
|
||||||
|
// Create profile card HTML
|
||||||
|
createProfileCardHtml: function(data) {},
|
||||||
|
|
||||||
|
// Copy URL to clipboard
|
||||||
|
copyShareUrl: function() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Localized variables required
|
||||||
|
hvac_sharing.ajax_url // WordPress AJAX URL
|
||||||
|
hvac_sharing.nonce // Security nonce
|
||||||
|
hvac_sharing.strings // UI messages
|
||||||
|
```
|
||||||
|
|
||||||
|
**Profile Card Features:**
|
||||||
|
- Professional trainer photo or initial placeholder
|
||||||
|
- measureQuick certification badge overlay
|
||||||
|
- Business name and location display
|
||||||
|
- QR code for instant sharing
|
||||||
|
- Responsive design (600x300px default)
|
||||||
|
- Professional styling with rounded corners
|
||||||
|
|
||||||
## Error Codes
|
## Error Codes
|
||||||
|
|
||||||
### Geocoding Errors
|
### Geocoding Errors
|
||||||
|
|
|
||||||
|
|
@ -297,3 +297,64 @@ None - plugin uses WordPress posts, user meta, and post meta
|
||||||
- TCPDF library for PDF creation
|
- TCPDF library for PDF creation
|
||||||
- Custom certificate templates
|
- Custom certificate templates
|
||||||
- Batch processing support
|
- Batch processing support
|
||||||
|
|
||||||
|
### Trainer Profile Sharing System
|
||||||
|
- QR code generation using QR Server API
|
||||||
|
- Shareable profile URLs with rewrite rules
|
||||||
|
- AJAX-powered sharing modals
|
||||||
|
- Professional profile cards with certification badges
|
||||||
|
|
||||||
|
## Profile Sharing Configuration
|
||||||
|
|
||||||
|
### QR Code Generation
|
||||||
|
The system uses QR Server API for generating QR codes:
|
||||||
|
```php
|
||||||
|
// API Endpoint
|
||||||
|
https://api.qrserver.com/v1/create-qr-code/
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
- size: QR code dimensions (default: 200x200)
|
||||||
|
- data: URL to encode
|
||||||
|
- ecc: Error correction level (L, M, Q, H)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Share URL Structure
|
||||||
|
Profile sharing URLs follow this pattern:
|
||||||
|
```
|
||||||
|
https://example.com/find-a-trainer/profile/{profile_id}/
|
||||||
|
```
|
||||||
|
|
||||||
|
Important: URLs must include trailing slash for WordPress rewrite rules to work properly.
|
||||||
|
|
||||||
|
### Rewrite Rules
|
||||||
|
Custom rewrite rules handle direct profile access:
|
||||||
|
```php
|
||||||
|
add_rewrite_rule(
|
||||||
|
'^find-a-trainer/profile/([0-9]+)/?$',
|
||||||
|
'index.php?pagename=find-a-trainer&trainer_profile_id=$matches[1]',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### AJAX Endpoints
|
||||||
|
Profile sharing uses these AJAX actions:
|
||||||
|
- `hvac_get_profile_share_data` - Retrieves profile data and QR code URL
|
||||||
|
- Requires nonce verification: `hvac_profile_sharing`
|
||||||
|
|
||||||
|
### CSS Classes for Styling
|
||||||
|
```css
|
||||||
|
.hvac-share-modal # Modal overlay
|
||||||
|
.hvac-share-profile-card # Profile card container
|
||||||
|
.hvac-share-avatar # Profile image container
|
||||||
|
.hvac-share-qr # QR code container
|
||||||
|
.hvac-mq-badge-overlay # Certification badge overlay
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript Localization
|
||||||
|
Profile sharing requires these localized variables:
|
||||||
|
```javascript
|
||||||
|
hvac_sharing.ajax_url # WordPress AJAX URL
|
||||||
|
hvac_sharing.nonce # Security nonce
|
||||||
|
hvac_sharing.strings.copied # "Copied!" message
|
||||||
|
hvac_sharing.strings.copy_error # Copy error message
|
||||||
|
```
|
||||||
|
|
@ -685,6 +685,371 @@ ssh user@staging-server "cd /path/to/plugin && php test-csv-import.php"
|
||||||
5. **Log all import activities** for debugging
|
5. **Log all import activities** for debugging
|
||||||
6. **Test with various CSV formats and edge cases**
|
6. **Test with various CSV formats and edge cases**
|
||||||
|
|
||||||
|
## Trainer Profile Sharing Implementation
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
The trainer profile sharing system allows trainers to generate shareable URLs and QR codes for their profiles, enabling professional marketing and networking opportunities.
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
#### 1. HVAC_QR_Generator Class
|
||||||
|
|
||||||
|
The main class handling QR code generation and profile sharing functionality:
|
||||||
|
|
||||||
|
```php
|
||||||
|
class HVAC_QR_Generator {
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
public static function instance() {
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __construct() {
|
||||||
|
$this->init_hooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function init_hooks() {
|
||||||
|
// AJAX handlers
|
||||||
|
add_action('wp_ajax_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']);
|
||||||
|
add_action('wp_ajax_nopriv_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']);
|
||||||
|
|
||||||
|
// URL rewrite rules
|
||||||
|
add_action('init', [$this, 'add_profile_rewrite_rules']);
|
||||||
|
add_filter('query_vars', [$this, 'add_profile_query_vars']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. QR Code Generation
|
||||||
|
|
||||||
|
The system uses QR Server API for generating QR codes (switched from deprecated Google Charts API):
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function generate_qr_url($data, $size = 200, $error_correction = 'M') {
|
||||||
|
$encoded_data = urlencode($data);
|
||||||
|
|
||||||
|
$qr_url = sprintf(
|
||||||
|
'https://api.qrserver.com/v1/create-qr-code/?size=%dx%d&data=%s&ecc=%s',
|
||||||
|
$size,
|
||||||
|
$size,
|
||||||
|
$encoded_data,
|
||||||
|
strtoupper($error_correction)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $qr_url;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Implementation Notes:**
|
||||||
|
- **API Migration**: Switched from Google Charts API (deprecated) to QR Server API
|
||||||
|
- **URL Encoding**: Proper URL encoding prevents malformed QR codes
|
||||||
|
- **Error Correction**: Medium level (M) provides good balance of data capacity and error recovery
|
||||||
|
- **Size Flexibility**: Configurable size for different use cases
|
||||||
|
|
||||||
|
#### 3. Share URL Structure
|
||||||
|
|
||||||
|
Profile sharing URLs follow a hierarchical pattern with proper WordPress rewrite rules:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function add_profile_rewrite_rules() {
|
||||||
|
add_rewrite_rule(
|
||||||
|
'^find-a-trainer/profile/([0-9]+)/?$',
|
||||||
|
'index.php?pagename=find-a-trainer&trainer_profile_id=$matches[1]',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_trainer_profile_share_url($profile_id) {
|
||||||
|
$find_trainer_page = get_page_by_path('find-a-trainer');
|
||||||
|
if (!$find_trainer_page) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$base_url = get_permalink($find_trainer_page->ID);
|
||||||
|
|
||||||
|
// CRITICAL: Include trailing slash for WordPress rewrite rules
|
||||||
|
$profile_url = trailingslashit($base_url) . 'profile/' . $profile_id . '/';
|
||||||
|
|
||||||
|
return $profile_url;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Implementation Details:**
|
||||||
|
- **Trailing Slash Required**: WordPress rewrite rules require trailing slash
|
||||||
|
- **Query Variables**: Custom query var `trainer_profile_id` for profile identification
|
||||||
|
- **URL Pattern**: `/find-a-trainer/profile/{profile_id}/` structure
|
||||||
|
|
||||||
|
#### 4. AJAX Handler Implementation
|
||||||
|
|
||||||
|
The AJAX handler follows security best practices with nonce verification:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function ajax_get_profile_share_data() {
|
||||||
|
// Security: Verify nonce
|
||||||
|
if (!wp_verify_nonce($_POST['nonce'], 'hvac_profile_sharing')) {
|
||||||
|
wp_send_json_error(['message' => 'Security check failed']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile_id = intval($_POST['profile_id']);
|
||||||
|
if (!$profile_id) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid profile ID']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get comprehensive share data
|
||||||
|
$share_data = $this->get_trainer_share_data($profile_id);
|
||||||
|
|
||||||
|
if (!$share_data) {
|
||||||
|
wp_send_json_error(['message' => 'Profile not found or not accessible']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success($share_data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Frontend JavaScript Implementation
|
||||||
|
|
||||||
|
The JavaScript component handles the share modal and user interactions:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var ProfileSharing = {
|
||||||
|
init: function() {
|
||||||
|
this.bindEvents();
|
||||||
|
},
|
||||||
|
|
||||||
|
bindEvents: function() {
|
||||||
|
$(document).on('click', '.hvac-share-profile-btn', this.openShareModal);
|
||||||
|
$(document).on('click', '.hvac-copy-url-btn', this.copyShareUrl);
|
||||||
|
$(document).on('click', '.hvac-modal-close', this.closeShareModal);
|
||||||
|
|
||||||
|
// Escape key support
|
||||||
|
$(document).on('keydown', function(e) {
|
||||||
|
if (e.keyCode === 27 && $('.hvac-share-modal:visible').length) {
|
||||||
|
ProfileSharing.closeShareModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
loadShareData: function(profileId) {
|
||||||
|
$.ajax({
|
||||||
|
url: hvac_sharing.ajax_url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'hvac_get_profile_share_data',
|
||||||
|
profile_id: profileId,
|
||||||
|
nonce: hvac_sharing.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success && response.data) {
|
||||||
|
ProfileSharing.populateShareData(response.data);
|
||||||
|
} else {
|
||||||
|
ProfileSharing.showError(response.data ? response.data.message : 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
ProfileSharing.showError('Network error occurred. Please try again.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Guidelines for Profile Sharing
|
||||||
|
|
||||||
|
#### 1. Security Considerations
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Always verify nonces in AJAX handlers
|
||||||
|
if (!wp_verify_nonce($_POST['nonce'], 'hvac_profile_sharing')) {
|
||||||
|
wp_send_json_error(['message' => 'Security check failed']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize and validate input
|
||||||
|
$profile_id = intval($_POST['profile_id']);
|
||||||
|
if (!$profile_id || $profile_id <= 0) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid profile ID']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check profile accessibility
|
||||||
|
$profile = get_post($profile_id);
|
||||||
|
if (!$profile || $profile->post_type !== 'trainer_profile') {
|
||||||
|
wp_send_json_error(['message' => 'Profile not found']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Error Handling Patterns
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Graceful degradation for missing data
|
||||||
|
$trainer_name = get_post_meta($profile_id, 'trainer_display_name', true) ?: $user->display_name ?: 'Unknown Trainer';
|
||||||
|
|
||||||
|
// API fallback handling
|
||||||
|
$qr_url = $this->generate_qr_url($profile_url, $size);
|
||||||
|
if (!$qr_url) {
|
||||||
|
error_log("QR code generation failed for profile {$profile_id}");
|
||||||
|
// Continue without QR code rather than failing completely
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Frontend User Experience
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Loading states
|
||||||
|
$cardContainer.html(
|
||||||
|
'<div class="hvac-share-card-loading">' +
|
||||||
|
'<span class="dashicons dashicons-update spin"></span>' +
|
||||||
|
'<p>Loading profile card...</p>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Error states with helpful messaging
|
||||||
|
ProfileSharing.showError = function(message) {
|
||||||
|
$cardContainer.html(
|
||||||
|
'<div class="hvac-share-card-loading">' +
|
||||||
|
'<span class="dashicons dashicons-warning" style="color: #dc3545;"></span>' +
|
||||||
|
'<p style="color: #dc3545;">' + message + '</p>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Success feedback for copy operations
|
||||||
|
ProfileSharing.showCopySuccess = function($button) {
|
||||||
|
var originalText = $button.text();
|
||||||
|
$button.addClass('copied').text('Copied!');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
$button.removeClass('copied').text(originalText);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Template Integration
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In page templates, add the share button
|
||||||
|
<button type="button" class="hvac-button hvac-button-secondary hvac-share-profile-btn"
|
||||||
|
data-profile-id="<?php echo esc_attr($profile->ID); ?>">
|
||||||
|
<span class="dashicons dashicons-share"></span> Share Profile
|
||||||
|
</button>
|
||||||
|
|
||||||
|
// Include the modal HTML at the end of the template
|
||||||
|
<div id="hvac-share-profile-modal" class="hvac-share-modal" style="display: none;">
|
||||||
|
<!-- Modal content -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Asset Management
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In HVAC_Scripts_Styles class
|
||||||
|
public function localize_sharing_data($additional_data = []) {
|
||||||
|
wp_localize_script('hvac-profile-sharing', 'hvac_sharing', array_merge([
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('hvac_profile_sharing'),
|
||||||
|
'strings' => [
|
||||||
|
'copied' => 'Copied!',
|
||||||
|
'copy_error' => 'Copy failed'
|
||||||
|
]
|
||||||
|
], $additional_data));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Profile Sharing
|
||||||
|
|
||||||
|
#### 1. Unit Testing
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Test QR URL generation
|
||||||
|
public function test_qr_url_generation() {
|
||||||
|
$qr_generator = HVAC_QR_Generator::instance();
|
||||||
|
$test_url = 'https://example.com/find-a-trainer/profile/123/';
|
||||||
|
|
||||||
|
$qr_url = $qr_generator->generate_qr_url($test_url, 200, 'M');
|
||||||
|
|
||||||
|
$this->assertStringContains('api.qrserver.com', $qr_url);
|
||||||
|
$this->assertStringContains('size=200x200', $qr_url);
|
||||||
|
$this->assertStringContains(urlencode($test_url), $qr_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test share URL generation
|
||||||
|
public function test_share_url_generation() {
|
||||||
|
$qr_generator = HVAC_QR_Generator::instance();
|
||||||
|
$profile_id = 123;
|
||||||
|
|
||||||
|
$share_url = $qr_generator->get_trainer_profile_share_url($profile_id);
|
||||||
|
|
||||||
|
$this->assertStringEndsWith('/', $share_url); // Must have trailing slash
|
||||||
|
$this->assertStringContains('/profile/' . $profile_id . '/', $share_url);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. End-to-End Testing
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Playwright test for profile sharing
|
||||||
|
test('trainer can share profile with QR code', async ({ page }) => {
|
||||||
|
await page.goto('/trainer/profile/');
|
||||||
|
await page.click('.hvac-share-profile-btn');
|
||||||
|
|
||||||
|
// Wait for modal to appear
|
||||||
|
await page.waitForSelector('#hvac-share-profile-modal');
|
||||||
|
|
||||||
|
// Verify share URL is populated
|
||||||
|
const shareUrl = await page.inputValue('#hvac-share-url');
|
||||||
|
expect(shareUrl).toContain('/find-a-trainer/profile/');
|
||||||
|
|
||||||
|
// Verify QR code image is loaded
|
||||||
|
const qrImage = page.locator('.hvac-share-qr img');
|
||||||
|
await expect(qrImage).toBeVisible();
|
||||||
|
|
||||||
|
// Test copy functionality
|
||||||
|
await page.click('.hvac-copy-url-btn');
|
||||||
|
await expect(page.locator('.hvac-copy-url-btn')).toHaveClass(/copied/);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployment Considerations
|
||||||
|
|
||||||
|
#### 1. URL Rewrite Rules
|
||||||
|
|
||||||
|
After deploying profile sharing functionality:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Flush rewrite rules to activate new URL patterns
|
||||||
|
wp rewrite flush
|
||||||
|
|
||||||
|
# Verify rewrite rules are working
|
||||||
|
wp rewrite list | grep "find-a-trainer"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. QR Code API Dependencies
|
||||||
|
|
||||||
|
- **External Dependency**: QR Server API (https://api.qrserver.com)
|
||||||
|
- **Fallback**: Consider implementing fallback QR generation if API becomes unavailable
|
||||||
|
- **Caching**: QR URLs are generated dynamically but could be cached for performance
|
||||||
|
|
||||||
|
#### 3. Performance Monitoring
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Add timing monitoring for QR generation
|
||||||
|
$start_time = microtime(true);
|
||||||
|
$qr_url = $this->generate_qr_url($data, $size);
|
||||||
|
$generation_time = microtime(true) - $start_time;
|
||||||
|
|
||||||
|
if ($generation_time > 2.0) {
|
||||||
|
error_log("Slow QR generation: {$generation_time}s for profile {$profile_id}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/)
|
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The HVAC Community Events plugin is a comprehensive event management system designed specifically for HVAC trainers. It integrates seamlessly with WordPress and The Events Calendar to provide trainer profiles, certificate generation, venue management, certification tracking, advanced reporting capabilities, and comprehensive CSV import functionality with taxonomy integration.
|
The HVAC Community Events plugin is a comprehensive event management system designed specifically for HVAC trainers. It integrates seamlessly with WordPress and The Events Calendar to provide trainer profiles, certificate generation, venue management, certification tracking, advanced reporting capabilities, comprehensive CSV import functionality with taxonomy integration, and professional trainer profile sharing with QR code generation.
|
||||||
|
|
||||||
## Documentation Structure
|
## Documentation Structure
|
||||||
|
|
||||||
|
|
|
||||||
1039
docs/TRAINER-API-REFERENCE.md
Normal file
1039
docs/TRAINER-API-REFERENCE.md
Normal file
File diff suppressed because it is too large
Load diff
220
docs/TRAINER-PROFILE-SHARING-GUIDE.md
Normal file
220
docs/TRAINER-PROFILE-SHARING-GUIDE.md
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
# Trainer Profile Sharing User Guide
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [How to Share Your Profile](#how-to-share-your-profile)
|
||||||
|
- [Sharing Options](#sharing-options)
|
||||||
|
- [Profile Card Features](#profile-card-features)
|
||||||
|
- [QR Code Usage](#qr-code-usage)
|
||||||
|
- [Customizing Your Profile](#customizing-your-profile)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The trainer profile sharing feature allows HVAC trainers to easily share their professional profiles with potential clients, colleagues, and on social media. Each trainer gets a unique shareable URL and QR code that leads directly to their profile information.
|
||||||
|
|
||||||
|
### Key Benefits
|
||||||
|
- **Professional Presentation**: Clean, branded profile cards with your photo and credentials
|
||||||
|
- **Easy Sharing**: One-click URL copying and QR code generation
|
||||||
|
- **Instant Access**: QR codes provide immediate mobile access to your profile
|
||||||
|
- **Marketing Tool**: Perfect for business cards, email signatures, and social media
|
||||||
|
|
||||||
|
## How to Share Your Profile
|
||||||
|
|
||||||
|
### Step 1: Access Your Profile
|
||||||
|
1. Log into your trainer dashboard
|
||||||
|
2. Navigate to **Customize** → **Personal Profile**
|
||||||
|
3. Click the **"Share Profile"** button (blue button with share icon)
|
||||||
|
|
||||||
|
### Step 2: Use the Share Modal
|
||||||
|
The share modal provides two sharing options:
|
||||||
|
|
||||||
|
#### Option 1: Copy URL
|
||||||
|
1. The modal displays your personal profile URL
|
||||||
|
2. Click the **"Copy"** button to copy the URL to your clipboard
|
||||||
|
3. Paste the URL anywhere you'd like to share it
|
||||||
|
|
||||||
|
#### Option 2: Screenshot the Profile Card
|
||||||
|
1. The modal shows a professional profile card
|
||||||
|
2. Take a screenshot of the card
|
||||||
|
3. Use the image on social media, in presentations, or marketing materials
|
||||||
|
|
||||||
|
### Step 3: Close and Share
|
||||||
|
- Click the X or press Escape to close the modal
|
||||||
|
- Share your copied URL or profile card image as needed
|
||||||
|
|
||||||
|
## Sharing Options
|
||||||
|
|
||||||
|
### Direct URL Sharing
|
||||||
|
Your personal profile URL follows this format:
|
||||||
|
```
|
||||||
|
https://upskillhvac.com/find-a-trainer/profile/YOUR-ID/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Best for:**
|
||||||
|
- Email signatures
|
||||||
|
- Website links
|
||||||
|
- LinkedIn profiles
|
||||||
|
- Direct messaging
|
||||||
|
|
||||||
|
### QR Code Sharing
|
||||||
|
Each profile includes a QR code that mobile users can scan to instantly access your profile.
|
||||||
|
|
||||||
|
**Best for:**
|
||||||
|
- Business cards
|
||||||
|
- Printed materials
|
||||||
|
- Trade show displays
|
||||||
|
- Quick mobile sharing
|
||||||
|
|
||||||
|
### Profile Card Screenshots
|
||||||
|
The generated profile card is professionally designed and includes all your key information.
|
||||||
|
|
||||||
|
**Best for:**
|
||||||
|
- Social media posts (LinkedIn, Facebook, Instagram)
|
||||||
|
- Marketing presentations
|
||||||
|
- Printed promotional materials
|
||||||
|
- Email marketing
|
||||||
|
|
||||||
|
## Profile Card Features
|
||||||
|
|
||||||
|
### Visual Elements
|
||||||
|
- **Professional Photo**: Your uploaded trainer photo or professional initial placeholder
|
||||||
|
- **Certification Badge**: measureQuick certification badge overlay (if applicable)
|
||||||
|
- **Clean Layout**: 600x300px professional design with rounded corners
|
||||||
|
|
||||||
|
### Information Displayed
|
||||||
|
- **Your Name**: Display name from your profile
|
||||||
|
- **Business Name**: Your training organization or company
|
||||||
|
- **Location**: City and state information
|
||||||
|
- **Certification Status**: Your current certification level
|
||||||
|
- **QR Code**: Instant mobile access code
|
||||||
|
|
||||||
|
### Certification Badges
|
||||||
|
If you're a **Certified measureQuick Trainer**, your profile card will automatically display the measureQuick certification badge in the top-right corner of your photo.
|
||||||
|
|
||||||
|
## QR Code Usage
|
||||||
|
|
||||||
|
### How QR Codes Work
|
||||||
|
- Mobile users can scan the QR code with their phone camera
|
||||||
|
- The code instantly opens your profile page in their browser
|
||||||
|
- No app installation required - works with standard camera apps
|
||||||
|
|
||||||
|
### QR Code Best Practices
|
||||||
|
1. **Size**: Ensure QR codes are at least 1 inch square when printed
|
||||||
|
2. **Contrast**: Use high contrast backgrounds for better scanning
|
||||||
|
3. **Testing**: Always test QR codes before printing materials
|
||||||
|
4. **Placement**: Position codes where they're easily accessible to mobile users
|
||||||
|
|
||||||
|
### Common Uses
|
||||||
|
- **Business Cards**: Add to back of cards for quick profile access
|
||||||
|
- **Trade Shows**: Include on booth displays and handouts
|
||||||
|
- **Presentations**: Add to final slides for audience follow-up
|
||||||
|
- **Vehicle Graphics**: Include on work vehicle decals
|
||||||
|
|
||||||
|
## Customizing Your Profile
|
||||||
|
|
||||||
|
To make your shared profile more effective, ensure your profile is complete:
|
||||||
|
|
||||||
|
### Essential Information
|
||||||
|
- **Professional Photo**: Upload a clear, professional headshot
|
||||||
|
- **Complete Name**: Use your professional display name
|
||||||
|
- **Business Information**: Include your company/organization name
|
||||||
|
- **Location Details**: Ensure city and state are accurate
|
||||||
|
- **Certification Status**: Keep certification information up to date
|
||||||
|
|
||||||
|
### Profile Enhancement Tips
|
||||||
|
1. **Professional Photo**: Use a high-quality headshot with good lighting
|
||||||
|
2. **Complete Bio**: Fill out your "About" section with relevant experience
|
||||||
|
3. **Contact Information**: Ensure your email and phone are current
|
||||||
|
4. **Training Details**: Include your specializations and training formats
|
||||||
|
5. **Location Accuracy**: Verify your location for proper geographic targeting
|
||||||
|
|
||||||
|
### Updating Your Profile
|
||||||
|
1. Navigate to **Customize** → **Personal Profile**
|
||||||
|
2. Click **"Edit Profile"** to make changes
|
||||||
|
3. Save your updates
|
||||||
|
4. Your shared profile will automatically reflect changes
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### QR Code Not Working
|
||||||
|
**Symptoms**: QR code displays as text or broken image
|
||||||
|
**Solutions**:
|
||||||
|
- Refresh the page and try sharing again
|
||||||
|
- Check your internet connection
|
||||||
|
- Ensure your profile information is complete
|
||||||
|
- Contact support if the issue persists
|
||||||
|
|
||||||
|
### Share URL Not Copying
|
||||||
|
**Symptoms**: Copy button doesn't work or shows error
|
||||||
|
**Solutions**:
|
||||||
|
- Try using Ctrl+C (PC) or Cmd+C (Mac) to manually copy
|
||||||
|
- Check browser permissions for clipboard access
|
||||||
|
- Try a different browser
|
||||||
|
- Manually select the URL text and copy
|
||||||
|
|
||||||
|
### Profile Card Missing Information
|
||||||
|
**Symptoms**: Profile card shows incomplete or blank information
|
||||||
|
**Solutions**:
|
||||||
|
- Complete your profile information in the Edit Profile section
|
||||||
|
- Ensure required fields (name, location) are filled
|
||||||
|
- Upload a professional photo
|
||||||
|
- Contact support if information isn't displaying correctly
|
||||||
|
|
||||||
|
### Mobile Scanning Issues
|
||||||
|
**Symptoms**: QR code won't scan with mobile devices
|
||||||
|
**Solutions**:
|
||||||
|
- Ensure adequate lighting when scanning
|
||||||
|
- Hold phone 6-12 inches from QR code
|
||||||
|
- Try different camera apps or QR scanner apps
|
||||||
|
- Verify QR code quality and size
|
||||||
|
|
||||||
|
### Share Modal Won't Open
|
||||||
|
**Symptoms**: Clicking Share Profile button doesn't open modal
|
||||||
|
**Solutions**:
|
||||||
|
- Refresh the page and try again
|
||||||
|
- Disable browser ad blockers temporarily
|
||||||
|
- Clear browser cache and cookies
|
||||||
|
- Try a different browser
|
||||||
|
- Contact support if the issue continues
|
||||||
|
|
||||||
|
### Common Error Messages
|
||||||
|
- **"Profile not found"**: Your trainer profile may not be properly set up
|
||||||
|
- **"Loading..."**: Check your internet connection and wait for data to load
|
||||||
|
- **"Error loading URL"**: Try refreshing the page or contact support
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
If you encounter issues not covered here:
|
||||||
|
1. Check the main [Troubleshooting Guide](./TROUBLESHOOTING.md)
|
||||||
|
2. Contact your system administrator
|
||||||
|
3. Submit a support ticket with details about the problem
|
||||||
|
|
||||||
|
## Best Practices for Profile Sharing
|
||||||
|
|
||||||
|
### Professional Presentation
|
||||||
|
- Keep your profile information current and accurate
|
||||||
|
- Use professional language in your bio and descriptions
|
||||||
|
- Maintain consistent branding across all shared materials
|
||||||
|
|
||||||
|
### Strategic Sharing
|
||||||
|
- Include your profile URL in all professional communications
|
||||||
|
- Use QR codes on printed materials for easy mobile access
|
||||||
|
- Share profile cards on social media to increase visibility
|
||||||
|
- Add your profile link to email signatures
|
||||||
|
|
||||||
|
### Tracking Success
|
||||||
|
While the system doesn't provide analytics, you can track sharing success by:
|
||||||
|
- Monitoring increased contact requests
|
||||||
|
- Asking new clients how they found you
|
||||||
|
- Including tracking parameters in shared URLs (if needed)
|
||||||
|
- Following up on leads generated from shared profiles
|
||||||
|
|
||||||
|
### Marketing Integration
|
||||||
|
- Include profile sharing in your overall marketing strategy
|
||||||
|
- Use profile cards in presentations and proposals
|
||||||
|
- Add QR codes to vehicle graphics and signage
|
||||||
|
- Share profiles in industry forums and groups
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: August 2025*
|
||||||
1035
docs/TRAINER-SYSTEM-DOCUMENTATION.md
Normal file
1035
docs/TRAINER-SYSTEM-DOCUMENTATION.md
Normal file
File diff suppressed because it is too large
Load diff
1313
docs/TRAINER-TROUBLESHOOTING.md
Normal file
1313
docs/TRAINER-TROUBLESHOOTING.md
Normal file
File diff suppressed because it is too large
Load diff
332
docs/WELCOME-POPUP-SYSTEM.md
Normal file
332
docs/WELCOME-POPUP-SYSTEM.md
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
# HVAC Welcome Popup System Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Welcome Popup System provides a guided onboarding experience for new HVAC trainers, introducing them to platform features through an interactive carousel modal. The system respects user account status and provides accessibility-compliant navigation.
|
||||||
|
|
||||||
|
**Version**: 1.0.6
|
||||||
|
**Status**: ✅ Complete
|
||||||
|
**Deployed**: 2025-08-05
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 🎯 Core Functionality
|
||||||
|
|
||||||
|
- **Interactive Carousel**: 4-card slideshow introducing platform features
|
||||||
|
- **Account Status Filtering**: Only shows to approved users
|
||||||
|
- **Accessibility Compliant**: WCAG 2.1 AA standards
|
||||||
|
- **Responsive Design**: Works on desktop, tablet, and mobile
|
||||||
|
- **Dismissal Management**: "Don't show this again" functionality
|
||||||
|
- **WordPress Integration**: Proper theme colors and button styling
|
||||||
|
|
||||||
|
### 📱 User Experience
|
||||||
|
|
||||||
|
#### Content Structure
|
||||||
|
1. **Welcome Card**: Platform introduction with 4 key benefits
|
||||||
|
2. **Events Card**: Event management features (3 key points)
|
||||||
|
3. **Profile Card**: Trainer profile and visibility features (4 key points)
|
||||||
|
4. **Features Card**: Reporting and analytics capabilities (3 key points)
|
||||||
|
|
||||||
|
#### Navigation
|
||||||
|
- **Arrow Buttons**: Previous/Next navigation
|
||||||
|
- **Dot Indicators**: Direct card access
|
||||||
|
- **Keyboard Support**: Tab navigation and ESC to close
|
||||||
|
- **Touch Support**: Mobile-friendly controls
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### 🏗️ Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
includes/class-hvac-welcome-popup.php # Main popup logic
|
||||||
|
assets/css/hvac-welcome-popup.css # Styling and layout
|
||||||
|
assets/js/hvac-welcome-popup.js # JavaScript functionality
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎨 Styling System
|
||||||
|
|
||||||
|
#### Layout Structure
|
||||||
|
```css
|
||||||
|
.hvac-welcome-popup # Full-screen overlay
|
||||||
|
.hvac-welcome-modal # Main modal container
|
||||||
|
.hvac-welcome-content # Content wrapper
|
||||||
|
.hvac-welcome-carousel # Carousel container
|
||||||
|
.hvac-welcome-card # Individual cards
|
||||||
|
.hvac-welcome-navigation # Navigation controls
|
||||||
|
.hvac-welcome-footer # Action buttons
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Key CSS Specifications
|
||||||
|
- **Modal**: `max-width: 800px`, `max-height: 90vh`
|
||||||
|
- **Carousel**: `min-height: 460px` (desktop), responsive scaling
|
||||||
|
- **Navigation**: Proper spacing with `margin: 20px 0 50px`
|
||||||
|
- **Footer**: `z-index: 10` with white background
|
||||||
|
- **Colors**: Astra theme integration with CSS custom properties
|
||||||
|
|
||||||
|
### 🔒 Access Control
|
||||||
|
|
||||||
|
#### Account Status Logic
|
||||||
|
```php
|
||||||
|
private function user_can_see_popup() {
|
||||||
|
// Admin users: Always show (for testing)
|
||||||
|
if (current_user_can('manage_options')) return true;
|
||||||
|
|
||||||
|
// Get account status via HVAC_Trainer_Status
|
||||||
|
$account_status = HVAC_Trainer_Status::get_trainer_status($user_id);
|
||||||
|
|
||||||
|
// Allow: approved, active, inactive users
|
||||||
|
// Block: pending, disabled users
|
||||||
|
$allowed_statuses = ['approved', 'active', 'inactive'];
|
||||||
|
return in_array($account_status, $allowed_statuses);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Status Behavior Matrix
|
||||||
|
| Account Status | Popup Shown | Reason |
|
||||||
|
|---------------|-------------|---------|
|
||||||
|
| `pending` | ❌ No | Awaiting approval |
|
||||||
|
| `approved` | ✅ Yes | Account approved |
|
||||||
|
| `active` | ✅ Yes | Recently active |
|
||||||
|
| `inactive` | ✅ Yes | Approved but needs re-engagement |
|
||||||
|
| `disabled` | ❌ No | Account suspended |
|
||||||
|
| Admin | ✅ Yes | Testing/management |
|
||||||
|
|
||||||
|
### 📝 Content Management
|
||||||
|
|
||||||
|
#### Card Content
|
||||||
|
Each card contains:
|
||||||
|
- **Icon**: Dashicons integration
|
||||||
|
- **Title**: Main heading (32px, theme colors)
|
||||||
|
- **Subtitle**: Descriptive text
|
||||||
|
- **Bullet Points**: 3-4 key features with checkmarks
|
||||||
|
- **Note Section**: Additional context (card 1 only)
|
||||||
|
|
||||||
|
#### Dismissal System
|
||||||
|
- **User Meta**: `hvac_welcome_popup_dismissed`
|
||||||
|
- **AJAX Endpoints**:
|
||||||
|
- `hvac_check_welcome_dismissed`
|
||||||
|
- `hvac_dismiss_welcome_popup`
|
||||||
|
- **Nonce Security**: `hvac_welcome_nonce`
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### 🔌 AJAX Endpoints
|
||||||
|
|
||||||
|
#### Check Dismissal Status
|
||||||
|
```javascript
|
||||||
|
jQuery.post(ajaxurl, {
|
||||||
|
action: 'hvac_check_welcome_dismissed',
|
||||||
|
nonce: hvac_welcome.nonce
|
||||||
|
}, function(response) {
|
||||||
|
// response.data.dismissed (boolean)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Dismiss Popup
|
||||||
|
```javascript
|
||||||
|
jQuery.post(ajaxurl, {
|
||||||
|
action: 'hvac_dismiss_welcome_popup',
|
||||||
|
nonce: hvac_welcome.nonce,
|
||||||
|
dont_show_again: true
|
||||||
|
}, function(response) {
|
||||||
|
// response.success (boolean)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎛️ JavaScript API
|
||||||
|
|
||||||
|
#### Global Object
|
||||||
|
```javascript
|
||||||
|
window.HVACWelcomePopup = {
|
||||||
|
showPopup(),
|
||||||
|
hidePopup(),
|
||||||
|
checkAndShowPopup(),
|
||||||
|
nextCard(),
|
||||||
|
prevCard(),
|
||||||
|
goToCard(index)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Events
|
||||||
|
- `hvac:welcome:shown` - Popup displayed
|
||||||
|
- `hvac:welcome:dismissed` - Popup closed
|
||||||
|
- `hvac:welcome:card-changed` - Card navigation
|
||||||
|
|
||||||
|
## Accessibility Features
|
||||||
|
|
||||||
|
### ♿ WCAG 2.1 AA Compliance
|
||||||
|
|
||||||
|
#### Focus Management
|
||||||
|
- **Proper Tab Order**: Close → Navigation → Footer elements
|
||||||
|
- **Focus Indicators**: 2px blue outline on all interactive elements
|
||||||
|
- **Keyboard Navigation**: Arrow keys, Tab, Enter, ESC support
|
||||||
|
|
||||||
|
#### Touch Targets
|
||||||
|
- **Minimum Size**: 44px × 44px on mobile devices
|
||||||
|
- **Proper Spacing**: No overlapping clickable areas
|
||||||
|
- **Clear Boundaries**: Visual separation between elements
|
||||||
|
|
||||||
|
#### Screen Reader Support
|
||||||
|
- **ARIA Labels**: All navigation buttons properly labeled
|
||||||
|
- **Semantic HTML**: Proper heading hierarchy
|
||||||
|
- **Alternative Text**: Icons have descriptive labels
|
||||||
|
|
||||||
|
#### Motion Preferences
|
||||||
|
```css
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.hvac-welcome-modal,
|
||||||
|
.hvac-welcome-card,
|
||||||
|
.hvac-welcome-nav {
|
||||||
|
animation: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development History
|
||||||
|
|
||||||
|
### Version Timeline
|
||||||
|
|
||||||
|
#### v1.0.6 (2025-08-05) - ✅ **FINAL**
|
||||||
|
- **Fixed**: Navigation overlap with content
|
||||||
|
- **Improved**: Carousel height calculations
|
||||||
|
- **Enhanced**: Mobile responsive spacing
|
||||||
|
- **Status**: Production ready
|
||||||
|
|
||||||
|
#### v1.0.5 (2025-08-05)
|
||||||
|
- **Added**: Inactive users to allowed status list
|
||||||
|
- **Fixed**: Account status filtering logic
|
||||||
|
|
||||||
|
#### v1.0.4 (2025-08-05)
|
||||||
|
- **Changed**: From certification_status to account_status
|
||||||
|
- **Improved**: Status checking with HVAC_Trainer_Status class
|
||||||
|
|
||||||
|
#### v1.0.3 (2025-08-05)
|
||||||
|
- **Added**: Certification status filtering
|
||||||
|
- **Blocked**: Pending/disabled users from seeing popup
|
||||||
|
|
||||||
|
#### v1.0.2 (2025-08-05)
|
||||||
|
- **Fixed**: Accessibility overlapping elements
|
||||||
|
- **Added**: Proper z-index layering
|
||||||
|
- **Improved**: WCAG compliance
|
||||||
|
|
||||||
|
#### v1.0.1 (2025-08-05)
|
||||||
|
- **Reduced**: Bullet points from 5-6 to 3-4 per card
|
||||||
|
- **Added**: Astra theme integration
|
||||||
|
- **Fixed**: Overflow scrolling behavior
|
||||||
|
|
||||||
|
#### v1.0.0 (2025-08-05)
|
||||||
|
- **Created**: Initial welcome popup system
|
||||||
|
- **Implemented**: 4-card carousel design
|
||||||
|
- **Added**: Basic dismiss functionality
|
||||||
|
|
||||||
|
## Testing & Quality Assurance
|
||||||
|
|
||||||
|
### 🧪 Test Coverage
|
||||||
|
|
||||||
|
#### Automated Tests
|
||||||
|
- **File**: `tests/e2e/welcome-popup-visual-verification.test.ts`
|
||||||
|
- **Coverage**: Element positioning, interaction, responsiveness
|
||||||
|
- **Screenshots**: Automated visual verification
|
||||||
|
|
||||||
|
#### Manual Testing Checklist
|
||||||
|
- [ ] Popup appears for approved users
|
||||||
|
- [ ] Popup blocked for pending/disabled users
|
||||||
|
- [ ] Navigation works on all devices
|
||||||
|
- [ ] Footer elements fully clickable
|
||||||
|
- [ ] Hard refresh shows updated styles
|
||||||
|
- [ ] Dismiss functionality works
|
||||||
|
- [ ] Account status filtering accurate
|
||||||
|
|
||||||
|
#### Browser Compatibility
|
||||||
|
- ✅ Chrome 90+
|
||||||
|
- ✅ Firefox 85+
|
||||||
|
- ✅ Safari 14+
|
||||||
|
- ✅ Edge 90+
|
||||||
|
- ✅ Mobile browsers (iOS/Android)
|
||||||
|
|
||||||
|
### 🔧 Troubleshooting
|
||||||
|
|
||||||
|
#### Common Issues
|
||||||
|
|
||||||
|
**Popup not showing for approved users**
|
||||||
|
```bash
|
||||||
|
# Check user account status
|
||||||
|
wp eval "echo get_user_meta(USER_ID, 'account_status', true);"
|
||||||
|
|
||||||
|
# Reset dismissal
|
||||||
|
wp eval "delete_user_meta(USER_ID, 'hvac_welcome_popup_dismissed');"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Navigation still overlapping**
|
||||||
|
```bash
|
||||||
|
# Force cache clear and hard refresh
|
||||||
|
wp cache flush
|
||||||
|
# Browser: Ctrl+Shift+R
|
||||||
|
```
|
||||||
|
|
||||||
|
**Styling not loading**
|
||||||
|
```bash
|
||||||
|
# Check CSS file exists
|
||||||
|
ls -la wp-content/plugins/hvac-community-events/assets/css/hvac-welcome-popup.css
|
||||||
|
|
||||||
|
# Verify version in file header
|
||||||
|
head -10 wp-content/plugins/hvac-community-events/assets/css/hvac-welcome-popup.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### 🛠️ Customization Options
|
||||||
|
|
||||||
|
#### Content Modification
|
||||||
|
Edit content in: `assets/js/hvac-welcome-popup.js` lines 50-200
|
||||||
|
|
||||||
|
#### Styling Changes
|
||||||
|
Edit styles in: `assets/css/hvac-welcome-popup.css`
|
||||||
|
|
||||||
|
#### Account Status Rules
|
||||||
|
Modify logic in: `includes/class-hvac-welcome-popup.php:user_can_see_popup()`
|
||||||
|
|
||||||
|
### 🎯 Performance Optimization
|
||||||
|
|
||||||
|
- **Conditional Loading**: Only loads on trainer pages
|
||||||
|
- **Lazy Initialization**: JavaScript loads after DOM ready
|
||||||
|
- **Efficient AJAX**: Minimal server requests
|
||||||
|
- **Cache-Friendly**: Static assets with version numbers
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### 🔐 Security Features
|
||||||
|
|
||||||
|
- **Nonce Verification**: All AJAX requests protected
|
||||||
|
- **Capability Checks**: User permission validation
|
||||||
|
- **Input Sanitization**: All user input sanitized
|
||||||
|
- **XSS Prevention**: Proper output escaping
|
||||||
|
|
||||||
|
### 🛡️ Security Best Practices
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Nonce verification
|
||||||
|
wp_verify_nonce($_POST['nonce'], 'hvac_welcome_nonce');
|
||||||
|
|
||||||
|
// Capability check
|
||||||
|
if (!$this->user_can_see_popup()) {
|
||||||
|
wp_send_json_error('Access denied');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input sanitization
|
||||||
|
$dont_show_again = (bool) sanitize_text_field($_POST['dont_show_again']);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The HVAC Welcome Popup System provides a comprehensive, accessible, and user-friendly onboarding experience for HVAC trainers. The system properly integrates with WordPress standards, respects user account status, and maintains high accessibility standards.
|
||||||
|
|
||||||
|
**Key Achievements:**
|
||||||
|
- ✅ Fully accessible (WCAG 2.1 AA)
|
||||||
|
- ✅ Responsive design
|
||||||
|
- ✅ Account status aware
|
||||||
|
- ✅ WordPress standards compliant
|
||||||
|
- ✅ Production ready
|
||||||
|
|
||||||
|
**Deployment Status:** Live on staging (v1.0.6) - Ready for production deployment when requested.
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: HVAC Community Events
|
* Plugin Name: HVAC Community Events
|
||||||
* Plugin URI: https://upskillhvac.com
|
* Plugin URI: https://upskillhvac.com
|
||||||
* Description: Custom plugin for HVAC trainer event management system
|
* Description: Custom plugin for HVAC trainer event management system
|
||||||
* Version: 1.0.1
|
* Version: 1.0.6
|
||||||
* Author: Upskill HVAC
|
* Author: Upskill HVAC
|
||||||
* Author URI: https://upskillhvac.com
|
* Author URI: https://upskillhvac.com
|
||||||
* License: GPL-2.0+
|
* License: GPL-2.0+
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class HVAC_Astra_Integration {
|
||||||
* Force content layout for HVAC pages
|
* Force content layout for HVAC pages
|
||||||
*/
|
*/
|
||||||
public function force_hvac_content_layout($layout) {
|
public function force_hvac_content_layout($layout) {
|
||||||
if ($this->is_hvac_page()) {
|
if ($this->is_hvac_page() && !$this->is_find_trainer_page()) {
|
||||||
return 'plain-container';
|
return 'plain-container';
|
||||||
}
|
}
|
||||||
return $layout;
|
return $layout;
|
||||||
|
|
@ -108,7 +108,7 @@ class HVAC_Astra_Integration {
|
||||||
* Force site layout for HVAC pages
|
* Force site layout for HVAC pages
|
||||||
*/
|
*/
|
||||||
public function force_hvac_site_layout($layout) {
|
public function force_hvac_site_layout($layout) {
|
||||||
if ($this->is_hvac_page()) {
|
if ($this->is_hvac_page() && !$this->is_find_trainer_page()) {
|
||||||
return 'ast-full-width-layout';
|
return 'ast-full-width-layout';
|
||||||
}
|
}
|
||||||
return $layout;
|
return $layout;
|
||||||
|
|
@ -118,7 +118,7 @@ class HVAC_Astra_Integration {
|
||||||
* Modify container class for HVAC pages
|
* Modify container class for HVAC pages
|
||||||
*/
|
*/
|
||||||
public function modify_container_class($classes, $layout) {
|
public function modify_container_class($classes, $layout) {
|
||||||
if ($this->is_hvac_page()) {
|
if ($this->is_hvac_page() && !$this->is_find_trainer_page()) {
|
||||||
// Remove any constrained container classes
|
// Remove any constrained container classes
|
||||||
$classes = str_replace('ast-container', 'ast-full-width-container', $classes);
|
$classes = str_replace('ast-container', 'ast-full-width-container', $classes);
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ class HVAC_Astra_Integration {
|
||||||
* Get HVAC-specific container class
|
* Get HVAC-specific container class
|
||||||
*/
|
*/
|
||||||
public function get_hvac_container_class($class) {
|
public function get_hvac_container_class($class) {
|
||||||
if ($this->is_hvac_page()) {
|
if ($this->is_hvac_page() && !$this->is_find_trainer_page()) {
|
||||||
return 'ast-full-width-container';
|
return 'ast-full-width-container';
|
||||||
}
|
}
|
||||||
return $class;
|
return $class;
|
||||||
|
|
@ -159,8 +159,8 @@ class HVAC_Astra_Integration {
|
||||||
* Setup content width for HVAC pages
|
* Setup content width for HVAC pages
|
||||||
*/
|
*/
|
||||||
public function setup_hvac_content_width() {
|
public function setup_hvac_content_width() {
|
||||||
if ($this->is_hvac_page()) {
|
if ($this->is_hvac_page() && !$this->is_find_trainer_page()) {
|
||||||
// Set global content width
|
// Set global content width for dashboard pages only
|
||||||
global $content_width;
|
global $content_width;
|
||||||
$content_width = 1920; // Full HD width
|
$content_width = 1920; // Full HD width
|
||||||
|
|
||||||
|
|
@ -168,6 +168,14 @@ class HVAC_Astra_Integration {
|
||||||
add_filter('astra_get_content_width', function() {
|
add_filter('astra_get_content_width', function() {
|
||||||
return 1920;
|
return 1920;
|
||||||
}, 999);
|
}, 999);
|
||||||
|
} elseif ($this->is_find_trainer_page()) {
|
||||||
|
// Set standard content width for Find A Trainer page
|
||||||
|
global $content_width;
|
||||||
|
$content_width = 1200; // Standard boxed width
|
||||||
|
|
||||||
|
add_filter('astra_get_content_width', function() {
|
||||||
|
return 1200;
|
||||||
|
}, 999);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,6 +184,57 @@ class HVAC_Astra_Integration {
|
||||||
*/
|
*/
|
||||||
public function add_hvac_dynamic_css($css) {
|
public function add_hvac_dynamic_css($css) {
|
||||||
if ($this->is_hvac_page()) {
|
if ($this->is_hvac_page()) {
|
||||||
|
if ($this->is_find_trainer_page()) {
|
||||||
|
// Find A Trainer page - boxed layout with 1200px max-width
|
||||||
|
$hvac_css = '
|
||||||
|
/* Find A Trainer - Boxed layout */
|
||||||
|
.hvac-find-trainer-page .ast-container {
|
||||||
|
max-width: 1200px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding-left: 20px !important;
|
||||||
|
padding-right: 20px !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-find-trainer-page .site-content .ast-container {
|
||||||
|
max-width: 1200px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove sidebar for Find A Trainer */
|
||||||
|
.hvac-find-trainer-page .widget-area,
|
||||||
|
.hvac-find-trainer-page .ast-sidebar,
|
||||||
|
.hvac-find-trainer-page #secondary,
|
||||||
|
.hvac-find-trainer-page aside.widget-area,
|
||||||
|
.hvac-find-trainer-page .sidebar-main {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single column content */
|
||||||
|
.hvac-find-trainer-page #primary,
|
||||||
|
.hvac-find-trainer-page .site-main,
|
||||||
|
.hvac-find-trainer-page .content-area {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map container constraints */
|
||||||
|
.hvac-find-trainer-page .hvac-map-section {
|
||||||
|
overflow: hidden !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-find-trainer-page .hvac-map-section .map_wrapper,
|
||||||
|
.hvac-find-trainer-page .hvac-map-section .map_box,
|
||||||
|
.hvac-find-trainer-page .hvac-map-section .map_container {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
';
|
||||||
|
} else {
|
||||||
|
// Other HVAC pages - full-width layout
|
||||||
$hvac_css = '
|
$hvac_css = '
|
||||||
/* HVAC Full-width overrides for Astra */
|
/* HVAC Full-width overrides for Astra */
|
||||||
.hvac-astra-integrated .ast-container {
|
.hvac-astra-integrated .ast-container {
|
||||||
|
|
@ -236,6 +295,7 @@ class HVAC_Astra_Integration {
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
';
|
';
|
||||||
|
}
|
||||||
|
|
||||||
$css .= $hvac_css;
|
$css .= $hvac_css;
|
||||||
}
|
}
|
||||||
|
|
@ -321,6 +381,21 @@ class HVAC_Astra_Integration {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current page is Find A Trainer page
|
||||||
|
*/
|
||||||
|
private function is_find_trainer_page() {
|
||||||
|
if (is_page()) {
|
||||||
|
global $post;
|
||||||
|
if ($post && $post->post_name === 'find-a-trainer') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_url = $_SERVER['REQUEST_URI'];
|
||||||
|
return strpos($current_url, 'find-a-trainer') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure correct template is loaded
|
* Ensure correct template is loaded
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -245,91 +245,143 @@ class HVAC_Help_System {
|
||||||
return '
|
return '
|
||||||
<section id="getting-started" class="hvac-doc-section">
|
<section id="getting-started" class="hvac-doc-section">
|
||||||
<h2><i class="fas fa-play-circle"></i> Getting Started</h2>
|
<h2><i class="fas fa-play-circle"></i> Getting Started</h2>
|
||||||
|
<div class="hvac-doc-intro">
|
||||||
|
<p class="hvac-welcome-message">Welcome to Upskill HVAC! You\'re part of our comprehensive training platform designed to help HVAC trainers get discovered, share training events, build professional reputation, and connect with the HVAC community.</p>
|
||||||
|
</div>
|
||||||
<div class="hvac-doc-grid">
|
<div class="hvac-doc-grid">
|
||||||
<div class="hvac-doc-card">
|
<div class="hvac-doc-card">
|
||||||
<h3>1. Your Dashboard is Home Base</h3>
|
<h3>1. Your Dashboard is Home Base</h3>
|
||||||
<p>Everything starts at your dashboard. See your total events, upcoming trainings, revenue progress, and quick links to all features. No need to access WordPress admin!</p>
|
<p>Everything starts at your dashboard. See your total events, upcoming trainings, revenue progress, and quick links to all features. Track your performance metrics and manage everything from one place.</p>
|
||||||
<a href="/hvac-dashboard" class="hvac-doc-btn">Go to Dashboard</a>
|
<a href="' . home_url('/trainer/dashboard/') . '" class="hvac-doc-btn">Go to Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-doc-card">
|
<div class="hvac-doc-card">
|
||||||
<h3>2. Create Your First Event</h3>
|
<h3>2. Update Your Profile First</h3>
|
||||||
<p>Click "Create Event" from any page. Fill in the simple form - event title, description, date, and pricing. Your event saves as a draft automatically.</p>
|
<p>Your trainer profile is your professional showcase in our directory. Complete your profile to appear in "Find A Trainer" searches and generate more training leads.</p>
|
||||||
<a href="/manage-event" class="hvac-doc-btn">Create Event</a>
|
<a href="' . home_url('/trainer/profile/') . '" class="hvac-doc-btn">Edit Profile</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-doc-card">
|
<div class="hvac-doc-card">
|
||||||
<h3>3. Complete Your Profile</h3>
|
<h3>3. Create Your First Event</h3>
|
||||||
<p>Add your credentials and business info to build trust with trainees. A complete profile helps your events get found and booked faster.</p>
|
<p>Ready to host training? Create events with pricing, capacity limits, and venue information. Your events integrate with The Events Calendar for seamless management.</p>
|
||||||
<a href="/trainer-profile" class="hvac-doc-btn">Edit Profile</a>
|
<a href="' . home_url('/tribe/events/add/') . '" class="hvac-doc-btn">Create Event</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="managing-events" class="hvac-doc-section">
|
<section id="managing-events" class="hvac-doc-section">
|
||||||
<h2><i class="fas fa-calendar-alt"></i> Managing Events</h2>
|
<h2><i class="fas fa-calendar-alt"></i> Managing Events & Training</h2>
|
||||||
<div class="hvac-feature-list">
|
<div class="hvac-feature-list">
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>Creating Events is Simple</h3>
|
<h3>Integrated Event System</h3>
|
||||||
<ol>
|
<p>Our platform integrates with The Events Calendar to provide comprehensive event management:</p>
|
||||||
<li><strong>Click "Create Event"</strong> from your dashboard or navigation menu</li>
|
|
||||||
<li><strong>Fill the form:</strong> Title, description, date/time, venue, and pricing</li>
|
|
||||||
<li><strong>Save as Draft:</strong> Review and edit anytime before publishing</li>
|
|
||||||
<li><strong>Publish:</strong> Your event goes live immediately</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
<div class="hvac-feature">
|
|
||||||
<h3>What You Can Do</h3>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Edit Events:</strong> Click any event title to modify details</li>
|
<li><strong>Training Events:</strong> Sessions with pricing, capacity, and location</li>
|
||||||
<li><strong>Set Capacity:</strong> Control how many can register</li>
|
<li><strong>Venues:</strong> Physical or virtual training locations</li>
|
||||||
<li><strong>Track Sales:</strong> See registrations in real-time</li>
|
<li><strong>Organizers:</strong> Business entities with logos and contact info</li>
|
||||||
<li><strong>Quick Actions:</strong> View, edit, or check attendees with one click</li>
|
<li><strong>Revenue Tracking:</strong> Monitor training income and attendance</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>Event Summary Page</h3>
|
<h3>Quick Event Management</h3>
|
||||||
<p>Click "View Summary" on any event to see everything at a glance: attendee list, revenue, check-in status, and quick links to email attendees or generate certificates.</p>
|
<ol>
|
||||||
|
<li><strong>Dashboard Overview:</strong> See all your events at a glance</li>
|
||||||
|
<li><strong>New Event:</strong> Click to create from navigation menu</li>
|
||||||
|
<li><strong>Event Management:</strong> Access manage page for detailed controls</li>
|
||||||
|
<li><strong>Analytics:</strong> View performance metrics and revenue data</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-feature">
|
||||||
|
<h3>Venues & Organizers</h3>
|
||||||
|
<p>Manage your training infrastructure:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Training Venues:</strong> Add locations where you conduct training</li>
|
||||||
|
<li><strong>Training Organizers:</strong> Set up business entities with branding</li>
|
||||||
|
<li><strong>Auto-Creation:</strong> We\'ve tried to auto-create these for you!</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="attendee-management" class="hvac-doc-section">
|
<section id="profile-directory" class="hvac-doc-section">
|
||||||
<h2><i class="fas fa-users"></i> Attendee Management</h2>
|
<h2><i class="fas fa-user-circle"></i> Your Professional Profile</h2>
|
||||||
<div class="hvac-feature-list">
|
<div class="hvac-feature-list">
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>See Who\'s Coming</h3>
|
<h3>Directory Listing</h3>
|
||||||
<p>Your dashboard shows registration counts. Click "View Attendees" on any event to see the full list with names, emails, and check-in status.</p>
|
<p>Your profile appears in our "Find A Trainer" directory where potential students can discover you:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Map Integration:</strong> Shows your location and service areas</li>
|
||||||
|
<li><strong>Contact Form:</strong> Students can reach out directly</li>
|
||||||
|
<li><strong>QR Code Sharing:</strong> Easy sharing at events and conferences</li>
|
||||||
|
<li><strong>Professional Showcase:</strong> Display credentials and experience</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>Easy Email Communication</h3>
|
<h3>Training Leads</h3>
|
||||||
<p>Click "Email Attendees" to send updates. Select all attendees or just those who are checked in. Add CC recipients and your message is sent instantly.</p>
|
<p>When potential students find you in the directory, they can submit contact requests that appear in your Training Leads page:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Lead Management:</strong> Track contact requests from potential clients</li>
|
||||||
|
<li><strong>Status Tracking:</strong> Mark leads as read, replied, or archived</li>
|
||||||
|
<li><strong>Direct Contact:</strong> Email and phone information included</li>
|
||||||
|
<li><strong>Message History:</strong> View full inquiry details</li>
|
||||||
|
</ul>
|
||||||
|
<a href="' . home_url('/trainer/training-leads/') . '" class="hvac-doc-btn">View Training Leads</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>Quick Check-In</h3>
|
<h3>Pro Tip: Share Your Profile</h3>
|
||||||
<p>During your event, use the attendee list to check people in. This helps track completion for certificates and keeps accurate records.</p>
|
<p>The more you share your profile, the more training leads you can generate! Use social media, business cards, or the built-in QR code feature.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="certificates" class="hvac-doc-section">
|
<section id="certificates" class="hvac-doc-section">
|
||||||
<h2><i class="fas fa-certificate"></i> Professional Certificates - NEW!</h2>
|
<h2><i class="fas fa-certificate"></i> Certificate Generation</h2>
|
||||||
<div class="hvac-feature-list">
|
<div class="hvac-feature-list">
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>Beautiful Certificates Automatically</h3>
|
<h3>Professional Certificates</h3>
|
||||||
<p>Generate professional certificates with the Upskill HVAC logo, your name as instructor, and attendee details. Each certificate has a unique number and can be verified.</p>
|
<p>Generate beautiful completion certificates with the Upskill HVAC logo, your name as instructor, and attendee details. Each certificate has a unique number and can be verified.</p>
|
||||||
</div>
|
|
||||||
<div class="hvac-feature">
|
|
||||||
<h3>Simple Generation Process</h3>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Go to "Generate Certificates"</strong> from the menu</li>
|
<li><strong>Automated Generation:</strong> Create certificates for event attendees</li>
|
||||||
<li><strong>Select your event</strong> from the dropdown</li>
|
<li><strong>Professional Design:</strong> Branded with Upskill HVAC styling</li>
|
||||||
<li><strong>Choose attendees</strong> (or select all)</li>
|
<li><strong>Unique Verification:</strong> Each certificate has a verification number</li>
|
||||||
<li><strong>Click Generate</strong> - certificates are created instantly!</li>
|
<li><strong>Easy Distribution:</strong> Download and share with attendees</li>
|
||||||
<li><strong>Click "Certificate Issued"</strong> text to view any certificate</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-feature">
|
<div class="hvac-feature">
|
||||||
<h3>Track Everything</h3>
|
<h3>Certificate Workflow</h3>
|
||||||
<p>The Certificate Reports page shows all certificates you\'ve issued. Filter by event, search by name, and download certificates anytime.</p>
|
<ol>
|
||||||
|
<li><strong>Navigate to Certificates:</strong> Use the navigation menu</li>
|
||||||
|
<li><strong>Generate New Certificates:</strong> Select event and attendees</li>
|
||||||
|
<li><strong>View Certificate Reports:</strong> Track all issued certificates</li>
|
||||||
|
<li><strong>Download & Share:</strong> Distribute to your trainees</li>
|
||||||
|
</ol>
|
||||||
|
<div class="hvac-feature-links">
|
||||||
|
<a href="' . home_url('/trainer/generate-certificates/') . '" class="hvac-doc-btn">Generate Certificates</a>
|
||||||
|
<a href="' . home_url('/trainer/certificate-reports/') . '" class="hvac-doc-btn hvac-doc-btn-secondary">View Reports</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="navigation" class="hvac-doc-section">
|
||||||
|
<h2><i class="fas fa-compass"></i> Navigation Guide</h2>
|
||||||
|
<div class="hvac-feature-list">
|
||||||
|
<div class="hvac-feature">
|
||||||
|
<h3>Updated Navigation Menu</h3>
|
||||||
|
<p>Our streamlined navigation makes it easy to access all platform features:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Events:</strong> Dashboard and create new events</li>
|
||||||
|
<li><strong>Certificates:</strong> Generate and view certificate reports</li>
|
||||||
|
<li><strong>Profile:</strong> Your professional profile, training leads, venues, and organizers</li>
|
||||||
|
<li><strong>Help (?):</strong> Access this documentation anytime</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-feature">
|
||||||
|
<h3>Quick Access Features</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Dashboard:</strong> Your training business overview</li>
|
||||||
|
<li><strong>Training Leads:</strong> Manage contact requests from potential clients</li>
|
||||||
|
<li><strong>Profile Management:</strong> Update your professional information</li>
|
||||||
|
<li><strong>Venue & Organizer Setup:</strong> Configure your training infrastructure</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -339,39 +391,48 @@ class HVAC_Help_System {
|
||||||
<div class="hvac-faq-list">
|
<div class="hvac-faq-list">
|
||||||
<div class="hvac-faq-item">
|
<div class="hvac-faq-item">
|
||||||
<h3>Where do I start?</h3>
|
<h3>Where do I start?</h3>
|
||||||
<p>Start at your dashboard! It shows everything you need. Click "Create Event" to add your first training, or "My Events" to see what you\'ve already created.</p>
|
<p>Start by updating your trainer profile to appear in our directory, then create your first training event. Your dashboard provides an overview of everything.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
<div class="hvac-faq-item">
|
||||||
<h3>How do I edit an event?</h3>
|
<h3>How do I get more training leads?</h3>
|
||||||
<p>From your dashboard, find the event and click its title. You\'ll go straight to the edit page. Make changes and click "Update Event" to save.</p>
|
<p>Complete your profile and share it! The more visible you are in our "Find A Trainer" directory, the more potential students will contact you through the Training Leads system.</p>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-faq-item">
|
||||||
|
<h3>What\'s the difference between venues and organizers?</h3>
|
||||||
|
<p>Venues are physical or virtual locations where you conduct training. Organizers are business entities (like your company) that host the training and can have logos and branding.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
<div class="hvac-faq-item">
|
||||||
<h3>How do certificates work?</h3>
|
<h3>How do certificates work?</h3>
|
||||||
<p>After your event, go to "Generate Certificates" and select your event. Choose which attendees get certificates (usually those who were checked in). Click generate and they\'re ready! Each certificate shows your name, the attendee\'s name, and has the Upskill HVAC logo.</p>
|
<p>After your training event, you can generate professional certificates for attendees. Each certificate is uniquely numbered and includes your name as the instructor and the Upskill HVAC branding.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
<div class="hvac-faq-item">
|
||||||
<h3>Can attendees view their certificates?</h3>
|
<h3>Can I track my training revenue?</h3>
|
||||||
<p>Yes! On the Generate Certificates page, you\'ll see "Certificate Issued" under each attendee who has one. Click this text to open their certificate - you can share this link with them.</p>
|
<p>Yes! Your dashboard shows revenue tracking and analytics for all your training events. You can monitor your training business performance over time.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
<div class="hvac-faq-item">
|
||||||
<h3>What\'s the revenue target on my dashboard?</h3>
|
<h3>What if I need help?</h3>
|
||||||
<p>This is your annual goal to maintain your trainer status. The progress bar shows how close you are. Keep creating quality events and you\'ll reach it!</p>
|
<p>Click the "?" icon in the navigation menu to return to this documentation anytime. You can also reach out to our support team for additional assistance.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
|
||||||
<h3>How do I email my attendees?</h3>
|
|
||||||
<p>Click "Email Attendees" from the menu or from any event summary. Select who to email, write your message, and send. You can CC yourself or others too.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
</section>
|
||||||
<h3>Do I need to use WordPress admin?</h3>
|
|
||||||
<p>No! Everything you need is in your trainer dashboard and the connected pages. The system is designed so you never need to access the WordPress backend.</p>
|
<section id="getting-help" class="hvac-doc-section">
|
||||||
|
<h2><i class="fas fa-life-ring"></i> Getting Support</h2>
|
||||||
|
<div class="hvac-feature-list">
|
||||||
|
<div class="hvac-feature">
|
||||||
|
<h3>Quick Start Checklist</h3>
|
||||||
|
<ol>
|
||||||
|
<li><strong>✓ Update Your Profile</strong> - Make sure your information is complete</li>
|
||||||
|
<li><strong>✓ Add Training Venues</strong> - Set up your training locations</li>
|
||||||
|
<li><strong>✓ Create Training Organizer</strong> - Add your business entity</li>
|
||||||
|
<li><strong>✓ Create First Event</strong> - Host your first training session</li>
|
||||||
|
<li><strong>✓ Share Your Profile</strong> - Start generating training leads</li>
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-faq-item">
|
<div class="hvac-feature">
|
||||||
<h3>How do payments work?</h3>
|
<h3>Remember</h3>
|
||||||
<p>Attendees pay through Stripe when they register. You receive 100% of ticket sales (minus Stripe\'s standard 2.9% + 30¢ fee) directly to your connected account.</p>
|
<p>We\'ve tried to auto-create venues and organizers for you based on your profile information, but you may need to make corrections or updates to ensure everything is accurate.</p>
|
||||||
</div>
|
<p><strong>Pro tip:</strong> The more complete your profile, the more professional you appear to potential students and the more training leads you\'ll generate!</p>
|
||||||
<div class="hvac-faq-item">
|
|
||||||
<h3>Need more help?</h3>
|
|
||||||
<p>Look for the (?) tooltips throughout the site - hover over them for quick help. This documentation is always available from the Help link. For urgent issues, contact support.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>';
|
</section>';
|
||||||
|
|
|
||||||
|
|
@ -169,17 +169,22 @@ class HVAC_Menu_System {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Customize section
|
// Profile section (previously Customize)
|
||||||
$menu[] = array(
|
$menu[] = array(
|
||||||
'title' => 'Customize',
|
'title' => 'Profile',
|
||||||
'url' => '#',
|
'url' => '#',
|
||||||
'icon' => 'dashicons-admin-customizer',
|
'icon' => 'dashicons-admin-users',
|
||||||
'children' => array(
|
'children' => array(
|
||||||
array(
|
array(
|
||||||
'title' => 'Personal Profile',
|
'title' => 'Trainer Profile',
|
||||||
'url' => home_url('/trainer/profile/'),
|
'url' => home_url('/trainer/profile/'),
|
||||||
'icon' => 'dashicons-admin-users'
|
'icon' => 'dashicons-admin-users'
|
||||||
),
|
),
|
||||||
|
array(
|
||||||
|
'title' => 'Training Leads',
|
||||||
|
'url' => home_url('/trainer/training-leads/'),
|
||||||
|
'icon' => 'dashicons-email-alt'
|
||||||
|
),
|
||||||
array(
|
array(
|
||||||
'title' => 'Training Organizers',
|
'title' => 'Training Organizers',
|
||||||
'url' => home_url('/trainer/organizer/list/'),
|
'url' => home_url('/trainer/organizer/list/'),
|
||||||
|
|
@ -203,22 +208,21 @@ class HVAC_Menu_System {
|
||||||
'icon' => 'dashicons-plus-alt'
|
'icon' => 'dashicons-plus-alt'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
)
|
array(
|
||||||
);
|
|
||||||
|
|
||||||
// Help section
|
|
||||||
$menu[] = array(
|
|
||||||
'title' => 'Help',
|
|
||||||
'url' => home_url('/trainer/documentation/'),
|
|
||||||
'icon' => 'dashicons-sos'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Logout (always last)
|
|
||||||
$menu[] = array(
|
|
||||||
'title' => 'Logout',
|
'title' => 'Logout',
|
||||||
'url' => wp_logout_url(home_url('/training-login/')),
|
'url' => wp_logout_url(home_url('/training-login/')),
|
||||||
'icon' => 'dashicons-exit'
|
'icon' => 'dashicons-exit'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Help section (moved to end, with question mark icon)
|
||||||
|
$menu[] = array(
|
||||||
|
'title' => '?',
|
||||||
|
'url' => home_url('/trainer/documentation/'),
|
||||||
|
'icon' => 'dashicons-editor-help',
|
||||||
|
'class' => 'hvac-help-menu-item'
|
||||||
);
|
);
|
||||||
|
|
||||||
return $menu;
|
return $menu;
|
||||||
|
|
@ -242,12 +246,21 @@ class HVAC_Menu_System {
|
||||||
$classes[] = 'level-' . $level;
|
$classes[] = 'level-' . $level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom class if specified
|
||||||
|
if (!empty($item['class'])) {
|
||||||
|
$classes[] = esc_attr($item['class']);
|
||||||
|
}
|
||||||
|
|
||||||
echo '<li class="' . implode(' ', $classes) . '">';
|
echo '<li class="' . implode(' ', $classes) . '">';
|
||||||
|
|
||||||
|
// Check if this is the help menu item (show only icon)
|
||||||
|
$is_help_menu = !empty($item['class']) && strpos($item['class'], 'hvac-help-menu-item') !== false;
|
||||||
|
$title_content = $is_help_menu ? '' : esc_html($item['title']);
|
||||||
|
|
||||||
if ($item['url'] === '#' && $has_children) {
|
if ($item['url'] === '#' && $has_children) {
|
||||||
echo '<span class="menu-toggle">' . $icon . esc_html($item['title']) . '<span class="dropdown-arrow">▼</span></span>';
|
echo '<span class="menu-toggle">' . $icon . $title_content . '<span class="dropdown-arrow">▼</span></span>';
|
||||||
} else {
|
} else {
|
||||||
echo '<a href="' . esc_url($item['url']) . '">' . $icon . esc_html($item['title']) . '</a>';
|
echo '<a href="' . esc_url($item['url']) . '">' . $icon . $title_content . '</a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($has_children) {
|
if ($has_children) {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,13 @@ class HVAC_Page_Manager {
|
||||||
'parent' => 'trainer/profile',
|
'parent' => 'trainer/profile',
|
||||||
'capability' => 'hvac_trainer'
|
'capability' => 'hvac_trainer'
|
||||||
],
|
],
|
||||||
|
'trainer/training-leads' => [
|
||||||
|
'title' => 'Training Leads',
|
||||||
|
'template' => 'page-trainer-training-leads.php',
|
||||||
|
'public' => false,
|
||||||
|
'parent' => 'trainer/profile',
|
||||||
|
'capability' => 'hvac_trainer'
|
||||||
|
],
|
||||||
|
|
||||||
// Venue management pages
|
// Venue management pages
|
||||||
'trainer/venue' => [
|
'trainer/venue' => [
|
||||||
|
|
@ -460,6 +467,7 @@ class HVAC_Page_Manager {
|
||||||
$shortcode_mappings = [
|
$shortcode_mappings = [
|
||||||
'trainer/profile' => '[hvac_trainer_profile_view]',
|
'trainer/profile' => '[hvac_trainer_profile_view]',
|
||||||
'trainer/profile/edit' => '[hvac_trainer_profile_edit]',
|
'trainer/profile/edit' => '[hvac_trainer_profile_edit]',
|
||||||
|
'trainer/training-leads' => '[hvac_trainer_training_leads]',
|
||||||
'trainer/venue/list' => '[hvac_trainer_venues_list]',
|
'trainer/venue/list' => '[hvac_trainer_venues_list]',
|
||||||
'trainer/venue/manage' => '[hvac_trainer_venue_manage]',
|
'trainer/venue/manage' => '[hvac_trainer_venue_manage]',
|
||||||
'trainer/organizer/list' => '[hvac_trainer_organizers_list]',
|
'trainer/organizer/list' => '[hvac_trainer_organizers_list]',
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,10 @@ class HVAC_Plugin {
|
||||||
*/
|
*/
|
||||||
private function define_constants() {
|
private function define_constants() {
|
||||||
if (!defined('HVAC_PLUGIN_VERSION')) {
|
if (!defined('HVAC_PLUGIN_VERSION')) {
|
||||||
define('HVAC_PLUGIN_VERSION', '1.0.1');
|
define('HVAC_PLUGIN_VERSION', '1.0.6');
|
||||||
}
|
}
|
||||||
if (!defined('HVAC_VERSION')) {
|
if (!defined('HVAC_VERSION')) {
|
||||||
define('HVAC_VERSION', '1.0.1');
|
define('HVAC_VERSION', '1.0.6');
|
||||||
}
|
}
|
||||||
if (!defined('HVAC_PLUGIN_FILE')) {
|
if (!defined('HVAC_PLUGIN_FILE')) {
|
||||||
define('HVAC_PLUGIN_FILE', dirname(__DIR__) . '/hvac-community-events.php');
|
define('HVAC_PLUGIN_FILE', dirname(__DIR__) . '/hvac-community-events.php');
|
||||||
|
|
@ -103,6 +103,7 @@ class HVAC_Plugin {
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-route-manager.php';
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-route-manager.php';
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-menu-system.php';
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-menu-system.php';
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-role-consolidator.php';
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-role-consolidator.php';
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-welcome-popup.php';
|
||||||
|
|
||||||
// Feature includes - check if files exist before including
|
// Feature includes - check if files exist before including
|
||||||
$feature_includes = [
|
$feature_includes = [
|
||||||
|
|
@ -115,10 +116,12 @@ class HVAC_Plugin {
|
||||||
'class-hvac-geocoding-service.php',
|
'class-hvac-geocoding-service.php',
|
||||||
'class-hvac-trainer-profile-settings.php',
|
'class-hvac-trainer-profile-settings.php',
|
||||||
'class-hvac-geocoding-ajax.php',
|
'class-hvac-geocoding-ajax.php',
|
||||||
|
'class-hvac-qr-generator.php',
|
||||||
'class-hvac-organizers.php',
|
'class-hvac-organizers.php',
|
||||||
'class-hvac-trainer-navigation.php',
|
'class-hvac-trainer-navigation.php',
|
||||||
'class-hvac-breadcrumbs.php',
|
'class-hvac-breadcrumbs.php',
|
||||||
'class-hvac-template-integration.php',
|
'class-hvac-template-integration.php',
|
||||||
|
'class-hvac-training-leads.php',
|
||||||
'class-hvac-manage-event.php',
|
'class-hvac-manage-event.php',
|
||||||
'class-hvac-event-summary.php',
|
'class-hvac-event-summary.php',
|
||||||
'class-hvac-trainer-profile.php',
|
'class-hvac-trainer-profile.php',
|
||||||
|
|
|
||||||
337
includes/class-hvac-qr-generator.php
Normal file
337
includes/class-hvac-qr-generator.php
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* HVAC QR Code Generator
|
||||||
|
*
|
||||||
|
* Generates QR codes for trainer profile sharing using Google Charts API
|
||||||
|
*
|
||||||
|
* @package HVAC_Community_Events
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Exit if accessed directly
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HVAC QR Code Generator Class
|
||||||
|
*/
|
||||||
|
class HVAC_QR_Generator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance
|
||||||
|
*
|
||||||
|
* @var HVAC_QR_Generator
|
||||||
|
*/
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance
|
||||||
|
*/
|
||||||
|
public static function instance() {
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
$this->init_hooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize WordPress hooks
|
||||||
|
*/
|
||||||
|
private function init_hooks() {
|
||||||
|
// AJAX handlers for profile sharing
|
||||||
|
add_action('wp_ajax_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']);
|
||||||
|
add_action('wp_ajax_nopriv_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']);
|
||||||
|
|
||||||
|
// URL rewrite rules for direct profile access
|
||||||
|
add_action('init', [$this, 'add_profile_rewrite_rules']);
|
||||||
|
add_filter('query_vars', [$this, 'add_profile_query_vars']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code URL using QR Server API
|
||||||
|
*
|
||||||
|
* @param string $data Data to encode in QR code
|
||||||
|
* @param int $size QR code size in pixels (default: 200)
|
||||||
|
* @param string $error_correction Error correction level (L, M, Q, H)
|
||||||
|
* @return string QR code image URL
|
||||||
|
*/
|
||||||
|
public function generate_qr_url($data, $size = 200, $error_correction = 'M') {
|
||||||
|
// Encode the data for URL
|
||||||
|
$encoded_data = urlencode($data);
|
||||||
|
|
||||||
|
// QR Server API URL (free and reliable alternative to Google Charts)
|
||||||
|
$qr_url = sprintf(
|
||||||
|
'https://api.qrserver.com/v1/create-qr-code/?size=%dx%d&data=%s&ecc=%s',
|
||||||
|
$size,
|
||||||
|
$size,
|
||||||
|
$encoded_data,
|
||||||
|
strtoupper($error_correction)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $qr_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code for trainer profile
|
||||||
|
*
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @param int $size QR code size in pixels
|
||||||
|
* @return string|false QR code URL or false on error
|
||||||
|
*/
|
||||||
|
public function generate_trainer_profile_qr($profile_id, $size = 200) {
|
||||||
|
if (!$profile_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the profile URL
|
||||||
|
$profile_url = $this->get_trainer_profile_share_url($profile_id);
|
||||||
|
|
||||||
|
if (!$profile_url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->generate_qr_url($profile_url, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get shareable trainer profile URL
|
||||||
|
*
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @return string|false Profile URL or false on error
|
||||||
|
*/
|
||||||
|
public function get_trainer_profile_share_url($profile_id) {
|
||||||
|
if (!$profile_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the Find a Trainer page URL
|
||||||
|
$find_trainer_page = get_page_by_path('find-a-trainer');
|
||||||
|
if (!$find_trainer_page) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$base_url = get_permalink($find_trainer_page->ID);
|
||||||
|
|
||||||
|
// Create the profile-specific URL (with trailing slash for WordPress rewrite rules)
|
||||||
|
$profile_url = trailingslashit($base_url) . 'profile/' . $profile_id . '/';
|
||||||
|
|
||||||
|
return $profile_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get trainer profile data for sharing
|
||||||
|
*
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @return array|false Profile data or false on error
|
||||||
|
*/
|
||||||
|
public function get_trainer_share_data($profile_id) {
|
||||||
|
if (!$profile_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the profile post
|
||||||
|
$profile = get_post($profile_id);
|
||||||
|
if (!$profile || $profile->post_type !== 'trainer_profile') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get profile metadata
|
||||||
|
$user_id = get_post_meta($profile_id, 'user_id', true);
|
||||||
|
if (!$user_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user data
|
||||||
|
$user = get_userdata($user_id);
|
||||||
|
if (!$user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile share data
|
||||||
|
$share_data = [
|
||||||
|
'profile_id' => $profile_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'trainer_name' => get_post_meta($profile_id, 'trainer_display_name', true) ?: $user->display_name,
|
||||||
|
'business_name' => get_user_meta($user_id, 'business_name', true),
|
||||||
|
'trainer_city' => get_post_meta($profile_id, 'trainer_city', true),
|
||||||
|
'trainer_state' => get_post_meta($profile_id, 'trainer_state', true),
|
||||||
|
'certification_type' => get_post_meta($profile_id, 'certification_type', true),
|
||||||
|
'profile_image' => get_post_meta($profile_id, 'profile_image_url', true),
|
||||||
|
'share_url' => $this->get_trainer_profile_share_url($profile_id),
|
||||||
|
'qr_code_url' => $this->generate_trainer_profile_qr($profile_id, 200)
|
||||||
|
];
|
||||||
|
|
||||||
|
return $share_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse trainer profile ID from URL
|
||||||
|
*
|
||||||
|
* @param string $url URL to parse
|
||||||
|
* @return int|false Profile ID or false if not found
|
||||||
|
*/
|
||||||
|
public function parse_profile_id_from_url($url = null) {
|
||||||
|
// First check if we have a query variable (from rewrite rule)
|
||||||
|
$profile_id = get_query_var('trainer_profile_id');
|
||||||
|
if ($profile_id) {
|
||||||
|
return intval($profile_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to URL parsing
|
||||||
|
if (!$url) {
|
||||||
|
$url = $_SERVER['REQUEST_URI'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if URL matches pattern: /find-a-trainer/profile/{profile_id}
|
||||||
|
if (preg_match('/\/find-a-trainer\/profile\/(\d+)\/?/', $url, $matches)) {
|
||||||
|
return intval($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate profile card HTML for sharing
|
||||||
|
*
|
||||||
|
* @param int $profile_id Trainer profile ID
|
||||||
|
* @param array $options Display options
|
||||||
|
* @return string|false HTML content or false on error
|
||||||
|
*/
|
||||||
|
public function generate_profile_card_html($profile_id, $options = []) {
|
||||||
|
$share_data = $this->get_trainer_share_data($profile_id);
|
||||||
|
if (!$share_data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default options
|
||||||
|
$options = wp_parse_args($options, [
|
||||||
|
'show_qr' => true,
|
||||||
|
'qr_size' => 150,
|
||||||
|
'card_width' => 600,
|
||||||
|
'card_height' => 300
|
||||||
|
]);
|
||||||
|
|
||||||
|
$qr_url = $options['show_qr'] ? $this->generate_trainer_profile_qr($profile_id, $options['qr_size']) : '';
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<div class="hvac-share-profile-card" style="width: <?php echo esc_attr($options['card_width']); ?>px; height: <?php echo esc_attr($options['card_height']); ?>px; border: 2px solid #e0e0e0; border-radius: 12px; padding: 20px; background: #fff; display: flex; align-items: center; gap: 20px; font-family: Arial, sans-serif;">
|
||||||
|
|
||||||
|
<!-- Profile Image -->
|
||||||
|
<div class="hvac-share-avatar" style="width: 120px; height: 120px; flex-shrink: 0; position: relative;">
|
||||||
|
<?php if (!empty($share_data['profile_image'])): ?>
|
||||||
|
<img src="<?php echo esc_url($share_data['profile_image']); ?>"
|
||||||
|
alt="<?php echo esc_attr($share_data['trainer_name']); ?>"
|
||||||
|
style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%; background: #ddd;">
|
||||||
|
<?php else: ?>
|
||||||
|
<div style="width: 100%; height: 100%; background: #6c757d; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 40px; font-weight: bold;">
|
||||||
|
<?php echo esc_html(substr($share_data['trainer_name'], 0, 1)); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- mQ Certified Badge -->
|
||||||
|
<?php if ($share_data['certification_type'] === 'Certified measureQuick Trainer'): ?>
|
||||||
|
<div style="position: absolute; top: -5px; right: -5px; width: 35px; height: 35px;">
|
||||||
|
<img src="/wp-content/uploads/2025/08/mQ-Certified-trainer.png"
|
||||||
|
alt="measureQuick Certified Trainer"
|
||||||
|
style="width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Details -->
|
||||||
|
<div class="hvac-share-details" style="flex: 1; min-width: 0;">
|
||||||
|
<h2 style="margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #333;">
|
||||||
|
<?php echo esc_html($share_data['trainer_name']); ?>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<?php if (!empty($share_data['business_name'])): ?>
|
||||||
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #666; font-weight: 500;">
|
||||||
|
<?php echo esc_html($share_data['business_name']); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #666;">
|
||||||
|
<?php echo esc_html($share_data['trainer_city'] . ', ' . $share_data['trainer_state']); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if (!empty($share_data['certification_type'])): ?>
|
||||||
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #0073aa; font-weight: 500;">
|
||||||
|
<?php echo esc_html($share_data['certification_type']); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- QR Code -->
|
||||||
|
<?php if ($options['show_qr'] && $qr_url): ?>
|
||||||
|
<div class="hvac-share-qr" style="width: <?php echo esc_attr($options['qr_size']); ?>px; height: <?php echo esc_attr($options['qr_size']); ?>px; flex-shrink: 0;">
|
||||||
|
<img src="<?php echo esc_url($qr_url); ?>"
|
||||||
|
alt="QR Code for <?php echo esc_attr($share_data['trainer_name']); ?>"
|
||||||
|
style="width: 100%; height: 100%; object-fit: contain;">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler for getting profile share data
|
||||||
|
*/
|
||||||
|
public function ajax_get_profile_share_data() {
|
||||||
|
// Verify nonce
|
||||||
|
if (!wp_verify_nonce($_POST['nonce'], 'hvac_profile_sharing')) {
|
||||||
|
wp_send_json_error(['message' => 'Security check failed']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile_id = intval($_POST['profile_id']);
|
||||||
|
if (!$profile_id) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid profile ID']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the share data
|
||||||
|
$share_data = $this->get_trainer_share_data($profile_id);
|
||||||
|
|
||||||
|
if (!$share_data) {
|
||||||
|
wp_send_json_error(['message' => 'Profile not found or not accessible']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success($share_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add rewrite rules for direct profile access
|
||||||
|
*/
|
||||||
|
public function add_profile_rewrite_rules() {
|
||||||
|
// Add rewrite rule for /find-a-trainer/profile/{profile_id}
|
||||||
|
add_rewrite_rule(
|
||||||
|
'^find-a-trainer/profile/([0-9]+)/?$',
|
||||||
|
'index.php?pagename=find-a-trainer&trainer_profile_id=$matches[1]',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom query variables
|
||||||
|
*/
|
||||||
|
public function add_profile_query_vars($vars) {
|
||||||
|
$vars[] = 'trainer_profile_id';
|
||||||
|
return $vars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
HVAC_QR_Generator::instance();
|
||||||
|
|
@ -1303,7 +1303,7 @@ class HVAC_Registration {
|
||||||
$user = get_userdata($user_id);
|
$user = get_userdata($user_id);
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$submitted_data = $_POST;
|
$submitted_data = $_POST;
|
||||||
$profile_page_url = home_url('/edit-profile/');
|
$profile_page_url = home_url('/trainer/profile/edit/');
|
||||||
|
|
||||||
// Verify nonce
|
// Verify nonce
|
||||||
if (!isset($_POST['hvac_profile_nonce']) || !wp_verify_nonce($_POST['hvac_profile_nonce'], 'hvac_update_profile')) {
|
if (!isset($_POST['hvac_profile_nonce']) || !wp_verify_nonce($_POST['hvac_profile_nonce'], 'hvac_update_profile')) {
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,14 @@ class HVAC_Scripts_Styles {
|
||||||
array('hvac-community-events'),
|
array('hvac-community-events'),
|
||||||
$this->version
|
$this->version
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Profile sharing styles - includes modal and QR code display
|
||||||
|
wp_enqueue_style(
|
||||||
|
'hvac-profile-sharing',
|
||||||
|
HVAC_PLUGIN_URL . 'assets/css/hvac-profile-sharing.css',
|
||||||
|
array('hvac-trainer-profile'),
|
||||||
|
$this->version
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event manage page styles
|
// Event manage page styles
|
||||||
|
|
@ -287,6 +295,17 @@ class HVAC_Scripts_Styles {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trainer profile scripts
|
||||||
|
if ($this->is_trainer_profile_page()) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'hvac-profile-sharing',
|
||||||
|
HVAC_PLUGIN_URL . 'assets/js/hvac-profile-sharing.js',
|
||||||
|
array('jquery', 'hvac-community-events'),
|
||||||
|
$this->version,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Help system scripts
|
// Help system scripts
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'hvac-help-system',
|
'hvac-help-system',
|
||||||
|
|
@ -315,6 +334,21 @@ class HVAC_Scripts_Styles {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Localize profile sharing script
|
||||||
|
if ($this->is_trainer_profile_page()) {
|
||||||
|
wp_localize_script('hvac-profile-sharing', 'hvac_sharing', array(
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('hvac_profile_sharing'),
|
||||||
|
'strings' => array(
|
||||||
|
'loading' => __('Loading...', 'hvac-community-events'),
|
||||||
|
'error' => __('An error occurred. Please try again.', 'hvac-community-events'),
|
||||||
|
'copied' => __('Copied to clipboard!', 'hvac-community-events'),
|
||||||
|
'copy_error' => __('Unable to copy. Please select and copy manually.', 'hvac-community-events'),
|
||||||
|
'loading_error' => __('Unable to load profile card. Please try again.', 'hvac-community-events')
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -635,4 +669,16 @@ class HVAC_Scripts_Styles {
|
||||||
|
|
||||||
return $this->version;
|
return $this->version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localize sharing data for profile pages
|
||||||
|
*
|
||||||
|
* @param array $data Sharing data to localize
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function localize_sharing_data($data) {
|
||||||
|
if ($this->is_trainer_profile_page() && wp_script_is('hvac-profile-sharing', 'enqueued')) {
|
||||||
|
wp_localize_script('hvac-profile-sharing', 'hvac_sharing_data', $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
735
includes/class-hvac-training-leads.php
Normal file
735
includes/class-hvac-training-leads.php
Normal file
|
|
@ -0,0 +1,735 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Training Leads Management
|
||||||
|
*
|
||||||
|
* @package HVAC_Community_Events
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HVAC_Training_Leads class
|
||||||
|
*/
|
||||||
|
class HVAC_Training_Leads {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance of this class
|
||||||
|
*
|
||||||
|
* @var HVAC_Training_Leads
|
||||||
|
*/
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance of this class
|
||||||
|
*
|
||||||
|
* @return HVAC_Training_Leads
|
||||||
|
*/
|
||||||
|
public static function get_instance() {
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
add_action('init', array($this, 'init_hooks'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize hooks
|
||||||
|
*/
|
||||||
|
public function init_hooks() {
|
||||||
|
// Register shortcode
|
||||||
|
add_shortcode('hvac_trainer_training_leads', array($this, 'render_training_leads_page'));
|
||||||
|
|
||||||
|
// AJAX handlers
|
||||||
|
add_action('wp_ajax_hvac_update_lead_status', array($this, 'ajax_update_lead_status'));
|
||||||
|
add_action('wp_ajax_hvac_mark_lead_replied', array($this, 'ajax_mark_lead_replied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the Training Leads page
|
||||||
|
*/
|
||||||
|
public function render_training_leads_page($atts = array()) {
|
||||||
|
if (!is_user_logged_in()) {
|
||||||
|
return '<p>Please log in to view your training leads.</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user capabilities
|
||||||
|
if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) {
|
||||||
|
return '<p>You do not have permission to view this page.</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current user
|
||||||
|
$current_user = wp_get_current_user();
|
||||||
|
|
||||||
|
// Get submissions for this trainer
|
||||||
|
$submissions = $this->get_trainer_submissions($current_user->ID);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<div class="hvac-training-leads-wrapper">
|
||||||
|
<div class="hvac-page-header">
|
||||||
|
<h1>Training Leads</h1>
|
||||||
|
<p class="hvac-page-description">Manage contact requests from potential training clients</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($submissions)) : ?>
|
||||||
|
<div class="hvac-leads-table-wrapper">
|
||||||
|
<table class="hvac-leads-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($submissions as $submission) : ?>
|
||||||
|
<tr class="hvac-lead-row" data-lead-id="<?php echo esc_attr($submission->id); ?>">
|
||||||
|
<td class="hvac-lead-date">
|
||||||
|
<?php echo esc_html(date('M j, Y', strtotime($submission->submission_date))); ?>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-name">
|
||||||
|
<?php echo esc_html($submission->first_name . ' ' . $submission->last_name); ?>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-email">
|
||||||
|
<a href="mailto:<?php echo esc_attr($submission->email); ?>">
|
||||||
|
<?php echo esc_html($submission->email); ?>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-phone">
|
||||||
|
<?php if ($submission->phone) : ?>
|
||||||
|
<a href="tel:<?php echo esc_attr($submission->phone); ?>">
|
||||||
|
<?php echo esc_html($submission->phone); ?>
|
||||||
|
</a>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="hvac-no-data">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-location">
|
||||||
|
<?php
|
||||||
|
$location_parts = array_filter([
|
||||||
|
$submission->city,
|
||||||
|
$submission->state_province
|
||||||
|
]);
|
||||||
|
if (!empty($location_parts)) {
|
||||||
|
echo esc_html(implode(', ', $location_parts));
|
||||||
|
} else {
|
||||||
|
echo '<span class="hvac-no-data">—</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-message">
|
||||||
|
<?php if ($submission->message) : ?>
|
||||||
|
<div class="hvac-message-preview" title="<?php echo esc_attr($submission->message); ?>">
|
||||||
|
<?php echo esc_html(wp_trim_words($submission->message, 8, '...')); ?>
|
||||||
|
</div>
|
||||||
|
<?php if (strlen($submission->message) > 50) : ?>
|
||||||
|
<button class="hvac-btn hvac-btn-small hvac-view-message"
|
||||||
|
data-message="<?php echo esc_attr($submission->message); ?>">
|
||||||
|
View Full
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="hvac-no-data">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-status">
|
||||||
|
<span class="hvac-status-badge hvac-status-<?php echo esc_attr($submission->status); ?>">
|
||||||
|
<?php echo esc_html(ucfirst($submission->status)); ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="hvac-lead-actions">
|
||||||
|
<div class="hvac-action-buttons">
|
||||||
|
<?php if ($submission->status === 'new') : ?>
|
||||||
|
<button class="hvac-btn hvac-btn-small hvac-mark-read"
|
||||||
|
data-lead-id="<?php echo esc_attr($submission->id); ?>">
|
||||||
|
Mark Read
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($submission->status !== 'replied') : ?>
|
||||||
|
<button class="hvac-btn hvac-btn-small hvac-btn-success hvac-mark-replied"
|
||||||
|
data-lead-id="<?php echo esc_attr($submission->id); ?>">
|
||||||
|
Mark Replied
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button class="hvac-btn hvac-btn-small hvac-btn-secondary hvac-archive-lead"
|
||||||
|
data-lead-id="<?php echo esc_attr($submission->id); ?>">
|
||||||
|
Archive
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="hvac-no-leads">
|
||||||
|
<div class="hvac-empty-state">
|
||||||
|
<div class="hvac-empty-icon">
|
||||||
|
<span class="dashicons dashicons-email-alt"></span>
|
||||||
|
</div>
|
||||||
|
<h3>No inbound training requests</h3>
|
||||||
|
<p>When potential clients contact you through the "Find a Trainer" directory, their messages will appear here.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="hvac-leads-cta">
|
||||||
|
<div class="hvac-cta-card">
|
||||||
|
<h3>Want more training leads?</h3>
|
||||||
|
<p>Share your profile with the world to attract more potential clients!</p>
|
||||||
|
<a href="<?php echo esc_url(home_url('/trainer/profile/')); ?>"
|
||||||
|
class="hvac-btn hvac-btn-primary hvac-btn-large">
|
||||||
|
Share your profile with the world!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Message Modal -->
|
||||||
|
<div id="hvac-message-modal" class="hvac-modal" style="display: none;">
|
||||||
|
<div class="hvac-modal-content">
|
||||||
|
<div class="hvac-modal-header">
|
||||||
|
<h4>Full Message</h4>
|
||||||
|
<button class="hvac-modal-close">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-modal-body">
|
||||||
|
<div id="hvac-full-message"></div>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-modal-footer">
|
||||||
|
<button class="hvac-btn hvac-btn-secondary hvac-modal-close">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery(document).ready(function($) {
|
||||||
|
// Handle status updates
|
||||||
|
$('.hvac-mark-read, .hvac-mark-replied, .hvac-archive-lead').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $button = $(this);
|
||||||
|
var leadId = $button.data('lead-id');
|
||||||
|
var action = '';
|
||||||
|
var newStatus = '';
|
||||||
|
|
||||||
|
if ($button.hasClass('hvac-mark-read')) {
|
||||||
|
action = 'hvac_update_lead_status';
|
||||||
|
newStatus = 'read';
|
||||||
|
} else if ($button.hasClass('hvac-mark-replied')) {
|
||||||
|
action = 'hvac_mark_lead_replied';
|
||||||
|
newStatus = 'replied';
|
||||||
|
} else if ($button.hasClass('hvac-archive-lead')) {
|
||||||
|
action = 'hvac_update_lead_status';
|
||||||
|
newStatus = 'archived';
|
||||||
|
}
|
||||||
|
|
||||||
|
$button.prop('disabled', true).text('Processing...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: hvac_ajax.url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: action,
|
||||||
|
lead_id: leadId,
|
||||||
|
status: newStatus,
|
||||||
|
nonce: hvac_ajax.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
location.reload(); // Refresh to show updated status
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (response.data.message || 'Unknown error'));
|
||||||
|
$button.prop('disabled', false).text($button.hasClass('hvac-mark-read') ? 'Mark Read' :
|
||||||
|
$button.hasClass('hvac-mark-replied') ? 'Mark Replied' : 'Archive');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Network error. Please try again.');
|
||||||
|
$button.prop('disabled', false).text($button.hasClass('hvac-mark-read') ? 'Mark Read' :
|
||||||
|
$button.hasClass('hvac-mark-replied') ? 'Mark Replied' : 'Archive');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle message modal
|
||||||
|
$('.hvac-view-message').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var message = $(this).data('message');
|
||||||
|
$('#hvac-full-message').html(message.replace(/\n/g, '<br>'));
|
||||||
|
$('#hvac-message-modal').fadeIn();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.hvac-modal-close').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#hvac-message-modal').fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal on outside click
|
||||||
|
$('#hvac-message-modal').on('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
$(this).fadeOut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hvac-training-leads-wrapper {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-page-header {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-page-header h1 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 28px;
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-page-description {
|
||||||
|
margin: 0;
|
||||||
|
color: #646970;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table th,
|
||||||
|
.hvac-leads-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2327;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table tbody tr:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-lead-date,
|
||||||
|
.hvac-lead-phone {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-lead-email a,
|
||||||
|
.hvac-lead-phone a {
|
||||||
|
color: #0073aa;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-lead-email a:hover,
|
||||||
|
.hvac-lead-phone a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-message-preview {
|
||||||
|
max-width: 200px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-no-data {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-status-badge {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-status-new {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-status-read {
|
||||||
|
background: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-status-replied {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-status-archived {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-small {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-large {
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-primary {
|
||||||
|
background: #0073aa;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-primary:hover {
|
||||||
|
background: #005a87;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-success {
|
||||||
|
background: #28a745;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-success:hover {
|
||||||
|
background: #218838;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-btn-secondary:hover {
|
||||||
|
background: #545b62;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-no-leads {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-empty-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #c3c4c7;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-empty-state h3 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #1d2327;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-empty-state p {
|
||||||
|
margin: 0;
|
||||||
|
color: #646970;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-cta {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 40px 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card h3 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card p {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card .hvac-btn-primary {
|
||||||
|
background: #fff;
|
||||||
|
color: #667eea;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card .hvac-btn-primary:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #667eea;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal styles */
|
||||||
|
.hvac-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-content {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-header {
|
||||||
|
padding: 20px 25px;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-header h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #646970;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-close:hover {
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-body {
|
||||||
|
padding: 25px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-modal-footer {
|
||||||
|
padding: 20px 25px;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.hvac-training-leads-wrapper {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table {
|
||||||
|
min-width: 800px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hvac-page-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-leads-table th,
|
||||||
|
.hvac-leads-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-action-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hvac-cta-card h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get contact submissions for a specific trainer
|
||||||
|
*/
|
||||||
|
private function get_trainer_submissions($trainer_id) {
|
||||||
|
if (!class_exists('HVAC_Contact_Submissions_Table')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
return HVAC_Contact_Submissions_Table::get_submissions([
|
||||||
|
'trainer_id' => $trainer_id,
|
||||||
|
'limit' => 100,
|
||||||
|
'orderby' => 'submission_date',
|
||||||
|
'order' => 'DESC'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler to update lead status
|
||||||
|
*/
|
||||||
|
public function ajax_update_lead_status() {
|
||||||
|
check_ajax_referer('hvac_ajax_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
|
||||||
|
wp_send_json_error(['message' => 'Unauthorized']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lead_id = intval($_POST['lead_id'] ?? 0);
|
||||||
|
$status = sanitize_text_field($_POST['status'] ?? '');
|
||||||
|
|
||||||
|
if (!$lead_id || !$status) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid parameters']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the lead belongs to the current user
|
||||||
|
if (!$this->verify_lead_ownership($lead_id, get_current_user_id())) {
|
||||||
|
wp_send_json_error(['message' => 'Access denied']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('HVAC_Contact_Submissions_Table')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = HVAC_Contact_Submissions_Table::update_status($lead_id, $status);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
wp_send_json_success(['message' => 'Status updated successfully']);
|
||||||
|
} else {
|
||||||
|
wp_send_json_error(['message' => 'Failed to update status']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler to mark lead as replied
|
||||||
|
*/
|
||||||
|
public function ajax_mark_lead_replied() {
|
||||||
|
check_ajax_referer('hvac_ajax_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
|
||||||
|
wp_send_json_error(['message' => 'Unauthorized']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lead_id = intval($_POST['lead_id'] ?? 0);
|
||||||
|
|
||||||
|
if (!$lead_id) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid lead ID']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the lead belongs to the current user
|
||||||
|
if (!$this->verify_lead_ownership($lead_id, get_current_user_id())) {
|
||||||
|
wp_send_json_error(['message' => 'Access denied']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('HVAC_Contact_Submissions_Table')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = HVAC_Contact_Submissions_Table::update_status($lead_id, 'replied');
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
wp_send_json_success(['message' => 'Lead marked as replied']);
|
||||||
|
} else {
|
||||||
|
wp_send_json_error(['message' => 'Failed to update status']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a lead belongs to the current user
|
||||||
|
*/
|
||||||
|
private function verify_lead_ownership($lead_id, $user_id) {
|
||||||
|
if (!class_exists('HVAC_Contact_Submissions_Table')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$submission = HVAC_Contact_Submissions_Table::get_submission($lead_id);
|
||||||
|
|
||||||
|
return $submission && $submission->trainer_id == $user_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the class
|
||||||
|
HVAC_Training_Leads::get_instance();
|
||||||
298
includes/class-hvac-welcome-popup.php
Normal file
298
includes/class-hvac-welcome-popup.php
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* HVAC Welcome Popup Handler
|
||||||
|
*
|
||||||
|
* Handles welcome popup display logic and user preferences
|
||||||
|
*
|
||||||
|
* @package HVAC_Community_Events
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HVAC_Welcome_Popup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User meta key for storing dismissal preference
|
||||||
|
*/
|
||||||
|
const DISMISSED_META_KEY = 'hvac_welcome_popup_dismissed';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin instance
|
||||||
|
*
|
||||||
|
* @var HVAC_Welcome_Popup
|
||||||
|
*/
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin instance
|
||||||
|
*
|
||||||
|
* @return HVAC_Welcome_Popup
|
||||||
|
*/
|
||||||
|
public static function get_instance() {
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_assets'));
|
||||||
|
add_action('wp_ajax_hvac_check_welcome_dismissed', array($this, 'ajax_check_welcome_dismissed'));
|
||||||
|
add_action('wp_ajax_hvac_dismiss_welcome_popup', array($this, 'ajax_dismiss_welcome_popup'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue popup assets on dashboard page
|
||||||
|
*/
|
||||||
|
public function enqueue_assets() {
|
||||||
|
// Only load on trainer dashboard page
|
||||||
|
if (!$this->is_dashboard_page()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only load for logged in users with trainer capabilities
|
||||||
|
if (!is_user_logged_in() || !$this->user_can_see_popup()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue CSS
|
||||||
|
wp_enqueue_style(
|
||||||
|
'hvac-welcome-popup-css',
|
||||||
|
HVAC_PLUGIN_URL . 'assets/css/hvac-welcome-popup.css',
|
||||||
|
array(),
|
||||||
|
HVAC_PLUGIN_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enqueue JavaScript
|
||||||
|
wp_enqueue_script(
|
||||||
|
'hvac-welcome-popup-js',
|
||||||
|
HVAC_PLUGIN_URL . 'assets/js/hvac-welcome-popup.js',
|
||||||
|
array('jquery'),
|
||||||
|
HVAC_PLUGIN_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Localize script
|
||||||
|
wp_localize_script('hvac-welcome-popup-js', 'hvac_welcome', array(
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('hvac_welcome_nonce')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current page is the dashboard page
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function is_dashboard_page() {
|
||||||
|
global $post, $wp;
|
||||||
|
|
||||||
|
// Check if we're on a page with the trainer dashboard shortcode
|
||||||
|
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'hvac_dashboard')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're on the trainer dashboard template
|
||||||
|
if (is_page_template('templates/page-trainer-dashboard.php')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check URL patterns
|
||||||
|
$current_url = home_url(add_query_arg(array(), $wp->request));
|
||||||
|
|
||||||
|
if (strpos($current_url, '/trainer/dashboard') !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a page with class indicating it's the trainer dashboard
|
||||||
|
if (is_a($post, 'WP_Post')) {
|
||||||
|
$page_template = get_page_template_slug($post->ID);
|
||||||
|
if ($page_template === 'templates/page-trainer-dashboard.php') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user can see the popup
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function user_can_see_popup() {
|
||||||
|
// Check basic capability first
|
||||||
|
if (!current_user_can('hvac_trainer') &&
|
||||||
|
!current_user_can('hvac_master_trainer') &&
|
||||||
|
!current_user_can('manage_options')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For admin users, always show (for testing purposes)
|
||||||
|
if (current_user_can('manage_options')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For trainers, check account status using the proper status system
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
|
||||||
|
// Use the HVAC_Trainer_Status class to get the proper account status
|
||||||
|
if (class_exists('HVAC_Trainer_Status')) {
|
||||||
|
$account_status = HVAC_Trainer_Status::get_trainer_status($user_id);
|
||||||
|
} else {
|
||||||
|
// Fallback to direct meta query if class not available
|
||||||
|
$account_status = get_user_meta($user_id, 'account_status', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logging for staging (will be removed in production)
|
||||||
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||||
|
error_log("HVAC Welcome Popup: User ID {$user_id}, Account Status: '{$account_status}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show popup for users with Active, Approved, or Inactive account status
|
||||||
|
// Users with Pending or Disabled status should not see the welcome popup
|
||||||
|
// Inactive users should see it as they're approved but just haven't been active recently
|
||||||
|
$allowed_statuses = ['approved', 'active', 'inactive'];
|
||||||
|
return in_array($account_status, $allowed_statuses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler to check if user has dismissed the popup
|
||||||
|
*/
|
||||||
|
public function ajax_check_welcome_dismissed() {
|
||||||
|
// Verify nonce
|
||||||
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_welcome_nonce')) {
|
||||||
|
wp_send_json_error(array('message' => 'Security check failed.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is logged in
|
||||||
|
if (!is_user_logged_in()) {
|
||||||
|
wp_send_json_error(array('message' => 'User not logged in.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user capabilities
|
||||||
|
if (!$this->user_can_see_popup()) {
|
||||||
|
wp_send_json_error(array('message' => 'Access denied.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
$dismissed = get_user_meta($user_id, self::DISMISSED_META_KEY, true);
|
||||||
|
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'dismissed' => (bool) $dismissed
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler to dismiss the popup permanently
|
||||||
|
*/
|
||||||
|
public function ajax_dismiss_welcome_popup() {
|
||||||
|
// Verify nonce
|
||||||
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_welcome_nonce')) {
|
||||||
|
wp_send_json_error(array('message' => 'Security check failed.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is logged in
|
||||||
|
if (!is_user_logged_in()) {
|
||||||
|
wp_send_json_error(array('message' => 'User not logged in.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user capabilities
|
||||||
|
if (!$this->user_can_see_popup()) {
|
||||||
|
wp_send_json_error(array('message' => 'Access denied.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
|
||||||
|
// Save dismissal preference
|
||||||
|
$result = update_user_meta($user_id, self::DISMISSED_META_KEY, true);
|
||||||
|
|
||||||
|
if ($result !== false) {
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'message' => 'Welcome popup dismissed successfully.'
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
wp_send_json_error(array(
|
||||||
|
'message' => 'Failed to save dismissal preference.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset dismissal for a user (useful for testing or re-onboarding)
|
||||||
|
*
|
||||||
|
* @param int $user_id User ID
|
||||||
|
* @return bool Success status
|
||||||
|
*/
|
||||||
|
public function reset_dismissal($user_id) {
|
||||||
|
if (!$user_id || !is_numeric($user_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delete_user_meta($user_id, self::DISMISSED_META_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a specific user has dismissed the popup
|
||||||
|
*
|
||||||
|
* @param int $user_id User ID
|
||||||
|
* @return bool True if dismissed, false otherwise
|
||||||
|
*/
|
||||||
|
public function is_dismissed_for_user($user_id) {
|
||||||
|
if (!$user_id || !is_numeric($user_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) get_user_meta($user_id, self::DISMISSED_META_KEY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dismissal statistics for all users
|
||||||
|
*
|
||||||
|
* @return array Statistics array
|
||||||
|
*/
|
||||||
|
public function get_dismissal_stats() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// Get total trainer users
|
||||||
|
$trainer_roles = array('hvac_trainer', 'hvac_master_trainer');
|
||||||
|
$total_trainers = 0;
|
||||||
|
|
||||||
|
foreach ($trainer_roles as $role) {
|
||||||
|
$users = get_users(array(
|
||||||
|
'role' => $role,
|
||||||
|
'count_total' => true,
|
||||||
|
'fields' => 'ID'
|
||||||
|
));
|
||||||
|
$total_trainers += count($users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get users who have dismissed the popup
|
||||||
|
$dismissed_count = $wpdb->get_var($wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = '1'",
|
||||||
|
self::DISMISSED_META_KEY
|
||||||
|
));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'total_trainers' => $total_trainers,
|
||||||
|
'dismissed_count' => (int) $dismissed_count,
|
||||||
|
'active_count' => $total_trainers - (int) $dismissed_count,
|
||||||
|
'dismissal_rate' => $total_trainers > 0 ? round(((int) $dismissed_count / $total_trainers) * 100, 2) : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the welcome popup system
|
||||||
|
HVAC_Welcome_Popup::get_instance();
|
||||||
386
scripts/cleanup-test-data.sh
Executable file
386
scripts/cleanup-test-data.sh
Executable file
|
|
@ -0,0 +1,386 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Production Pre-Deployment Cleanup Script
|
||||||
|
# Removes all test data from staging before pushing to production
|
||||||
|
# This ensures no test accounts, events, or data make it to the live site
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "🧹 PRODUCTION PRE-DEPLOYMENT CLEANUP"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Target: $UPSKILL_STAGING_IP"
|
||||||
|
echo "This will remove ALL test data from staging"
|
||||||
|
echo "before pushing to production."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirm cleanup action
|
||||||
|
echo "⚠️ WARNING: This will permanently delete:"
|
||||||
|
echo " • Test user accounts (test_trainer, JoeMedosch@gmail.com)"
|
||||||
|
echo " • Test events and all associated data"
|
||||||
|
echo " • Test attendees and check-in records"
|
||||||
|
echo " • Test certificates and certificate files"
|
||||||
|
echo " • Test venues and organizers"
|
||||||
|
echo " • Test tickets and orders"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Are you sure you want to proceed? (yes/no): " confirm
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Cleanup cancelled."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Starting test data cleanup..."
|
||||||
|
|
||||||
|
# Upload and execute comprehensive cleanup PHP script
|
||||||
|
sshpass -p "$UPSKILL_STAGING_PASS" scp -o StrictHostKeyChecking=no /dev/stdin $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP:tmp/cleanup-test-data.php << 'PHPEOF'
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Production Pre-Deployment Test Data Cleanup
|
||||||
|
*
|
||||||
|
* Removes all test data including:
|
||||||
|
* - Test user accounts
|
||||||
|
* - Test events and associated data
|
||||||
|
* - Test certificates and files
|
||||||
|
* - Test attendees and orders
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('wp-load.php');
|
||||||
|
|
||||||
|
echo "=== Starting Production Pre-Deployment Cleanup ===\n\n";
|
||||||
|
|
||||||
|
// Initialize counters
|
||||||
|
$deleted_users = 0;
|
||||||
|
$deleted_events = 0;
|
||||||
|
$deleted_attendees = 0;
|
||||||
|
$deleted_certificates = 0;
|
||||||
|
$deleted_venues = 0;
|
||||||
|
$deleted_organizers = 0;
|
||||||
|
$deleted_tickets = 0;
|
||||||
|
$deleted_files = 0;
|
||||||
|
|
||||||
|
// 1. REMOVE TEST USER ACCOUNTS
|
||||||
|
echo "🗑️ Removing test user accounts...\n";
|
||||||
|
|
||||||
|
$test_users = [
|
||||||
|
'test_trainer',
|
||||||
|
'joemedosch',
|
||||||
|
'JoeMedosch@gmail.com' // search by email
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($test_users as $identifier) {
|
||||||
|
// Try to find user by login first, then by email
|
||||||
|
$user = get_user_by('login', $identifier);
|
||||||
|
if (!$user) {
|
||||||
|
$user = get_user_by('email', $identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
echo " - Removing user: {$user->user_login} ({$user->user_email})\n";
|
||||||
|
|
||||||
|
// Remove user and reassign their content to admin (ID 1)
|
||||||
|
$admin_user = get_user_by('ID', 1);
|
||||||
|
if ($admin_user) {
|
||||||
|
wp_delete_user($user->ID, 1); // Reassign to admin
|
||||||
|
} else {
|
||||||
|
wp_delete_user($user->ID); // Delete without reassignment
|
||||||
|
}
|
||||||
|
$deleted_users++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove HVAC roles from joe@measurequick.com if they exist
|
||||||
|
$joe_mq = get_user_by('email', 'joe@measurequick.com');
|
||||||
|
if ($joe_mq) {
|
||||||
|
echo " - Removing HVAC roles from joe@measurequick.com\n";
|
||||||
|
$user = new WP_User($joe_mq->ID);
|
||||||
|
$user->remove_role('hvac_trainer');
|
||||||
|
$user->remove_role('hvac_master_trainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_users} test user accounts\n\n";
|
||||||
|
|
||||||
|
// 2. REMOVE TEST EVENTS AND ASSOCIATED DATA
|
||||||
|
echo "🗑️ Removing test events and associated data...\n";
|
||||||
|
|
||||||
|
// Get all events that might be test events (look for common test patterns)
|
||||||
|
$test_event_patterns = [
|
||||||
|
'HVAC System Diagnostics',
|
||||||
|
'Commercial Refrigeration',
|
||||||
|
'Energy Efficient HVAC',
|
||||||
|
'Advanced HVAC Troubleshooting',
|
||||||
|
'HVAC Energy Efficiency Workshop',
|
||||||
|
'Commercial Refrigeration Systems',
|
||||||
|
'Residential HVAC Installation Best Practices',
|
||||||
|
'HVAC Controls and Automation'
|
||||||
|
];
|
||||||
|
|
||||||
|
$all_events = get_posts([
|
||||||
|
'post_type' => 'tribe_events',
|
||||||
|
'post_status' => 'any',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'meta_query' => [
|
||||||
|
'relation' => 'OR',
|
||||||
|
[
|
||||||
|
'key' => '_eventbrite_event_id',
|
||||||
|
'compare' => 'NOT EXISTS'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => '_eventbrite_event_id',
|
||||||
|
'value' => '',
|
||||||
|
'compare' => '='
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($all_events as $event) {
|
||||||
|
$is_test_event = false;
|
||||||
|
|
||||||
|
// Check if event title matches test patterns
|
||||||
|
foreach ($test_event_patterns as $pattern) {
|
||||||
|
if (stripos($event->post_title, $pattern) !== false) {
|
||||||
|
$is_test_event = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if event was created recently (within last 6 months) and has test-like characteristics
|
||||||
|
$event_date = strtotime($event->post_date);
|
||||||
|
$six_months_ago = strtotime('-6 months');
|
||||||
|
|
||||||
|
if ($event_date > $six_months_ago) {
|
||||||
|
// Check for test-like content
|
||||||
|
$test_indicators = ['test', 'training center', 'example', 'dummy', 'sample'];
|
||||||
|
$event_content = strtolower($event->post_title . ' ' . $event->post_content);
|
||||||
|
|
||||||
|
foreach ($test_indicators as $indicator) {
|
||||||
|
if (strpos($event_content, $indicator) !== false) {
|
||||||
|
$is_test_event = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_test_event) {
|
||||||
|
$event_id = $event->ID;
|
||||||
|
echo " - Removing event: {$event->post_title} (ID: {$event_id})\n";
|
||||||
|
|
||||||
|
// Get associated attendees first
|
||||||
|
$attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'meta_query' => [
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tpp_event',
|
||||||
|
'value' => $event_id
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Remove attendees
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
wp_delete_post($attendee->ID, true);
|
||||||
|
$deleted_attendees++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get associated tickets
|
||||||
|
$tickets = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_tickets',
|
||||||
|
'meta_query' => [
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tpp_for_event',
|
||||||
|
'value' => $event_id
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Remove tickets
|
||||||
|
foreach ($tickets as $ticket) {
|
||||||
|
wp_delete_post($ticket->ID, true);
|
||||||
|
$deleted_tickets++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get venue ID before deleting event
|
||||||
|
$venue_id = get_post_meta($event_id, '_EventVenueID', true);
|
||||||
|
|
||||||
|
// Remove the event
|
||||||
|
wp_delete_post($event_id, true);
|
||||||
|
$deleted_events++;
|
||||||
|
|
||||||
|
// Remove associated venue if it looks like a test venue
|
||||||
|
if ($venue_id) {
|
||||||
|
$venue = get_post($venue_id);
|
||||||
|
if ($venue && $venue->post_type === 'tribe_venue') {
|
||||||
|
$test_venue_indicators = ['training center', 'test', 'example', 'demo'];
|
||||||
|
$venue_content = strtolower($venue->post_title . ' ' . $venue->post_content);
|
||||||
|
|
||||||
|
$is_test_venue = false;
|
||||||
|
foreach ($test_venue_indicators as $indicator) {
|
||||||
|
if (strpos($venue_content, $indicator) !== false) {
|
||||||
|
$is_test_venue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_test_venue) {
|
||||||
|
wp_delete_post($venue_id, true);
|
||||||
|
$deleted_venues++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_events} test events, {$deleted_attendees} attendees, {$deleted_tickets} tickets, {$deleted_venues} venues\n\n";
|
||||||
|
|
||||||
|
// 3. REMOVE TEST CERTIFICATES AND FILES
|
||||||
|
echo "🗑️ Removing test certificates and files...\n";
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Certificate_Manager')) {
|
||||||
|
global $wpdb;
|
||||||
|
$certificate_table = $wpdb->prefix . 'hvac_certificates';
|
||||||
|
|
||||||
|
// Get all certificates
|
||||||
|
$certificates = $wpdb->get_results("SELECT * FROM {$certificate_table}");
|
||||||
|
|
||||||
|
foreach ($certificates as $certificate) {
|
||||||
|
$should_delete = false;
|
||||||
|
|
||||||
|
// Check if associated event was deleted (event doesn't exist anymore)
|
||||||
|
$event_exists = get_post($certificate->event_id);
|
||||||
|
if (!$event_exists) {
|
||||||
|
$should_delete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if certificate file looks like test data
|
||||||
|
if (strpos($certificate->file_path, 'test') !== false ||
|
||||||
|
strpos($certificate->file_path, 'example') !== false ||
|
||||||
|
strpos($certificate->file_path, 'demo') !== false) {
|
||||||
|
$should_delete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($should_delete) {
|
||||||
|
// Remove certificate file
|
||||||
|
$upload_dir = wp_upload_dir();
|
||||||
|
$full_file_path = $upload_dir['basedir'] . '/' . $certificate->file_path;
|
||||||
|
|
||||||
|
if (file_exists($full_file_path)) {
|
||||||
|
unlink($full_file_path);
|
||||||
|
$deleted_files++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove certificate record
|
||||||
|
$wpdb->delete($certificate_table, ['id' => $certificate->id]);
|
||||||
|
$deleted_certificates++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_certificates} test certificates and {$deleted_files} certificate files\n\n";
|
||||||
|
|
||||||
|
// 4. REMOVE TEST ORGANIZERS
|
||||||
|
echo "🗑️ Removing test organizers...\n";
|
||||||
|
|
||||||
|
$organizers = get_posts([
|
||||||
|
'post_type' => 'tribe_organizer',
|
||||||
|
'post_status' => 'any',
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($organizers as $organizer) {
|
||||||
|
$test_organizer_indicators = ['test', 'example', 'demo', 'training', 'sample'];
|
||||||
|
$organizer_content = strtolower($organizer->post_title . ' ' . $organizer->post_content);
|
||||||
|
|
||||||
|
$is_test_organizer = false;
|
||||||
|
foreach ($test_organizer_indicators as $indicator) {
|
||||||
|
if (strpos($organizer_content, $indicator) !== false) {
|
||||||
|
$is_test_organizer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Be more conservative - only remove organizers with clear test indicators
|
||||||
|
// Don't automatically remove based on creation date for organizers
|
||||||
|
// Many legitimate training organizations may have been added recently
|
||||||
|
|
||||||
|
if ($is_test_organizer) {
|
||||||
|
wp_delete_post($organizer->ID, true);
|
||||||
|
$deleted_organizers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_organizers} test organizers\n\n";
|
||||||
|
|
||||||
|
// 5. CLEAN UP ORPHANED META DATA
|
||||||
|
echo "🗑️ Cleaning up orphaned meta data...\n";
|
||||||
|
|
||||||
|
// Clean up post meta for deleted posts
|
||||||
|
$wpdb->query("DELETE pm FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.ID IS NULL");
|
||||||
|
|
||||||
|
// Clean up user meta for deleted users
|
||||||
|
$wpdb->query("DELETE um FROM {$wpdb->usermeta} um LEFT JOIN {$wpdb->users} u ON um.user_id = u.ID WHERE u.ID IS NULL");
|
||||||
|
|
||||||
|
echo "✅ Cleaned up orphaned meta data\n\n";
|
||||||
|
|
||||||
|
// 6. FINAL CLEANUP
|
||||||
|
echo "🗑️ Final cleanup tasks...\n";
|
||||||
|
|
||||||
|
// Clear any caches
|
||||||
|
if (function_exists('wp_cache_flush')) {
|
||||||
|
wp_cache_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear object cache
|
||||||
|
if (function_exists('wp_cache_delete_group')) {
|
||||||
|
wp_cache_delete_group('posts');
|
||||||
|
wp_cache_delete_group('users');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear transients related to events
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_tribe_events_%'");
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_tribe_events_%'");
|
||||||
|
|
||||||
|
echo "✅ Final cleanup completed\n\n";
|
||||||
|
|
||||||
|
// SUMMARY
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "🎉 CLEANUP SUMMARY\n";
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "Users removed: {$deleted_users}\n";
|
||||||
|
echo "Events removed: {$deleted_events}\n";
|
||||||
|
echo "Attendees removed: {$deleted_attendees}\n";
|
||||||
|
echo "Tickets removed: {$deleted_tickets}\n";
|
||||||
|
echo "Venues removed: {$deleted_venues}\n";
|
||||||
|
echo "Organizers removed: {$deleted_organizers}\n";
|
||||||
|
echo "Certificates removed: {$deleted_certificates}\n";
|
||||||
|
echo "Certificate files removed: {$deleted_files}\n";
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "✅ Production cleanup completed successfully!\n";
|
||||||
|
echo "Staging is now clean and ready for production deployment.\n";
|
||||||
|
?>
|
||||||
|
PHPEOF
|
||||||
|
|
||||||
|
# Execute the cleanup script on the server
|
||||||
|
echo "Executing cleanup script on staging server..."
|
||||||
|
sshpass -p "$UPSKILL_STAGING_PASS" ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && php ../tmp/cleanup-test-data.php && rm ../tmp/cleanup-test-data.php"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "✅ PRODUCTION CLEANUP COMPLETED!"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
echo "The staging environment has been cleaned of all test data:"
|
||||||
|
echo "• Test user accounts removed"
|
||||||
|
echo "• Test events and associated data removed"
|
||||||
|
echo "• Test certificates and files removed"
|
||||||
|
echo "• Orphaned data cleaned up"
|
||||||
|
echo "• Caches cleared"
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Staging is now ready for production deployment!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Verify cleanup was successful"
|
||||||
|
echo "2. Run final tests on staging"
|
||||||
|
echo "3. Deploy to production when ready"
|
||||||
353
scripts/force-cleanup-test-data.sh
Executable file
353
scripts/force-cleanup-test-data.sh
Executable file
|
|
@ -0,0 +1,353 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# FORCE Production Pre-Deployment Cleanup Script
|
||||||
|
# This version bypasses the confirmation prompt for automated cleanup
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "🧹 FORCE PRODUCTION PRE-DEPLOYMENT CLEANUP"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Target: $UPSKILL_STAGING_IP"
|
||||||
|
echo "Proceeding with automatic cleanup..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Upload and execute comprehensive cleanup PHP script directly
|
||||||
|
sshpass -p "$UPSKILL_STAGING_PASS" scp -o StrictHostKeyChecking=no /dev/stdin $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP:tmp/force-cleanup-test-data.php << 'PHPEOF'
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* FORCE Production Pre-Deployment Test Data Cleanup
|
||||||
|
*
|
||||||
|
* Removes all test data including:
|
||||||
|
* - Test user accounts
|
||||||
|
* - Test events and associated data
|
||||||
|
* - Test certificates and files
|
||||||
|
* - Test attendees and orders
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('wp-load.php');
|
||||||
|
|
||||||
|
echo "=== Starting FORCE Production Pre-Deployment Cleanup ===\n\n";
|
||||||
|
|
||||||
|
// Initialize counters
|
||||||
|
$deleted_users = 0;
|
||||||
|
$deleted_events = 0;
|
||||||
|
$deleted_attendees = 0;
|
||||||
|
$deleted_certificates = 0;
|
||||||
|
$deleted_venues = 0;
|
||||||
|
$deleted_organizers = 0;
|
||||||
|
$deleted_tickets = 0;
|
||||||
|
$deleted_files = 0;
|
||||||
|
|
||||||
|
// 1. REMOVE TEST USER ACCOUNTS
|
||||||
|
echo "🗑️ Removing test user accounts...\n";
|
||||||
|
|
||||||
|
$test_users = [
|
||||||
|
'test_trainer',
|
||||||
|
'joemedosch',
|
||||||
|
'JoeMedosch@gmail.com' // search by email
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($test_users as $identifier) {
|
||||||
|
// Try to find user by login first, then by email
|
||||||
|
$user = get_user_by('login', $identifier);
|
||||||
|
if (!$user) {
|
||||||
|
$user = get_user_by('email', $identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
echo " - Removing user: {$user->user_login} ({$user->user_email})\n";
|
||||||
|
|
||||||
|
// Remove user and reassign their content to admin (ID 1)
|
||||||
|
$admin_user = get_user_by('ID', 1);
|
||||||
|
if ($admin_user) {
|
||||||
|
wp_delete_user($user->ID, 1); // Reassign to admin
|
||||||
|
} else {
|
||||||
|
wp_delete_user($user->ID); // Delete without reassignment
|
||||||
|
}
|
||||||
|
$deleted_users++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove HVAC roles from joe@measurequick.com if they exist
|
||||||
|
$joe_mq = get_user_by('email', 'joe@measurequick.com');
|
||||||
|
if ($joe_mq) {
|
||||||
|
echo " - Removing HVAC roles from joe@measurequick.com\n";
|
||||||
|
$user = new WP_User($joe_mq->ID);
|
||||||
|
$user->remove_role('hvac_trainer');
|
||||||
|
$user->remove_role('hvac_master_trainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_users} test user accounts\n\n";
|
||||||
|
|
||||||
|
// 2. REMOVE TEST EVENTS AND ASSOCIATED DATA
|
||||||
|
echo "🗑️ Removing test events and associated data...\n";
|
||||||
|
|
||||||
|
// Get all events that might be test events (look for common test patterns)
|
||||||
|
$test_event_patterns = [
|
||||||
|
'HVAC System Diagnostics',
|
||||||
|
'Commercial Refrigeration',
|
||||||
|
'Energy Efficient HVAC',
|
||||||
|
'Advanced HVAC Troubleshooting',
|
||||||
|
'HVAC Energy Efficiency Workshop',
|
||||||
|
'Commercial Refrigeration Systems',
|
||||||
|
'Residential HVAC Installation Best Practices',
|
||||||
|
'HVAC Controls and Automation',
|
||||||
|
'AUER STEEL' // Add this pattern that showed up in verification
|
||||||
|
];
|
||||||
|
|
||||||
|
$all_events = get_posts([
|
||||||
|
'post_type' => 'tribe_events',
|
||||||
|
'post_status' => 'any',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'meta_query' => [
|
||||||
|
'relation' => 'OR',
|
||||||
|
[
|
||||||
|
'key' => '_eventbrite_event_id',
|
||||||
|
'compare' => 'NOT EXISTS'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => '_eventbrite_event_id',
|
||||||
|
'value' => '',
|
||||||
|
'compare' => '='
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($all_events as $event) {
|
||||||
|
$is_test_event = false;
|
||||||
|
|
||||||
|
// Check if event title matches test patterns
|
||||||
|
foreach ($test_event_patterns as $pattern) {
|
||||||
|
if (stripos($event->post_title, $pattern) !== false) {
|
||||||
|
$is_test_event = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if event was created recently (within last 6 months) and has test-like characteristics
|
||||||
|
$event_date = strtotime($event->post_date);
|
||||||
|
$six_months_ago = strtotime('-6 months');
|
||||||
|
|
||||||
|
if ($event_date > $six_months_ago) {
|
||||||
|
// Check for test-like content
|
||||||
|
$test_indicators = ['test', 'training center', 'example', 'dummy', 'sample'];
|
||||||
|
$event_content = strtolower($event->post_title . ' ' . $event->post_content);
|
||||||
|
|
||||||
|
foreach ($test_indicators as $indicator) {
|
||||||
|
if (strpos($event_content, $indicator) !== false) {
|
||||||
|
$is_test_event = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_test_event) {
|
||||||
|
$event_id = $event->ID;
|
||||||
|
echo " - Removing event: {$event->post_title} (ID: {$event_id})\n";
|
||||||
|
|
||||||
|
// Get associated attendees first
|
||||||
|
$attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'meta_query' => [
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tpp_event',
|
||||||
|
'value' => $event_id
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Remove attendees
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
wp_delete_post($attendee->ID, true);
|
||||||
|
$deleted_attendees++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get associated tickets
|
||||||
|
$tickets = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_tickets',
|
||||||
|
'meta_query' => [
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tpp_for_event',
|
||||||
|
'value' => $event_id
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Remove tickets
|
||||||
|
foreach ($tickets as $ticket) {
|
||||||
|
wp_delete_post($ticket->ID, true);
|
||||||
|
$deleted_tickets++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get venue ID before deleting event
|
||||||
|
$venue_id = get_post_meta($event_id, '_EventVenueID', true);
|
||||||
|
|
||||||
|
// Remove the event
|
||||||
|
wp_delete_post($event_id, true);
|
||||||
|
$deleted_events++;
|
||||||
|
|
||||||
|
// Remove associated venue if it looks like a test venue
|
||||||
|
if ($venue_id) {
|
||||||
|
$venue = get_post($venue_id);
|
||||||
|
if ($venue && $venue->post_type === 'tribe_venue') {
|
||||||
|
$test_venue_indicators = ['training center', 'test', 'example', 'demo'];
|
||||||
|
$venue_content = strtolower($venue->post_title . ' ' . $venue->post_content);
|
||||||
|
|
||||||
|
$is_test_venue = false;
|
||||||
|
foreach ($test_venue_indicators as $indicator) {
|
||||||
|
if (strpos($venue_content, $indicator) !== false) {
|
||||||
|
$is_test_venue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_test_venue) {
|
||||||
|
wp_delete_post($venue_id, true);
|
||||||
|
$deleted_venues++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_events} test events, {$deleted_attendees} attendees, {$deleted_tickets} tickets, {$deleted_venues} venues\n\n";
|
||||||
|
|
||||||
|
// 3. REMOVE TEST CERTIFICATES AND FILES
|
||||||
|
echo "🗑️ Removing test certificates and files...\n";
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Certificate_Manager')) {
|
||||||
|
global $wpdb;
|
||||||
|
$certificate_table = $wpdb->prefix . 'hvac_certificates';
|
||||||
|
|
||||||
|
// Get all certificates
|
||||||
|
$certificates = $wpdb->get_results("SELECT * FROM {$certificate_table}");
|
||||||
|
|
||||||
|
foreach ($certificates as $certificate) {
|
||||||
|
$should_delete = false;
|
||||||
|
|
||||||
|
// Check if associated event was deleted (event doesn't exist anymore)
|
||||||
|
$event_exists = get_post($certificate->event_id);
|
||||||
|
if (!$event_exists) {
|
||||||
|
$should_delete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if certificate file looks like test data
|
||||||
|
if (strpos($certificate->file_path, 'test') !== false ||
|
||||||
|
strpos($certificate->file_path, 'example') !== false ||
|
||||||
|
strpos($certificate->file_path, 'demo') !== false) {
|
||||||
|
$should_delete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($should_delete) {
|
||||||
|
// Remove certificate file
|
||||||
|
$upload_dir = wp_upload_dir();
|
||||||
|
$full_file_path = $upload_dir['basedir'] . '/' . $certificate->file_path;
|
||||||
|
|
||||||
|
if (file_exists($full_file_path)) {
|
||||||
|
unlink($full_file_path);
|
||||||
|
$deleted_files++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove certificate record
|
||||||
|
$wpdb->delete($certificate_table, ['id' => $certificate->id]);
|
||||||
|
$deleted_certificates++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_certificates} test certificates and {$deleted_files} certificate files\n\n";
|
||||||
|
|
||||||
|
// 4. REMOVE CLEAR TEST ORGANIZERS ONLY
|
||||||
|
echo "🗑️ Removing clear test organizers...\n";
|
||||||
|
|
||||||
|
$organizers = get_posts([
|
||||||
|
'post_type' => 'tribe_organizer',
|
||||||
|
'post_status' => 'any',
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($organizers as $organizer) {
|
||||||
|
// Only remove organizers with VERY clear test indicators
|
||||||
|
$clear_test_indicators = ['test', 'example', 'demo', 'sample', 'bentest'];
|
||||||
|
$organizer_content = strtolower($organizer->post_title . ' ' . $organizer->post_content);
|
||||||
|
|
||||||
|
$is_clear_test_organizer = false;
|
||||||
|
foreach ($clear_test_indicators as $indicator) {
|
||||||
|
if (strpos($organizer_content, $indicator) !== false) {
|
||||||
|
echo " - Removing test organizer: {$organizer->post_title} (ID: {$organizer->ID})\n";
|
||||||
|
$is_clear_test_organizer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_clear_test_organizer) {
|
||||||
|
wp_delete_post($organizer->ID, true);
|
||||||
|
$deleted_organizers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Removed {$deleted_organizers} clear test organizers\n\n";
|
||||||
|
|
||||||
|
// 5. CLEAN UP ORPHANED META DATA
|
||||||
|
echo "🗑️ Cleaning up orphaned meta data...\n";
|
||||||
|
|
||||||
|
// Clean up post meta for deleted posts
|
||||||
|
$wpdb->query("DELETE pm FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.ID IS NULL");
|
||||||
|
|
||||||
|
// Clean up user meta for deleted users
|
||||||
|
$wpdb->query("DELETE um FROM {$wpdb->usermeta} um LEFT JOIN {$wpdb->users} u ON um.user_id = u.ID WHERE u.ID IS NULL");
|
||||||
|
|
||||||
|
echo "✅ Cleaned up orphaned meta data\n\n";
|
||||||
|
|
||||||
|
// 6. FINAL CLEANUP
|
||||||
|
echo "🗑️ Final cleanup tasks...\n";
|
||||||
|
|
||||||
|
// Clear any caches
|
||||||
|
if (function_exists('wp_cache_flush')) {
|
||||||
|
wp_cache_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear object cache
|
||||||
|
if (function_exists('wp_cache_delete_group')) {
|
||||||
|
wp_cache_delete_group('posts');
|
||||||
|
wp_cache_delete_group('users');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear transients related to events
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_tribe_events_%'");
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_tribe_events_%'");
|
||||||
|
|
||||||
|
echo "✅ Final cleanup completed\n\n";
|
||||||
|
|
||||||
|
// SUMMARY
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "🎉 CLEANUP SUMMARY\n";
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "Users removed: {$deleted_users}\n";
|
||||||
|
echo "Events removed: {$deleted_events}\n";
|
||||||
|
echo "Attendees removed: {$deleted_attendees}\n";
|
||||||
|
echo "Tickets removed: {$deleted_tickets}\n";
|
||||||
|
echo "Venues removed: {$deleted_venues}\n";
|
||||||
|
echo "Organizers removed: {$deleted_organizers}\n";
|
||||||
|
echo "Certificates removed: {$deleted_certificates}\n";
|
||||||
|
echo "Certificate files removed: {$deleted_files}\n";
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "✅ Production cleanup completed successfully!\n";
|
||||||
|
echo "Staging is now clean and ready for production deployment.\n";
|
||||||
|
?>
|
||||||
|
PHPEOF
|
||||||
|
|
||||||
|
# Execute the cleanup script on the server
|
||||||
|
echo "🚀 Executing force cleanup script on staging server..."
|
||||||
|
sshpass -p "$UPSKILL_STAGING_PASS" ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && php ../tmp/force-cleanup-test-data.php && rm ../tmp/force-cleanup-test-data.php"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "✅ FORCE PRODUCTION CLEANUP COMPLETED!"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
echo "The staging environment has been cleaned of all test data."
|
||||||
|
echo "🚀 Staging is now ready for production deployment!"
|
||||||
203
scripts/manual-cleanup.sh
Executable file
203
scripts/manual-cleanup.sh
Executable file
|
|
@ -0,0 +1,203 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Manual Test Data Cleanup Script
|
||||||
|
# Uses the same method as deploy-to-staging.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
if [ -f .env ]; then
|
||||||
|
export $(cat .env | sed 's/#.*//g' | xargs)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required variables
|
||||||
|
if [ -z "$UPSKILL_STAGING_IP" ] || [ -z "$UPSKILL_STAGING_SSH_USER" ]; then
|
||||||
|
echo "❌ Missing required environment variables"
|
||||||
|
echo "Required: UPSKILL_STAGING_IP, UPSKILL_STAGING_SSH_USER"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
STAGING_HOST="$UPSKILL_STAGING_IP"
|
||||||
|
STAGING_USER="$UPSKILL_STAGING_SSH_USER"
|
||||||
|
STAGING_PATH="$UPSKILL_STAGING_PATH"
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "🧹 MANUAL TEST DATA CLEANUP"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Target: $STAGING_HOST"
|
||||||
|
echo "User: $STAGING_USER"
|
||||||
|
echo "Path: $STAGING_PATH"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create cleanup PHP script locally
|
||||||
|
cat > cleanup-manual.php << 'EOF'
|
||||||
|
<?php
|
||||||
|
require_once('wp-load.php');
|
||||||
|
|
||||||
|
echo "=== Manual Cleanup Starting ===\n";
|
||||||
|
|
||||||
|
$deleted_items = 0;
|
||||||
|
|
||||||
|
// 1. Remove test users by login
|
||||||
|
echo "Removing test users...\n";
|
||||||
|
$test_users = ['test_trainer', 'joemedosch'];
|
||||||
|
foreach ($test_users as $username) {
|
||||||
|
$user = get_user_by('login', $username);
|
||||||
|
if ($user) {
|
||||||
|
echo " - Removing user: {$username} ({$user->user_email})\n";
|
||||||
|
wp_delete_user($user->ID, 1); // Reassign content to admin
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove JoeMedosch by email
|
||||||
|
$joe_user = get_user_by('email', 'JoeMedosch@gmail.com');
|
||||||
|
if ($joe_user) {
|
||||||
|
echo " - Removing JoeMedosch@gmail.com\n";
|
||||||
|
wp_delete_user($joe_user->ID, 1);
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove HVAC roles from joe@measurequick.com
|
||||||
|
$joe_mq = get_user_by('email', 'joe@measurequick.com');
|
||||||
|
if ($joe_mq) {
|
||||||
|
$user = new WP_User($joe_mq->ID);
|
||||||
|
$had_roles = false;
|
||||||
|
if (in_array('hvac_trainer', $user->roles)) {
|
||||||
|
$user->remove_role('hvac_trainer');
|
||||||
|
$had_roles = true;
|
||||||
|
}
|
||||||
|
if (in_array('hvac_master_trainer', $user->roles)) {
|
||||||
|
$user->remove_role('hvac_master_trainer');
|
||||||
|
$had_roles = true;
|
||||||
|
}
|
||||||
|
if ($had_roles) {
|
||||||
|
echo " - Removed HVAC roles from joe@measurequick.com\n";
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Remove events with specific test patterns
|
||||||
|
echo "Removing test events...\n";
|
||||||
|
$test_patterns = [
|
||||||
|
'HVAC System Diagnostics',
|
||||||
|
'Commercial Refrigeration',
|
||||||
|
'Energy Efficient HVAC',
|
||||||
|
'Advanced HVAC Troubleshooting',
|
||||||
|
'HVAC Energy Efficiency Workshop',
|
||||||
|
'Commercial Refrigeration Systems',
|
||||||
|
'Residential HVAC Installation Best Practices',
|
||||||
|
'HVAC Controls and Automation',
|
||||||
|
'AUER STEEL'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($test_patterns as $pattern) {
|
||||||
|
$events = get_posts([
|
||||||
|
'post_type' => 'tribe_events',
|
||||||
|
's' => $pattern,
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'post_status' => 'any'
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($events as $event) {
|
||||||
|
echo " - Removing event: {$event->post_title} (ID: {$event->ID})\n";
|
||||||
|
|
||||||
|
// Remove associated data first
|
||||||
|
$attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'meta_query' => [['key' => '_tribe_tpp_event', 'value' => $event->ID]],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
wp_delete_post($attendee->ID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tickets = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_tickets',
|
||||||
|
'meta_query' => [['key' => '_tribe_tpp_for_event', 'value' => $event->ID]],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
foreach ($tickets as $ticket) {
|
||||||
|
wp_delete_post($ticket->ID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_delete_post($event->ID, true);
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Remove attendees with @example.com emails
|
||||||
|
echo "Removing test attendees...\n";
|
||||||
|
$attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'meta_query' => [['key' => '_tribe_tickets_email', 'value' => '@example.com', 'compare' => 'LIKE']],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
$email = get_post_meta($attendee->ID, '_tribe_tickets_email', true);
|
||||||
|
echo " - Removing test attendee: {$attendee->post_title} ({$email})\n";
|
||||||
|
wp_delete_post($attendee->ID, true);
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Remove clear test organizers and venues
|
||||||
|
echo "Removing test organizers and venues...\n";
|
||||||
|
$test_organizers = get_posts([
|
||||||
|
'post_type' => 'tribe_organizer',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'post_status' => 'any'
|
||||||
|
]);
|
||||||
|
foreach ($test_organizers as $organizer) {
|
||||||
|
$title_lower = strtolower($organizer->post_title);
|
||||||
|
if (strpos($title_lower, 'test') !== false || strpos($title_lower, 'bentest') !== false) {
|
||||||
|
echo " - Removing test organizer: {$organizer->post_title}\n";
|
||||||
|
wp_delete_post($organizer->ID, true);
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$test_venues = get_posts([
|
||||||
|
'post_type' => 'tribe_venue',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'post_status' => 'any'
|
||||||
|
]);
|
||||||
|
foreach ($test_venues as $venue) {
|
||||||
|
$title_lower = strtolower($venue->post_title);
|
||||||
|
if (strpos($title_lower, 'training center') !== false || strpos($title_lower, 'test') !== false) {
|
||||||
|
echo " - Removing test venue: {$venue->post_title}\n";
|
||||||
|
wp_delete_post($venue->ID, true);
|
||||||
|
$deleted_items++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear caches
|
||||||
|
wp_cache_flush();
|
||||||
|
|
||||||
|
echo "=== Manual Cleanup Complete ===\n";
|
||||||
|
echo "Total items removed: {$deleted_items}\n";
|
||||||
|
?>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Try to upload and execute without sshpass first (key-based auth)
|
||||||
|
echo "📤 Attempting to upload cleanup script..."
|
||||||
|
if scp -o StrictHostKeyChecking=no cleanup-manual.php $STAGING_USER@$STAGING_HOST:tmp/ 2>/dev/null; then
|
||||||
|
echo "✅ Upload successful (key-based auth)"
|
||||||
|
echo "🧹 Executing cleanup..."
|
||||||
|
ssh -o StrictHostKeyChecking=no $STAGING_USER@$STAGING_HOST "cd $STAGING_PATH && php ../tmp/cleanup-manual.php && rm ../tmp/cleanup-manual.php"
|
||||||
|
else
|
||||||
|
echo "❌ Key-based auth failed. Please run cleanup manually:"
|
||||||
|
echo ""
|
||||||
|
echo "1. Copy this content to a file on the server:"
|
||||||
|
echo " File: cleanup-manual.php"
|
||||||
|
echo ""
|
||||||
|
cat cleanup-manual.php
|
||||||
|
echo ""
|
||||||
|
echo "2. Run: cd $STAGING_PATH && php cleanup-manual.php"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up local file
|
||||||
|
rm cleanup-manual.php
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Manual cleanup process completed!"
|
||||||
130
scripts/simple-cleanup.sh
Executable file
130
scripts/simple-cleanup.sh
Executable file
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple Test Data Cleanup via SSH
|
||||||
|
# Uses WordPress CLI commands to clean up test data
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "🧹 SIMPLE TEST DATA CLEANUP"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Target: $UPSKILL_STAGING_IP"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create a simple PHP cleanup script
|
||||||
|
cat > /tmp/cleanup-test-data.php << 'EOF'
|
||||||
|
<?php
|
||||||
|
require_once('wp-load.php');
|
||||||
|
|
||||||
|
echo "=== Simple Cleanup Starting ===\n";
|
||||||
|
|
||||||
|
// 1. Remove test users
|
||||||
|
$test_users = ['test_trainer', 'joemedosch'];
|
||||||
|
foreach ($test_users as $username) {
|
||||||
|
$user = get_user_by('login', $username);
|
||||||
|
if ($user) {
|
||||||
|
echo "Removing user: {$username}\n";
|
||||||
|
wp_delete_user($user->ID, 1); // Reassign content to admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove JoeMedosch by email
|
||||||
|
$joe_user = get_user_by('email', 'JoeMedosch@gmail.com');
|
||||||
|
if ($joe_user) {
|
||||||
|
echo "Removing JoeMedosch@gmail.com\n";
|
||||||
|
wp_delete_user($joe_user->ID, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove HVAC roles from joe@measurequick.com
|
||||||
|
$joe_mq = get_user_by('email', 'joe@measurequick.com');
|
||||||
|
if ($joe_mq) {
|
||||||
|
echo "Removing HVAC roles from joe@measurequick.com\n";
|
||||||
|
$user = new WP_User($joe_mq->ID);
|
||||||
|
$user->remove_role('hvac_trainer');
|
||||||
|
$user->remove_role('hvac_master_trainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Remove test events by title patterns
|
||||||
|
$test_patterns = ['HVAC System Diagnostics', 'Commercial Refrigeration', 'Energy Efficient HVAC', 'AUER STEEL'];
|
||||||
|
foreach ($test_patterns as $pattern) {
|
||||||
|
$events = get_posts([
|
||||||
|
'post_type' => 'tribe_events',
|
||||||
|
's' => $pattern,
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($events as $event) {
|
||||||
|
echo "Removing event: {$event->post_title}\n";
|
||||||
|
|
||||||
|
// Remove associated attendees
|
||||||
|
$attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'meta_query' => [['key' => '_tribe_tpp_event', 'value' => $event->ID]],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
wp_delete_post($attendee->ID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove associated tickets
|
||||||
|
$tickets = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_tickets',
|
||||||
|
'meta_query' => [['key' => '_tribe_tpp_for_event', 'value' => $event->ID]],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
foreach ($tickets as $ticket) {
|
||||||
|
wp_delete_post($ticket->ID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_delete_post($event->ID, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Remove test attendees with @example.com emails
|
||||||
|
$attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'meta_query' => [['key' => '_tribe_tickets_email', 'value' => '@example.com', 'compare' => 'LIKE']],
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
echo "Removing test attendee: {$attendee->post_title}\n";
|
||||||
|
wp_delete_post($attendee->ID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Remove obvious test organizers
|
||||||
|
$organizers = get_posts(['post_type' => 'tribe_organizer', 'posts_per_page' => -1]);
|
||||||
|
foreach ($organizers as $organizer) {
|
||||||
|
if (stripos($organizer->post_title, 'test') !== false ||
|
||||||
|
stripos($organizer->post_title, 'bentest') !== false) {
|
||||||
|
echo "Removing test organizer: {$organizer->post_title}\n";
|
||||||
|
wp_delete_post($organizer->ID, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Remove test venues
|
||||||
|
$venues = get_posts(['post_type' => 'tribe_venue', 'posts_per_page' => -1]);
|
||||||
|
foreach ($venues as $venue) {
|
||||||
|
if (stripos($venue->post_title, 'training center') !== false ||
|
||||||
|
stripos($venue->post_title, 'test') !== false) {
|
||||||
|
echo "Removing test venue: {$venue->post_title}\n";
|
||||||
|
wp_delete_post($venue->ID, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_cache_flush();
|
||||||
|
echo "=== Simple Cleanup Complete ===\n";
|
||||||
|
?>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Upload and execute the cleanup script
|
||||||
|
echo "📤 Uploading cleanup script..."
|
||||||
|
scp /tmp/cleanup-test-data.php $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP:~/
|
||||||
|
|
||||||
|
echo "🧹 Executing cleanup..."
|
||||||
|
ssh $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && php ~/cleanup-test-data.php && rm ~/cleanup-test-data.php"
|
||||||
|
|
||||||
|
# Clean up local temp file
|
||||||
|
rm /tmp/cleanup-test-data.php
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Simple cleanup completed!"
|
||||||
295
scripts/verify-test-data.sh
Executable file
295
scripts/verify-test-data.sh
Executable file
|
|
@ -0,0 +1,295 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test Data Verification Script
|
||||||
|
# Checks what test data exists in staging environment
|
||||||
|
# Use this before/after cleanup to verify the process
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "🔍 TEST DATA VERIFICATION"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Target: $UPSKILL_STAGING_IP"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Upload and execute verification PHP script
|
||||||
|
sshpass -p "$UPSKILL_STAGING_PASS" scp -o StrictHostKeyChecking=no /dev/stdin $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP:tmp/verify-test-data.php << 'PHPEOF'
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Test Data Verification Script
|
||||||
|
*
|
||||||
|
* Checks for presence of test data in the system
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('wp-load.php');
|
||||||
|
|
||||||
|
echo "=== Test Data Verification Report ===\n\n";
|
||||||
|
|
||||||
|
// 1. CHECK TEST USERS
|
||||||
|
echo "👥 TEST USERS:\n";
|
||||||
|
echo "================\n";
|
||||||
|
|
||||||
|
$test_users = [
|
||||||
|
'test_trainer' => 'login',
|
||||||
|
'joemedosch' => 'login',
|
||||||
|
'JoeMedosch@gmail.com' => 'email'
|
||||||
|
];
|
||||||
|
|
||||||
|
$found_users = 0;
|
||||||
|
foreach ($test_users as $identifier => $type) {
|
||||||
|
$user = ($type === 'email') ? get_user_by('email', $identifier) : get_user_by('login', $identifier);
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
echo "✅ Found: {$user->user_login} ({$user->user_email}) - Roles: " . implode(', ', $user->roles) . "\n";
|
||||||
|
$found_users++;
|
||||||
|
} else {
|
||||||
|
echo "❌ Not found: {$identifier}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check joe@measurequick.com for HVAC roles
|
||||||
|
$joe_mq = get_user_by('email', 'joe@measurequick.com');
|
||||||
|
if ($joe_mq) {
|
||||||
|
$hvac_roles = array_intersect(['hvac_trainer', 'hvac_master_trainer'], $joe_mq->roles);
|
||||||
|
if (!empty($hvac_roles)) {
|
||||||
|
echo "⚠️ joe@measurequick.com has HVAC roles: " . implode(', ', $hvac_roles) . "\n";
|
||||||
|
$found_users++;
|
||||||
|
} else {
|
||||||
|
echo "✅ joe@measurequick.com has no HVAC roles\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "❌ joe@measurequick.com not found\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\nTotal test users found: {$found_users}\n\n";
|
||||||
|
|
||||||
|
// 2. CHECK TEST EVENTS
|
||||||
|
echo "📅 TEST EVENTS:\n";
|
||||||
|
echo "================\n";
|
||||||
|
|
||||||
|
$test_event_patterns = [
|
||||||
|
'HVAC System Diagnostics',
|
||||||
|
'Commercial Refrigeration',
|
||||||
|
'Energy Efficient HVAC',
|
||||||
|
'Advanced HVAC Troubleshooting',
|
||||||
|
'HVAC Energy Efficiency Workshop',
|
||||||
|
'Commercial Refrigeration Systems',
|
||||||
|
'Residential HVAC Installation Best Practices',
|
||||||
|
'HVAC Controls and Automation'
|
||||||
|
];
|
||||||
|
|
||||||
|
$all_events = get_posts([
|
||||||
|
'post_type' => 'tribe_events',
|
||||||
|
'post_status' => 'any',
|
||||||
|
'posts_per_page' => -1
|
||||||
|
]);
|
||||||
|
|
||||||
|
$found_test_events = 0;
|
||||||
|
$recent_events = 0;
|
||||||
|
$six_months_ago = strtotime('-6 months');
|
||||||
|
|
||||||
|
foreach ($all_events as $event) {
|
||||||
|
$is_test_event = false;
|
||||||
|
|
||||||
|
// Check against known test patterns
|
||||||
|
foreach ($test_event_patterns as $pattern) {
|
||||||
|
if (stripos($event->post_title, $pattern) !== false) {
|
||||||
|
$is_test_event = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for test-like indicators
|
||||||
|
$test_indicators = ['test', 'training center', 'example', 'dummy', 'sample'];
|
||||||
|
$event_content = strtolower($event->post_title . ' ' . $event->post_content);
|
||||||
|
|
||||||
|
foreach ($test_indicators as $indicator) {
|
||||||
|
if (strpos($event_content, $indicator) !== false) {
|
||||||
|
$is_test_event = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_test_event) {
|
||||||
|
$event_date = date('Y-m-d', strtotime($event->post_date));
|
||||||
|
echo "⚠️ Test Event: '{$event->post_title}' (ID: {$event->ID}, Created: {$event_date})\n";
|
||||||
|
$found_test_events++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count recent events (created in last 6 months)
|
||||||
|
if (strtotime($event->post_date) > $six_months_ago) {
|
||||||
|
$recent_events++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\nTotal test events found: {$found_test_events}\n";
|
||||||
|
echo "Total recent events (last 6 months): {$recent_events}\n\n";
|
||||||
|
|
||||||
|
// 3. CHECK TEST ATTENDEES
|
||||||
|
echo "🎟️ TEST ATTENDEES:\n";
|
||||||
|
echo "===================\n";
|
||||||
|
|
||||||
|
$test_attendees = get_posts([
|
||||||
|
'post_type' => 'tribe_tpp_attendees',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'meta_query' => [
|
||||||
|
'relation' => 'OR',
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tickets_email',
|
||||||
|
'value' => '@example.com',
|
||||||
|
'compare' => 'LIKE'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tickets_email',
|
||||||
|
'value' => 'ben@tealmaker.com',
|
||||||
|
'compare' => '='
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => '_tribe_tickets_full_name',
|
||||||
|
'value' => 'Test',
|
||||||
|
'compare' => 'LIKE'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "Test attendees found: " . count($test_attendees) . "\n";
|
||||||
|
|
||||||
|
if (count($test_attendees) > 0) {
|
||||||
|
echo "Sample test attendees:\n";
|
||||||
|
foreach (array_slice($test_attendees, 0, 5) as $attendee) {
|
||||||
|
$name = get_post_meta($attendee->ID, '_tribe_tickets_full_name', true);
|
||||||
|
$email = get_post_meta($attendee->ID, '_tribe_tickets_email', true);
|
||||||
|
$event_id = get_post_meta($attendee->ID, '_tribe_tpp_event', true);
|
||||||
|
$event = get_post($event_id);
|
||||||
|
$event_title = $event ? $event->post_title : 'Unknown Event';
|
||||||
|
|
||||||
|
echo " - {$name} ({$email}) for '{$event_title}'\n";
|
||||||
|
}
|
||||||
|
if (count($test_attendees) > 5) {
|
||||||
|
echo " ... and " . (count($test_attendees) - 5) . " more\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "\n";
|
||||||
|
|
||||||
|
// 4. CHECK TEST CERTIFICATES
|
||||||
|
echo "🏆 TEST CERTIFICATES:\n";
|
||||||
|
echo "=====================\n";
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Certificate_Manager')) {
|
||||||
|
global $wpdb;
|
||||||
|
$certificate_table = $wpdb->prefix . 'hvac_certificates';
|
||||||
|
|
||||||
|
if ($wpdb->get_var("SHOW TABLES LIKE '$certificate_table'") === $certificate_table) {
|
||||||
|
$total_certificates = $wpdb->get_var("SELECT COUNT(*) FROM {$certificate_table}");
|
||||||
|
echo "Total certificates in database: {$total_certificates}\n";
|
||||||
|
|
||||||
|
// Check for test certificate files
|
||||||
|
$test_certificates = $wpdb->get_results("
|
||||||
|
SELECT * FROM {$certificate_table}
|
||||||
|
WHERE file_path LIKE '%test%'
|
||||||
|
OR file_path LIKE '%example%'
|
||||||
|
OR file_path LIKE '%demo%'
|
||||||
|
");
|
||||||
|
|
||||||
|
echo "Test certificates found: " . count($test_certificates) . "\n";
|
||||||
|
|
||||||
|
// Check for orphaned certificates (event doesn't exist)
|
||||||
|
$orphaned_certificates = $wpdb->get_results("
|
||||||
|
SELECT c.* FROM {$certificate_table} c
|
||||||
|
LEFT JOIN {$wpdb->posts} p ON c.event_id = p.ID
|
||||||
|
WHERE p.ID IS NULL
|
||||||
|
");
|
||||||
|
|
||||||
|
echo "Orphaned certificates (event deleted): " . count($orphaned_certificates) . "\n";
|
||||||
|
} else {
|
||||||
|
echo "Certificate table does not exist\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "HVAC Certificate Manager not available\n";
|
||||||
|
}
|
||||||
|
echo "\n";
|
||||||
|
|
||||||
|
// 5. CHECK TEST VENUES AND ORGANIZERS
|
||||||
|
echo "🏢 TEST VENUES & ORGANIZERS:\n";
|
||||||
|
echo "=============================\n";
|
||||||
|
|
||||||
|
$test_venues = get_posts([
|
||||||
|
'post_type' => 'tribe_venue',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$test_venue_count = 0;
|
||||||
|
foreach ($test_venues as $venue) {
|
||||||
|
$test_indicators = ['training center', 'test', 'example', 'demo'];
|
||||||
|
$venue_content = strtolower($venue->post_title . ' ' . $venue->post_content);
|
||||||
|
|
||||||
|
foreach ($test_indicators as $indicator) {
|
||||||
|
if (strpos($venue_content, $indicator) !== false) {
|
||||||
|
echo "⚠️ Test Venue: '{$venue->post_title}' (ID: {$venue->ID})\n";
|
||||||
|
$test_venue_count++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$test_organizers = get_posts([
|
||||||
|
'post_type' => 'tribe_organizer',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$test_organizer_count = 0;
|
||||||
|
foreach ($test_organizers as $organizer) {
|
||||||
|
$test_indicators = ['test', 'example', 'demo', 'training', 'sample'];
|
||||||
|
$organizer_content = strtolower($organizer->post_title . ' ' . $organizer->post_content);
|
||||||
|
|
||||||
|
foreach ($test_indicators as $indicator) {
|
||||||
|
if (strpos($organizer_content, $indicator) !== false) {
|
||||||
|
echo "⚠️ Test Organizer: '{$organizer->post_title}' (ID: {$organizer->ID})\n";
|
||||||
|
$test_organizer_count++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Test venues found: {$test_venue_count}\n";
|
||||||
|
echo "Test organizers found: {$test_organizer_count}\n\n";
|
||||||
|
|
||||||
|
// SUMMARY
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "📊 VERIFICATION SUMMARY\n";
|
||||||
|
echo "========================================\n";
|
||||||
|
echo "Test users: {$found_users}\n";
|
||||||
|
echo "Test events: {$found_test_events}\n";
|
||||||
|
echo "Test attendees: " . count($test_attendees) . "\n";
|
||||||
|
echo "Test venues: {$test_venue_count}\n";
|
||||||
|
echo "Test organizers: {$test_organizer_count}\n";
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Certificate_Manager') && isset($test_certificates)) {
|
||||||
|
echo "Test certificates: " . count($test_certificates) . "\n";
|
||||||
|
echo "Orphaned certificates: " . count($orphaned_certificates) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_test_items = $found_users + $found_test_events + count($test_attendees) + $test_venue_count + $test_organizer_count;
|
||||||
|
if (isset($test_certificates)) {
|
||||||
|
$total_test_items += count($test_certificates);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "========================================\n";
|
||||||
|
|
||||||
|
if ($total_test_items > 0) {
|
||||||
|
echo "⚠️ TEST DATA FOUND: {$total_test_items} items need cleanup\n";
|
||||||
|
echo "Run scripts/cleanup-test-data.sh to clean up before production.\n";
|
||||||
|
} else {
|
||||||
|
echo "✅ NO TEST DATA FOUND: Ready for production deployment!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "========================================\n";
|
||||||
|
?>
|
||||||
|
PHPEOF
|
||||||
|
|
||||||
|
# Execute the verification script on the server
|
||||||
|
echo "Executing verification script on staging server..."
|
||||||
|
sshpass -p "$UPSKILL_STAGING_PASS" ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && php ../tmp/verify-test-data.php && rm ../tmp/verify-test-data.php"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Test data verification completed!"
|
||||||
152
scripts/wp-cli-cleanup.sh
Executable file
152
scripts/wp-cli-cleanup.sh
Executable file
|
|
@ -0,0 +1,152 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# WP-CLI Test Data Cleanup Script
|
||||||
|
# Uses WordPress CLI commands to clean up test data safely
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "🧹 WP-CLI TEST DATA CLEANUP"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Target: $UPSKILL_STAGING_IP"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to run WP-CLI commands on staging
|
||||||
|
run_wp_cli() {
|
||||||
|
local cmd="$1"
|
||||||
|
echo "🔧 Running: wp $cmd"
|
||||||
|
ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && wp $cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "🚀 Starting WP-CLI cleanup process..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Remove test users
|
||||||
|
echo "👥 REMOVING TEST USERS"
|
||||||
|
echo "======================="
|
||||||
|
|
||||||
|
# Remove test_trainer user
|
||||||
|
echo "Removing test_trainer user..."
|
||||||
|
run_wp_cli "user delete test_trainer --reassign=1 --yes"
|
||||||
|
|
||||||
|
# Remove joemedosch user
|
||||||
|
echo "Removing joemedosch user..."
|
||||||
|
run_wp_cli "user delete joemedosch --reassign=1 --yes"
|
||||||
|
|
||||||
|
# Remove user by email if username doesn't work
|
||||||
|
echo "Removing JoeMedosch@gmail.com..."
|
||||||
|
run_wp_cli "user list --field=ID --user_email=JoeMedosch@gmail.com" | while read user_id; do
|
||||||
|
if [ -n "$user_id" ]; then
|
||||||
|
run_wp_cli "user delete $user_id --reassign=1 --yes"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove HVAC roles from joe@measurequick.com
|
||||||
|
echo "Removing HVAC roles from joe@measurequick.com..."
|
||||||
|
run_wp_cli "user remove-role joe@measurequick.com hvac_trainer" || true
|
||||||
|
run_wp_cli "user remove-role joe@measurequick.com hvac_master_trainer" || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 2. Remove test events with specific patterns
|
||||||
|
echo "📅 REMOVING TEST EVENTS"
|
||||||
|
echo "======================="
|
||||||
|
|
||||||
|
# Get event IDs for known test patterns
|
||||||
|
test_patterns=("HVAC System Diagnostics" "Commercial Refrigeration" "Energy Efficient HVAC" "Advanced HVAC Troubleshooting" "HVAC Energy Efficiency Workshop" "AUER STEEL")
|
||||||
|
|
||||||
|
for pattern in "${test_patterns[@]}"; do
|
||||||
|
echo "Looking for events with pattern: $pattern"
|
||||||
|
# Get post IDs matching the pattern
|
||||||
|
ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && wp post list --post_type=tribe_events --s='$pattern' --field=ID --format=csv" | while read event_id; do
|
||||||
|
if [ -n "$event_id" ] && [ "$event_id" != "ID" ]; then
|
||||||
|
echo "Removing event ID: $event_id"
|
||||||
|
|
||||||
|
# Remove associated attendees first
|
||||||
|
run_wp_cli "post list --post_type=tribe_tpp_attendees --meta_key=_tribe_tpp_event --meta_value=$event_id --field=ID --format=csv" | while read attendee_id; do
|
||||||
|
if [ -n "$attendee_id" ] && [ "$attendee_id" != "ID" ]; then
|
||||||
|
run_wp_cli "post delete $attendee_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove associated tickets
|
||||||
|
run_wp_cli "post list --post_type=tribe_tpp_tickets --meta_key=_tribe_tpp_for_event --meta_value=$event_id --field=ID --format=csv" | while read ticket_id; do
|
||||||
|
if [ -n "$ticket_id" ] && [ "$ticket_id" != "ID" ]; then
|
||||||
|
run_wp_cli "post delete $ticket_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove the event
|
||||||
|
run_wp_cli "post delete $event_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 3. Remove test attendees with @example.com emails
|
||||||
|
echo "🎟️ REMOVING TEST ATTENDEES"
|
||||||
|
echo "==========================="
|
||||||
|
|
||||||
|
echo "Removing attendees with @example.com emails..."
|
||||||
|
ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && wp post list --post_type=tribe_tpp_attendees --meta_key=_tribe_tickets_email --meta_value='@example.com' --meta_compare=LIKE --field=ID --format=csv" | while read attendee_id; do
|
||||||
|
if [ -n "$attendee_id" ] && [ "$attendee_id" != "ID" ]; then
|
||||||
|
echo "Removing test attendee ID: $attendee_id"
|
||||||
|
run_wp_cli "post delete $attendee_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 4. Remove obvious test organizers
|
||||||
|
echo "🏢 REMOVING TEST ORGANIZERS"
|
||||||
|
echo "==========================="
|
||||||
|
|
||||||
|
# Remove organizers with title exactly "Test" or "BenTest"
|
||||||
|
echo "Removing 'Test' organizer..."
|
||||||
|
ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && wp post list --post_type=tribe_organizer --post_title='Test' --field=ID --format=csv" | while read org_id; do
|
||||||
|
if [ -n "$org_id" ] && [ "$org_id" != "ID" ]; then
|
||||||
|
echo "Removing Test organizer ID: $org_id"
|
||||||
|
run_wp_cli "post delete $org_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Removing 'BenTest' organizer..."
|
||||||
|
ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && wp post list --post_type=tribe_organizer --s='BenTest' --field=ID --format=csv" | while read org_id; do
|
||||||
|
if [ -n "$org_id" ] && [ "$org_id" != "ID" ]; then
|
||||||
|
echo "Removing BenTest organizer ID: $org_id"
|
||||||
|
run_wp_cli "post delete $org_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 5. Remove test venues
|
||||||
|
echo "🏛️ REMOVING TEST VENUES"
|
||||||
|
echo "========================"
|
||||||
|
|
||||||
|
echo "Removing 'Woolworth Training Center' venue..."
|
||||||
|
ssh -o StrictHostKeyChecking=no $UPSKILL_STAGING_SSH_USER@$UPSKILL_STAGING_IP "cd $UPSKILL_STAGING_PATH && wp post list --post_type=tribe_venue --s='Woolworth Training Center' --field=ID --format=csv" | while read venue_id; do
|
||||||
|
if [ -n "$venue_id" ] && [ "$venue_id" != "ID" ]; then
|
||||||
|
echo "Removing test venue ID: $venue_id"
|
||||||
|
run_wp_cli "post delete $venue_id --force"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 6. Clear caches
|
||||||
|
echo "🧽 CLEARING CACHES"
|
||||||
|
echo "=================="
|
||||||
|
run_wp_cli "cache flush"
|
||||||
|
run_wp_cli "breeze purge --all" || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "✅ WP-CLI CLEANUP COMPLETED!"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Run verification to see results
|
||||||
|
echo "🔍 Running verification to check results..."
|
||||||
|
./scripts/verify-test-data.sh
|
||||||
|
|
@ -1,12 +1,57 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Template Name: Documentation
|
* Template Name: Trainer Documentation
|
||||||
* Description: Template for the documentation page
|
* Template for displaying trainer documentation page
|
||||||
|
*
|
||||||
|
* @package HVAC_Plugin
|
||||||
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Define constant to identify we're in a page template
|
||||||
|
define('HVAC_IN_PAGE_TEMPLATE', true);
|
||||||
|
|
||||||
|
// Get header
|
||||||
get_header();
|
get_header();
|
||||||
|
|
||||||
// Render the documentation shortcode
|
// Initialize breadcrumbs if available
|
||||||
echo do_shortcode('[hvac_documentation]');
|
if (class_exists('HVAC_Breadcrumbs')) {
|
||||||
|
$breadcrumbs = HVAC_Breadcrumbs::get_instance();
|
||||||
|
$breadcrumbs->set_custom_breadcrumb([
|
||||||
|
['title' => 'Trainer', 'url' => home_url('/trainer/')],
|
||||||
|
['title' => 'Documentation', 'url' => '']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
get_footer();
|
?>
|
||||||
|
|
||||||
|
<div class="container hvac-trainer-page hvac-documentation-page">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Render navigation menu
|
||||||
|
if (class_exists('HVAC_Menu_System') && !defined('HVAC_NAV_RENDERED')) {
|
||||||
|
define('HVAC_NAV_RENDERED', true);
|
||||||
|
HVAC_Menu_System::instance()->render_trainer_menu();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Render breadcrumbs if available
|
||||||
|
if (isset($breadcrumbs)) {
|
||||||
|
echo $breadcrumbs->render();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="hvac-main-content">
|
||||||
|
<?php
|
||||||
|
// Render the documentation content using shortcode
|
||||||
|
if (class_exists('HVAC_Help_System')) {
|
||||||
|
echo do_shortcode('[hvac_documentation]');
|
||||||
|
} else {
|
||||||
|
echo '<p>Documentation is not available. Please contact an administrator.</p>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php get_footer(); ?>
|
||||||
|
|
@ -22,6 +22,71 @@ if (class_exists('HVAC_Trainer_Directory_Query')) {
|
||||||
$directory_query = HVAC_Trainer_Directory_Query::get_instance();
|
$directory_query = HVAC_Trainer_Directory_Query::get_instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we have a direct profile URL pattern
|
||||||
|
$qr_generator = HVAC_QR_Generator::instance();
|
||||||
|
$direct_profile_id = $qr_generator->parse_profile_id_from_url();
|
||||||
|
$show_direct_profile = false;
|
||||||
|
$direct_profile_data = null;
|
||||||
|
|
||||||
|
if ($direct_profile_id) {
|
||||||
|
// Get the specific profile data
|
||||||
|
$direct_profile_data = $qr_generator->get_trainer_share_data($direct_profile_id);
|
||||||
|
if ($direct_profile_data) {
|
||||||
|
$show_direct_profile = true;
|
||||||
|
|
||||||
|
// Get additional profile data for full display
|
||||||
|
$profile_post = get_post($direct_profile_id);
|
||||||
|
$user_id = get_post_meta($direct_profile_id, 'user_id', true);
|
||||||
|
$user = get_userdata($user_id);
|
||||||
|
|
||||||
|
// Get profile metadata
|
||||||
|
$profile_meta = [];
|
||||||
|
if ($profile_post) {
|
||||||
|
$all_meta = get_post_meta($direct_profile_id);
|
||||||
|
foreach ($all_meta as $key => $value) {
|
||||||
|
$profile_meta[$key] = is_array($value) ? $value[0] : $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get event count
|
||||||
|
$event_count = 0;
|
||||||
|
if ($user_id && function_exists('tribe_get_events')) {
|
||||||
|
$events = tribe_get_events([
|
||||||
|
'author' => $user_id,
|
||||||
|
'eventDisplay' => 'all',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'fields' => 'ids'
|
||||||
|
]);
|
||||||
|
$event_count = count($events);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get upcoming events
|
||||||
|
$upcoming_events = [];
|
||||||
|
if ($user_id && function_exists('tribe_get_events')) {
|
||||||
|
$events = tribe_get_events([
|
||||||
|
'author' => $user_id,
|
||||||
|
'eventDisplay' => 'list',
|
||||||
|
'posts_per_page' => 5,
|
||||||
|
'start_date' => 'now'
|
||||||
|
]);
|
||||||
|
foreach ($events as $event) {
|
||||||
|
$upcoming_events[] = [
|
||||||
|
'title' => $event->post_title,
|
||||||
|
'date' => tribe_get_start_date($event->ID, false, 'M j, Y'),
|
||||||
|
'url' => get_permalink($event->ID)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add additional data to the profile data array
|
||||||
|
$direct_profile_data['profile_meta'] = $profile_meta;
|
||||||
|
$direct_profile_data['user'] = $user;
|
||||||
|
$direct_profile_data['event_count'] = $event_count;
|
||||||
|
$direct_profile_data['upcoming_events'] = $upcoming_events;
|
||||||
|
$direct_profile_data['profile_content'] = $profile_post ? $profile_post->post_content : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get trainers for initial display with user status filtering
|
// Get trainers for initial display with user status filtering
|
||||||
$trainers = [];
|
$trainers = [];
|
||||||
$total_pages = 1;
|
$total_pages = 1;
|
||||||
|
|
@ -131,11 +196,26 @@ wp_enqueue_style('hvac-find-trainer', HVAC_PLUGIN_URL . 'assets/css/find-trainer
|
||||||
wp_enqueue_script('hvac-find-trainer', HVAC_PLUGIN_URL . 'assets/js/find-trainer.js', ['jquery'], HVAC_VERSION, true);
|
wp_enqueue_script('hvac-find-trainer', HVAC_PLUGIN_URL . 'assets/js/find-trainer.js', ['jquery'], HVAC_VERSION, true);
|
||||||
wp_enqueue_style('dashicons');
|
wp_enqueue_style('dashicons');
|
||||||
|
|
||||||
|
// Enqueue profile sharing assets if showing direct profile
|
||||||
|
if ($show_direct_profile) {
|
||||||
|
wp_enqueue_style('hvac-profile-sharing', HVAC_PLUGIN_URL . 'assets/css/hvac-profile-sharing.css', ['hvac-find-trainer'], HVAC_VERSION);
|
||||||
|
wp_enqueue_script('hvac-profile-sharing', HVAC_PLUGIN_URL . 'assets/js/hvac-profile-sharing.js', ['jquery', 'hvac-find-trainer'], HVAC_VERSION, true);
|
||||||
|
|
||||||
|
// Localize sharing script with nonce and AJAX URL
|
||||||
|
wp_localize_script('hvac-profile-sharing', 'hvac_sharing', [
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('hvac_profile_sharing'),
|
||||||
|
'profile_id' => $direct_profile_id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// Localize script with necessary data
|
// Localize script with necessary data
|
||||||
wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
|
wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
|
||||||
'ajax_url' => admin_url('admin-ajax.php'),
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
'nonce' => wp_create_nonce('hvac_find_trainer'),
|
'nonce' => wp_create_nonce('hvac_find_trainer'),
|
||||||
'map_id' => '5872',
|
'map_id' => '5872',
|
||||||
|
'direct_profile_id' => $direct_profile_id ?: null,
|
||||||
|
'show_direct_profile' => $show_direct_profile,
|
||||||
'messages' => [
|
'messages' => [
|
||||||
'loading' => __('Loading...', 'hvac'),
|
'loading' => __('Loading...', 'hvac'),
|
||||||
'error' => __('An error occurred. Please try again.', 'hvac'),
|
'error' => __('An error occurred. Please try again.', 'hvac'),
|
||||||
|
|
@ -152,14 +232,168 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
|
||||||
<div class="ast-container">
|
<div class="ast-container">
|
||||||
|
|
||||||
<!-- Page Title -->
|
<!-- Page Title -->
|
||||||
<h1 class="hvac-page-title">Find a Trainer</h1>
|
<h1 class="hvac-page-title"><?php echo $show_direct_profile ? 'Trainer Profile' : 'Find a Trainer'; ?></h1>
|
||||||
|
|
||||||
<!-- Container 1: Summary -->
|
<!-- Direct Profile Display -->
|
||||||
<div class="hvac-summary-container">
|
<?php if ($show_direct_profile && $direct_profile_data): ?>
|
||||||
<p>Find certified HVAC trainers in your area. Use the interactive map and filters below to discover trainers who match your specific needs. Click on any trainer to view their profile and contact them directly.</p>
|
<div class="hvac-direct-profile-container">
|
||||||
|
<div class="hvac-direct-profile-header">
|
||||||
|
<a href="<?php echo esc_url(remove_query_arg('', get_permalink())); ?>" class="hvac-back-to-directory">
|
||||||
|
<span class="dashicons dashicons-arrow-left-alt2"></span> Back to Trainer Directory
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Container 2: Map & Filters -->
|
<!-- Full Trainer Profile Display -->
|
||||||
|
<div class="hvac-trainer-profile-full">
|
||||||
|
|
||||||
|
<!-- Profile Header Section -->
|
||||||
|
<div class="hvac-trainer-profile-header">
|
||||||
|
<div class="hvac-trainer-image-section">
|
||||||
|
<?php if (!empty($direct_profile_data['profile_image'])): ?>
|
||||||
|
<img src="<?php echo esc_url($direct_profile_data['profile_image']); ?>" alt="<?php echo esc_attr($direct_profile_data['trainer_name']); ?>" class="hvac-trainer-main-image">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="hvac-trainer-avatar-large">
|
||||||
|
<span class="dashicons dashicons-businessperson"></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- mQ Certified Badge -->
|
||||||
|
<?php if ($direct_profile_data['certification_type'] === 'Certified measureQuick Trainer'): ?>
|
||||||
|
<div class="hvac-mq-badge-overlay">
|
||||||
|
<img src="/wp-content/uploads/2025/08/mQ-Certified-trainer.png" alt="measureQuick Certified Trainer" class="hvac-mq-badge">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hvac-trainer-header-info">
|
||||||
|
<h2 class="hvac-trainer-name"><?php echo esc_html($direct_profile_data['trainer_name']); ?></h2>
|
||||||
|
<p class="hvac-trainer-location">
|
||||||
|
<?php echo esc_html($direct_profile_data['trainer_city'] . ', ' . $direct_profile_data['trainer_state']); ?>
|
||||||
|
</p>
|
||||||
|
<p class="hvac-trainer-certification"><?php echo esc_html($direct_profile_data['certification_type'] ?: 'HVAC Trainer'); ?></p>
|
||||||
|
<?php if (!empty($direct_profile_data['business_name'])): ?>
|
||||||
|
<p class="hvac-trainer-business"><?php echo esc_html($direct_profile_data['business_name']); ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<p class="hvac-trainer-events-stat">Total Training Events: <strong><?php echo esc_html($direct_profile_data['event_count']); ?></strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Training Details Section -->
|
||||||
|
<div class="hvac-trainer-details-section">
|
||||||
|
<h3>Training Information</h3>
|
||||||
|
<div class="hvac-training-details-grid">
|
||||||
|
<?php
|
||||||
|
$profile_meta = $direct_profile_data['profile_meta'] ?? [];
|
||||||
|
$training_formats = $profile_meta['training_formats'] ?? 'In-Person, Virtual';
|
||||||
|
$training_locations = $profile_meta['training_locations'] ?? 'On-site, Remote';
|
||||||
|
?>
|
||||||
|
<div class="hvac-training-detail">
|
||||||
|
<strong>Training Formats:</strong>
|
||||||
|
<span><?php echo esc_html($training_formats); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-training-detail">
|
||||||
|
<strong>Training Locations:</strong>
|
||||||
|
<span><?php echo esc_html($training_locations); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($profile_meta['training_audience'])): ?>
|
||||||
|
<div class="hvac-training-detail">
|
||||||
|
<strong>Training Audience:</strong>
|
||||||
|
<span><?php echo esc_html($profile_meta['training_audience']); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($profile_meta['years_experience'])): ?>
|
||||||
|
<div class="hvac-training-detail">
|
||||||
|
<strong>Years Experience:</strong>
|
||||||
|
<span><?php echo esc_html($profile_meta['years_experience']); ?> years</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Upcoming Events Section -->
|
||||||
|
<div class="hvac-upcoming-events-section">
|
||||||
|
<h3>Upcoming Events</h3>
|
||||||
|
<?php if (!empty($direct_profile_data['upcoming_events'])): ?>
|
||||||
|
<ul class="hvac-events-list">
|
||||||
|
<?php foreach ($direct_profile_data['upcoming_events'] as $event): ?>
|
||||||
|
<li>
|
||||||
|
<a href="<?php echo esc_url($event['url']); ?>" target="_blank">
|
||||||
|
<?php echo esc_html($event['title']); ?>
|
||||||
|
</a>
|
||||||
|
- <?php echo esc_html($event['date']); ?>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php else: ?>
|
||||||
|
<p>No upcoming events scheduled</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- About Section -->
|
||||||
|
<?php if (!empty($direct_profile_data['profile_content'])): ?>
|
||||||
|
<div class="hvac-trainer-about-section">
|
||||||
|
<h3>About</h3>
|
||||||
|
<div class="hvac-trainer-bio">
|
||||||
|
<?php echo wp_kses_post(wpautop($direct_profile_data['profile_content'])); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Contact Section -->
|
||||||
|
<div class="hvac-contact-section">
|
||||||
|
<h3>Contact</h3>
|
||||||
|
<form id="hvac-direct-contact-form" class="hvac-contact-form">
|
||||||
|
<div class="hvac-form-row">
|
||||||
|
<input type="text" name="first_name" placeholder="First Name" required>
|
||||||
|
<input type="text" name="last_name" placeholder="Last Name" required>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-row">
|
||||||
|
<input type="email" name="email" placeholder="Email" required>
|
||||||
|
<input type="tel" name="phone" placeholder="Phone Number">
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-row">
|
||||||
|
<input type="text" name="city" placeholder="City">
|
||||||
|
<input type="text" name="state_province" placeholder="State/Province">
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-full">
|
||||||
|
<input type="text" name="company" placeholder="Company">
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-full">
|
||||||
|
<textarea name="message" placeholder="Message" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="trainer_id" value="<?php echo esc_attr($direct_profile_data['user_id']); ?>">
|
||||||
|
<input type="hidden" name="trainer_profile_id" value="<?php echo esc_attr($direct_profile_id); ?>">
|
||||||
|
<button type="submit" class="hvac-form-submit">Send Message</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Success/Error Messages -->
|
||||||
|
<div class="hvac-form-message hvac-form-success" style="display: none;">
|
||||||
|
Your message has been sent! Check your inbox for more details.
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-message hvac-form-error" style="display: none;">
|
||||||
|
There was an error sending your message. Please try again.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Container 1: Summary (hidden for direct profiles) -->
|
||||||
|
<?php if (!$show_direct_profile): ?>
|
||||||
|
<div class="hvac-summary-container">
|
||||||
|
<p>Upskill HVAC is proud to be the only training body offering Certified measureQuick training.</p>
|
||||||
|
|
||||||
|
<p><strong>Certified measureQuick Trainers</strong> have demonstrated their skills and mastery of HVAC science and the measureQuick app, and are authorized to provide measureQuick training to the industry.</p>
|
||||||
|
|
||||||
|
<p><strong>measureQuick Certified Champions</strong> have also demonstrated mastery of HVAC science and the measureQuick app, but they do not offer public training.</p>
|
||||||
|
|
||||||
|
<p>Use the interactive map and filters below to discover trainers who match your specific needs. Click on any <strong>Certified measureQuick Trainer</strong> to view their profile and contact them directly about training opportunities.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Container 2: Map & Filters (hidden for direct profiles) -->
|
||||||
|
<?php if (!$show_direct_profile): ?>
|
||||||
<div class="hvac-map-filters-container">
|
<div class="hvac-map-filters-container">
|
||||||
|
|
||||||
<!-- Container 3: Map (2/3 width) -->
|
<!-- Container 3: Map (2/3 width) -->
|
||||||
|
|
@ -220,8 +454,10 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
|
||||||
<div class="hvac-active-filters"></div>
|
<div class="hvac-active-filters"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Container 5: Trainer Directory -->
|
<!-- Container 5: Trainer Directory (hidden for direct profiles) -->
|
||||||
|
<?php if (!$show_direct_profile): ?>
|
||||||
<div class="hvac-trainer-directory-container">
|
<div class="hvac-trainer-directory-container">
|
||||||
<div class="hvac-trainer-grid">
|
<div class="hvac-trainer-grid">
|
||||||
<?php if (!empty($trainers)) : ?>
|
<?php if (!empty($trainers)) : ?>
|
||||||
|
|
@ -313,11 +549,14 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Container 6: CTA Section -->
|
<!-- Container 6: CTA Section (hidden for direct profiles) -->
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!$show_direct_profile): ?>
|
||||||
<div class="hvac-cta-container">
|
<div class="hvac-cta-container">
|
||||||
<p class="hvac-cta-text">Are you an HVAC Trainer that wants to be listed in our directory?</p>
|
<p class="hvac-cta-text">Are you an HVAC Trainer that wants to be listed in our directory?</p>
|
||||||
<a href="/trainer-registration/" class="hvac-cta-button">Become A Trainer</a>
|
<a href="/trainer-registration/" class="hvac-cta-button">Become A Trainer</a>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -409,6 +648,7 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Get footer
|
// Get footer
|
||||||
get_footer();
|
get_footer();
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,12 @@ get_header();
|
||||||
<div class="hvac-trainer-profile-view">
|
<div class="hvac-trainer-profile-view">
|
||||||
<div class="hvac-page-header">
|
<div class="hvac-page-header">
|
||||||
<h1>Trainer Profile</h1>
|
<h1>Trainer Profile</h1>
|
||||||
|
<div class="hvac-page-header-actions">
|
||||||
<a href="/trainer/profile/edit/" class="hvac-button hvac-button-primary">Edit Profile</a>
|
<a href="/trainer/profile/edit/" class="hvac-button hvac-button-primary">Edit Profile</a>
|
||||||
|
<button type="button" class="hvac-button hvac-button-secondary hvac-share-profile-btn" data-profile-id="<?php echo esc_attr($profile->ID); ?>">
|
||||||
|
<span class="dashicons dashicons-share"></span> Share Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hvac-profile-content">
|
<div class="hvac-profile-content">
|
||||||
|
|
@ -272,5 +277,57 @@ get_header();
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Share Profile Modal -->
|
||||||
|
<div id="hvac-share-profile-modal" class="hvac-share-modal" style="display: none;">
|
||||||
|
<div class="hvac-share-modal-content">
|
||||||
|
<!-- Close Button -->
|
||||||
|
<button class="hvac-modal-close" aria-label="Close">
|
||||||
|
<span class="dashicons dashicons-no"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Modal Title -->
|
||||||
|
<h2 class="hvac-share-modal-title">Share Your Profile</h2>
|
||||||
|
|
||||||
|
<!-- Modal Description -->
|
||||||
|
<p class="hvac-share-description">
|
||||||
|
Get more training requests by sharing your trainer profile on your website, social media, or email!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Share URL Section -->
|
||||||
|
<div class="hvac-share-url-section">
|
||||||
|
<label for="hvac-share-url" class="hvac-share-url-label">
|
||||||
|
<strong>Your personal training profile link:</strong>
|
||||||
|
</label>
|
||||||
|
<div class="hvac-share-url-container">
|
||||||
|
<input type="text" id="hvac-share-url" class="hvac-share-url-input" readonly value="" placeholder="Loading...">
|
||||||
|
<button type="button" class="hvac-copy-url-btn" title="Copy to clipboard">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Card Section -->
|
||||||
|
<div class="hvac-share-card-section">
|
||||||
|
<p class="hvac-share-card-description">Or screenshot the image below!</p>
|
||||||
|
<div class="hvac-share-profile-card-container" id="hvac-share-card-container">
|
||||||
|
<!-- Profile card will be loaded here via AJAX -->
|
||||||
|
<div class="hvac-share-card-loading">
|
||||||
|
<span class="dashicons dashicons-update spin"></span>
|
||||||
|
<p>Loading profile card...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
// The CSS and JS are handled by the HVAC_Scripts_Styles class
|
||||||
|
// Additional localization for profile-specific data
|
||||||
|
if (class_exists('HVAC_Scripts_Styles')) {
|
||||||
|
$scripts_styles = HVAC_Scripts_Styles::instance();
|
||||||
|
$scripts_styles->localize_sharing_data([
|
||||||
|
'profile_id' => $profile->ID
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
get_footer();
|
get_footer();
|
||||||
|
|
|
||||||
58
templates/page-trainer-training-leads.php
Normal file
58
templates/page-trainer-training-leads.php
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Template Name: Trainer Training Leads
|
||||||
|
* Template for displaying trainer training leads page
|
||||||
|
*
|
||||||
|
* @package HVAC_Plugin
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Define constant to identify we're in a page template
|
||||||
|
define('HVAC_IN_PAGE_TEMPLATE', true);
|
||||||
|
|
||||||
|
// Get header
|
||||||
|
get_header();
|
||||||
|
|
||||||
|
// Initialize breadcrumbs if available
|
||||||
|
if (class_exists('HVAC_Breadcrumbs')) {
|
||||||
|
$breadcrumbs = HVAC_Breadcrumbs::get_instance();
|
||||||
|
$breadcrumbs->set_custom_breadcrumb([
|
||||||
|
['title' => 'Trainer', 'url' => home_url('/trainer/')],
|
||||||
|
['title' => 'Profile', 'url' => home_url('/trainer/profile/')],
|
||||||
|
['title' => 'Training Leads', 'url' => '']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container hvac-trainer-page">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Render navigation menu
|
||||||
|
if (class_exists('HVAC_Menu_System')) {
|
||||||
|
$menu_system = HVAC_Menu_System::instance();
|
||||||
|
$menu_system->render_trainer_menu();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Render breadcrumbs if available
|
||||||
|
if (isset($breadcrumbs)) {
|
||||||
|
echo $breadcrumbs->render();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="hvac-main-content">
|
||||||
|
<?php
|
||||||
|
// Render the training leads content using shortcode
|
||||||
|
if (class_exists('HVAC_Training_Leads')) {
|
||||||
|
echo do_shortcode('[hvac_trainer_training_leads]');
|
||||||
|
} else {
|
||||||
|
echo '<p>Training Leads functionality is not available. Please contact an administrator.</p>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php get_footer(); ?>
|
||||||
Loading…
Reference in a new issue