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:
bengizmo 2025-08-05 16:02:57 -03:00
parent 2446997385
commit 705e6b563c
31 changed files with 8163 additions and 168 deletions

View file

@ -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.
- **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.
- **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 ...]

View file

@ -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
### Geocoding Errors

View file

@ -297,3 +297,64 @@ None - plugin uses WordPress posts, user meta, and post meta
- TCPDF library for PDF creation
- Custom certificate templates
- 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
```

View file

@ -685,6 +685,371 @@ ssh user@staging-server "cd /path/to/plugin && php test-csv-import.php"
5. **Log all import activities** for debugging
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
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/)

View file

@ -2,7 +2,7 @@
## 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

File diff suppressed because it is too large Load diff

View 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*

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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.

View file

@ -3,7 +3,7 @@
* Plugin Name: HVAC Community Events
* Plugin URI: https://upskillhvac.com
* Description: Custom plugin for HVAC trainer event management system
* Version: 1.0.1
* Version: 1.0.6
* Author: Upskill HVAC
* Author URI: https://upskillhvac.com
* License: GPL-2.0+

View file

@ -98,7 +98,7 @@ class HVAC_Astra_Integration {
* Force content layout for HVAC pages
*/
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 $layout;
@ -108,7 +108,7 @@ class HVAC_Astra_Integration {
* Force site layout for HVAC pages
*/
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 $layout;
@ -118,7 +118,7 @@ class HVAC_Astra_Integration {
* Modify container class for HVAC pages
*/
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
$classes = str_replace('ast-container', 'ast-full-width-container', $classes);
}
@ -129,7 +129,7 @@ class HVAC_Astra_Integration {
* Get HVAC-specific container 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 $class;
@ -159,8 +159,8 @@ class HVAC_Astra_Integration {
* Setup content width for HVAC pages
*/
public function setup_hvac_content_width() {
if ($this->is_hvac_page()) {
// Set global content width
if ($this->is_hvac_page() && !$this->is_find_trainer_page()) {
// Set global content width for dashboard pages only
global $content_width;
$content_width = 1920; // Full HD width
@ -168,6 +168,14 @@ class HVAC_Astra_Integration {
add_filter('astra_get_content_width', function() {
return 1920;
}, 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) {
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 Full-width overrides for Astra */
.hvac-astra-integrated .ast-container {
@ -236,6 +295,7 @@ class HVAC_Astra_Integration {
max-width: 100% !important;
}
';
}
$css .= $hvac_css;
}
@ -321,6 +381,21 @@ class HVAC_Astra_Integration {
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
*/

View file

@ -245,91 +245,143 @@ class HVAC_Help_System {
return '
<section id="getting-started" class="hvac-doc-section">
<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-card">
<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>
<a href="/hvac-dashboard" class="hvac-doc-btn">Go to Dashboard</a>
<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="' . home_url('/trainer/dashboard/') . '" class="hvac-doc-btn">Go to Dashboard</a>
</div>
<div class="hvac-doc-card">
<h3>2. Create Your First Event</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>
<a href="/manage-event" class="hvac-doc-btn">Create Event</a>
<h3>2. Update Your Profile First</h3>
<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="' . home_url('/trainer/profile/') . '" class="hvac-doc-btn">Edit Profile</a>
</div>
<div class="hvac-doc-card">
<h3>3. Complete Your Profile</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>
<a href="/trainer-profile" class="hvac-doc-btn">Edit Profile</a>
<h3>3. Create Your First Event</h3>
<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="' . home_url('/tribe/events/add/') . '" class="hvac-doc-btn">Create Event</a>
</div>
</div>
</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">
<h3>Creating Events is Simple</h3>
<ol>
<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>
<h3>Integrated Event System</h3>
<p>Our platform integrates with The Events Calendar to provide comprehensive event management:</p>
<ul>
<li><strong>Edit Events:</strong> Click any event title to modify details</li>
<li><strong>Set Capacity:</strong> Control how many can register</li>
<li><strong>Track Sales:</strong> See registrations in real-time</li>
<li><strong>Quick Actions:</strong> View, edit, or check attendees with one click</li>
<li><strong>Training Events:</strong> Sessions with pricing, capacity, and location</li>
<li><strong>Venues:</strong> Physical or virtual training locations</li>
<li><strong>Organizers:</strong> Business entities with logos and contact info</li>
<li><strong>Revenue Tracking:</strong> Monitor training income and attendance</li>
</ul>
</div>
<div class="hvac-feature">
<h3>Event Summary Page</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>
<h3>Quick Event Management</h3>
<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>
</section>
<section id="attendee-management" class="hvac-doc-section">
<h2><i class="fas fa-users"></i> Attendee Management</h2>
<section id="profile-directory" class="hvac-doc-section">
<h2><i class="fas fa-user-circle"></i> Your Professional Profile</h2>
<div class="hvac-feature-list">
<div class="hvac-feature">
<h3>See Who\'s Coming</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>
<h3>Directory Listing</h3>
<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 class="hvac-feature">
<h3>Easy Email Communication</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>
<h3>Training Leads</h3>
<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 class="hvac-feature">
<h3>Quick Check-In</h3>
<p>During your event, use the attendee list to check people in. This helps track completion for certificates and keeps accurate records.</p>
<h3>Pro Tip: Share Your Profile</h3>
<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>
</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">
<h3>Beautiful Certificates Automatically</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>
</div>
<div class="hvac-feature">
<h3>Simple Generation Process</h3>
<h3>Professional Certificates</h3>
<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>
<ul>
<li><strong>Go to "Generate Certificates"</strong> from the menu</li>
<li><strong>Select your event</strong> from the dropdown</li>
<li><strong>Choose attendees</strong> (or select all)</li>
<li><strong>Click Generate</strong> - certificates are created instantly!</li>
<li><strong>Click "Certificate Issued"</strong> text to view any certificate</li>
<li><strong>Automated Generation:</strong> Create certificates for event attendees</li>
<li><strong>Professional Design:</strong> Branded with Upskill HVAC styling</li>
<li><strong>Unique Verification:</strong> Each certificate has a verification number</li>
<li><strong>Easy Distribution:</strong> Download and share with attendees</li>
</ul>
</div>
<div class="hvac-feature">
<h3>Track Everything</h3>
<p>The Certificate Reports page shows all certificates you\'ve issued. Filter by event, search by name, and download certificates anytime.</p>
<h3>Certificate Workflow</h3>
<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>
</section>
@ -339,39 +391,48 @@ class HVAC_Help_System {
<div class="hvac-faq-list">
<div class="hvac-faq-item">
<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 class="hvac-faq-item">
<h3>How do I edit an event?</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>
<h3>How do I get more training leads?</h3>
<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 class="hvac-faq-item">
<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 class="hvac-faq-item">
<h3>Can attendees view their certificates?</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>
<h3>Can I track my training revenue?</h3>
<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 class="hvac-faq-item">
<h3>What\'s the revenue target on my dashboard?</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>
<h3>What if I need help?</h3>
<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 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 class="hvac-faq-item">
<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>
<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 class="hvac-faq-item">
<h3>How do payments work?</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>
</div>
<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 class="hvac-feature">
<h3>Remember</h3>
<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>
<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>
</div>
</section>';

View file

@ -169,17 +169,22 @@ class HVAC_Menu_System {
)
);
// Customize section
// Profile section (previously Customize)
$menu[] = array(
'title' => 'Customize',
'title' => 'Profile',
'url' => '#',
'icon' => 'dashicons-admin-customizer',
'icon' => 'dashicons-admin-users',
'children' => array(
array(
'title' => 'Personal Profile',
'title' => 'Trainer Profile',
'url' => home_url('/trainer/profile/'),
'icon' => 'dashicons-admin-users'
),
array(
'title' => 'Training Leads',
'url' => home_url('/trainer/training-leads/'),
'icon' => 'dashicons-email-alt'
),
array(
'title' => 'Training Organizers',
'url' => home_url('/trainer/organizer/list/'),
@ -203,22 +208,21 @@ class HVAC_Menu_System {
'icon' => 'dashicons-plus-alt'
)
)
)
)
);
// Help section
$menu[] = array(
'title' => 'Help',
'url' => home_url('/trainer/documentation/'),
'icon' => 'dashicons-sos'
);
// Logout (always last)
$menu[] = array(
),
array(
'title' => 'Logout',
'url' => wp_logout_url(home_url('/training-login/')),
'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;
@ -242,12 +246,21 @@ class HVAC_Menu_System {
$classes[] = 'level-' . $level;
}
// Add custom class if specified
if (!empty($item['class'])) {
$classes[] = esc_attr($item['class']);
}
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) {
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 {
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) {

View file

@ -84,6 +84,13 @@ class HVAC_Page_Manager {
'parent' => 'trainer/profile',
'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
'trainer/venue' => [
@ -460,6 +467,7 @@ class HVAC_Page_Manager {
$shortcode_mappings = [
'trainer/profile' => '[hvac_trainer_profile_view]',
'trainer/profile/edit' => '[hvac_trainer_profile_edit]',
'trainer/training-leads' => '[hvac_trainer_training_leads]',
'trainer/venue/list' => '[hvac_trainer_venues_list]',
'trainer/venue/manage' => '[hvac_trainer_venue_manage]',
'trainer/organizer/list' => '[hvac_trainer_organizers_list]',

View file

@ -57,10 +57,10 @@ class HVAC_Plugin {
*/
private function define_constants() {
if (!defined('HVAC_PLUGIN_VERSION')) {
define('HVAC_PLUGIN_VERSION', '1.0.1');
define('HVAC_PLUGIN_VERSION', '1.0.6');
}
if (!defined('HVAC_VERSION')) {
define('HVAC_VERSION', '1.0.1');
define('HVAC_VERSION', '1.0.6');
}
if (!defined('HVAC_PLUGIN_FILE')) {
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-menu-system.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 = [
@ -115,10 +116,12 @@ class HVAC_Plugin {
'class-hvac-geocoding-service.php',
'class-hvac-trainer-profile-settings.php',
'class-hvac-geocoding-ajax.php',
'class-hvac-qr-generator.php',
'class-hvac-organizers.php',
'class-hvac-trainer-navigation.php',
'class-hvac-breadcrumbs.php',
'class-hvac-template-integration.php',
'class-hvac-training-leads.php',
'class-hvac-manage-event.php',
'class-hvac-event-summary.php',
'class-hvac-trainer-profile.php',

View 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();

View file

@ -1303,7 +1303,7 @@ class HVAC_Registration {
$user = get_userdata($user_id);
$errors = [];
$submitted_data = $_POST;
$profile_page_url = home_url('/edit-profile/');
$profile_page_url = home_url('/trainer/profile/edit/');
// Verify nonce
if (!isset($_POST['hvac_profile_nonce']) || !wp_verify_nonce($_POST['hvac_profile_nonce'], 'hvac_update_profile')) {

View file

@ -211,6 +211,14 @@ class HVAC_Scripts_Styles {
array('hvac-community-events'),
$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
@ -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
wp_enqueue_script(
'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;
}
/**
* 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);
}
}
}

View 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">&times;</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();

View 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
View 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"

View 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
View 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
View 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
View 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
View 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

View file

@ -1,12 +1,57 @@
<?php
/**
* Template Name: Documentation
* Description: Template for the documentation page
* Template Name: Trainer Documentation
* 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();
// Render the documentation shortcode
echo do_shortcode('[hvac_documentation]');
// 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' => '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(); ?>

View file

@ -22,6 +22,71 @@ if (class_exists('HVAC_Trainer_Directory_Query')) {
$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
$trainers = [];
$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_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
wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('hvac_find_trainer'),
'map_id' => '5872',
'direct_profile_id' => $direct_profile_id ?: null,
'show_direct_profile' => $show_direct_profile,
'messages' => [
'loading' => __('Loading...', '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">
<!-- 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 -->
<div class="hvac-summary-container">
<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>
<!-- Direct Profile Display -->
<?php if ($show_direct_profile && $direct_profile_data): ?>
<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>
<!-- 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">
<!-- 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>
</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-grid">
<?php if (!empty($trainers)) : ?>
@ -313,11 +549,14 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
<?php endif; ?>
</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">
<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>
</div>
<?php endif; ?>
</div>
</div>
@ -409,6 +648,7 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
</div>
</div>
<?php
// Get footer
get_footer();

View file

@ -64,7 +64,12 @@ get_header();
<div class="hvac-trainer-profile-view">
<div class="hvac-page-header">
<h1>Trainer Profile</h1>
<div class="hvac-page-header-actions">
<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 class="hvac-profile-content">
@ -272,5 +277,57 @@ get_header();
</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
// 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();

View 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(); ?>