diff --git a/CLAUDE.md b/CLAUDE.md index eacaba4b..04c2503e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 ...] \ No newline at end of file diff --git a/docs/API-REFERENCE.md b/docs/API-REFERENCE.md index 75d8ab35..6a709e4a 100644 --- a/docs/API-REFERENCE.md +++ b/docs/API-REFERENCE.md @@ -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 diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index ea7317b0..4ff988d6 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -296,4 +296,65 @@ None - plugin uses WordPress posts, user meta, and post meta ### Certificate Generation - TCPDF library for PDF creation - Custom certificate templates -- Batch processing support \ No newline at end of file +- 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 +``` \ No newline at end of file diff --git a/docs/DEVELOPMENT-GUIDE.md b/docs/DEVELOPMENT-GUIDE.md index c13d864e..c966b388 100644 --- a/docs/DEVELOPMENT-GUIDE.md +++ b/docs/DEVELOPMENT-GUIDE.md @@ -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( + '
' + + '' + + '

Loading profile card...

' + + '
' +); + +// Error states with helpful messaging +ProfileSharing.showError = function(message) { + $cardContainer.html( + '
' + + '' + + '

' + message + '

' + + '
' + ); +}; + +// 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 + + +// Include the modal HTML at the end of the template + +``` + +#### 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/) diff --git a/docs/README.md b/docs/README.md index 62a10384..4cdfe2e2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/TRAINER-API-REFERENCE.md b/docs/TRAINER-API-REFERENCE.md new file mode 100644 index 00000000..a6c6476b --- /dev/null +++ b/docs/TRAINER-API-REFERENCE.md @@ -0,0 +1,1039 @@ +# HVAC Trainer System API Reference + +This document provides detailed API reference for developers working with the HVAC Trainer system, including PHP classes, JavaScript APIs, AJAX endpoints, and database schemas. + +## Table of Contents + +1. [PHP Classes](#php-classes) +2. [AJAX Endpoints](#ajax-endpoints) +3. [JavaScript APIs](#javascript-apis) +4. [Database Schema](#database-schema) +5. [Hooks & Filters](#hooks--filters) +6. [Constants & Configuration](#constants--configuration) +7. [Error Codes](#error-codes) + +## PHP Classes + +### HVAC_Trainer_Profile_Manager + +**File**: `includes/class-hvac-trainer-profile-manager.php` +**Purpose**: Core trainer profile management functionality + +#### Class Methods + +##### `get_instance()` +Returns singleton instance of the profile manager. + +```php +$manager = HVAC_Trainer_Profile_Manager::get_instance(); +``` + +**Returns**: `HVAC_Trainer_Profile_Manager` - Singleton instance + +##### `create_profile($data)` +Creates a new trainer profile. + +```php +$profile_data = [ + 'trainer_first_name' => 'John', + 'trainer_last_name' => 'Smith', + 'trainer_city' => 'Chicago', + 'trainer_state' => 'Illinois', + 'user_id' => 123, + 'certification_type' => 'Certified measureQuick Trainer' +]; + +$profile_id = $manager->create_profile($profile_data); +``` + +**Parameters**: +- `$data` (array) - Profile data array + +**Returns**: `int|false` - Profile post ID on success, false on failure + +**Data Fields**: +- `trainer_first_name` (string) - First name +- `trainer_last_name` (string) - Last name +- `trainer_display_name` (string) - Display name +- `trainer_city` (string) - City +- `trainer_state` (string) - State/Province +- `trainer_country` (string) - Country +- `profile_image_url` (string) - Profile image URL +- `certification_type` (string) - Certification level +- `user_id` (int) - WordPress user ID +- `is_public_profile` (string) - Public visibility ('1' or '0') + +##### `update_profile($profile_id, $data)` +Updates an existing trainer profile. + +```php +$updated_data = [ + 'trainer_city' => 'New York', + 'trainer_state' => 'New York', + 'certification_type' => 'Certified measureQuick Champion' +]; + +$success = $manager->update_profile(5840, $updated_data); +``` + +**Parameters**: +- `$profile_id` (int) - Profile post ID +- `$data` (array) - Updated profile data + +**Returns**: `bool` - Success status + +##### `get_profile($profile_id)` +Retrieves a trainer profile. + +```php +$profile = $manager->get_profile(5840); +``` + +**Parameters**: +- `$profile_id` (int) - Profile post ID + +**Returns**: `array|false` - Profile data array or false if not found + +**Return Structure**: +```php +[ + 'profile_id' => 5840, + 'user_id' => 31, + 'name' => 'John Smith', + 'city' => 'Chicago', + 'state' => 'Illinois', + 'certification_type' => 'Certified measureQuick Trainer', + 'profile_image' => 'https://...', + 'business_type' => 'Independent Contractor', + 'event_count' => 5, + 'training_formats' => 'In-Person, Virtual', + 'training_locations' => 'On-site, Remote' +] +``` + +##### `get_public_profiles($args = [])` +Retrieves all public trainer profiles. + +```php +$profiles = $manager->get_public_profiles([ + 'posts_per_page' => 20, + 'meta_query' => [ + [ + 'key' => 'trainer_state', + 'value' => 'Illinois', + 'compare' => '=' + ] + ] +]); +``` + +**Parameters**: +- `$args` (array) - WP_Query arguments + +**Returns**: `array` - Array of profile data + +##### `delete_profile($profile_id)` +Deletes a trainer profile. + +```php +$success = $manager->delete_profile(5840); +``` + +**Parameters**: +- `$profile_id` (int) - Profile post ID + +**Returns**: `bool` - Success status + +##### `migrate_certification_colors()` +Migrates existing profiles to add certification color fields. + +```php +$manager->migrate_certification_colors(); +``` + +**Returns**: `void` + +##### `get_certification_color($certification_type)` +Gets the color code for a certification type. + +```php +$color = $manager->get_certification_color('Certified measureQuick Trainer'); +// Returns: '#5077bb' +``` + +**Parameters**: +- `$certification_type` (string) - Certification type + +**Returns**: `string` - Hex color code + +**Color Mapping**: +- `'Certified measureQuick Champion'` → `'#f19a42'` +- `'Certified measureQuick Trainer'` → `'#5077bb'` +- Default → `'#f0f7e8'` + +### HVAC_Find_Trainer_Page + +**File**: `includes/find-trainer/class-hvac-find-trainer-page.php` +**Purpose**: Find A Trainer page functionality + +#### Class Methods + +##### `get_instance()` +Returns singleton instance. + +```php +$page = HVAC_Find_Trainer_Page::get_instance(); +``` + +##### `render_page()` +Renders the complete Find A Trainer page. + +```php +$page->render_page(); +``` + +**Returns**: `void` - Outputs HTML directly + +##### `get_filter_options()` +Gets available filter options for the directory. + +```php +$options = $page->get_filter_options(); +``` + +**Returns**: `array` - Filter options structure + +**Return Structure**: +```php +[ + 'states' => ['Illinois', 'New York', 'California'], + 'business_types' => ['Contractor', 'Distributor', 'Consultant'], + 'training_formats' => ['In-person', 'Virtual', 'Hybrid'], + 'training_resources' => ['Classroom', 'Training Lab', 'Online'] +] +``` + +##### `build_query_args($filters, $page = 1, $per_page = 12)` +Builds WP_Query arguments from filter criteria. + +```php +$filters = [ + 'state' => 'Illinois', + 'business_type' => ['Contractor', 'Consultant'], + 'search' => 'John' +]; + +$args = $page->build_query_args($filters, 1, 20); +``` + +**Parameters**: +- `$filters` (array) - Filter criteria +- `$page` (int) - Page number +- `$per_page` (int) - Results per page + +**Returns**: `array` - WP_Query arguments + +##### `render_trainer_cards($trainers)` +Renders HTML for trainer cards. + +```php +$trainers = $page->get_filtered_trainers($filters); +$page->render_trainer_cards($trainers); +``` + +**Parameters**: +- `$trainers` (array) - Array of trainer data + +**Returns**: `void` - Outputs HTML directly + +### HVAC_MapGeo_Integration + +**File**: `includes/find-trainer/class-hvac-mapgeo-integration.php` +**Purpose**: MapGeo plugin integration for interactive maps + +#### Class Methods + +##### `get_instance()` +Returns singleton instance. + +```php +$integration = HVAC_MapGeo_Integration::get_instance(); +``` + +##### `modify_map_layout($meta, $map_id = null)` +Modifies MapGeo map layout to add trainer profile data. + +```php +add_filter('igm_add_meta', [$integration, 'modify_map_layout'], 10, 2); +``` + +**Parameters**: +- `$meta` (array) - MapGeo metadata +- `$map_id` (string) - Map ID + +**Returns**: `array` - Modified metadata + +##### `ajax_get_trainer_profile()` +AJAX handler for retrieving complete trainer profile data. + +```php +add_action('wp_ajax_hvac_get_trainer_profile', [$integration, 'ajax_get_trainer_profile']); +add_action('wp_ajax_nopriv_hvac_get_trainer_profile', [$integration, 'ajax_get_trainer_profile']); +``` + +**Expected POST Data**: +- `profile_id` (int) - Trainer profile ID +- `nonce` (string) - Security nonce + +**Response Format**: +```json +{ + "success": true, + "data": { + "profile_id": 5840, + "user_id": "31", + "name": "John Smith", + "city": "Chicago", + "state": "Illinois", + "certification_type": "Certified measureQuick Trainer", + "profile_image": "https://...", + "business_type": "Independent Contractor", + "event_count": 5, + "training_formats": "In-Person, Virtual", + "training_locations": "On-site, Remote", + "upcoming_events": [] + } +} +``` + +##### `ajax_get_trainer_certification()` +AJAX handler for getting trainer certification information. + +```php +add_action('wp_ajax_hvac_get_trainer_certification', [$integration, 'ajax_get_trainer_certification']); +``` + +**Expected POST Data**: +- `profile_id` (int) - Trainer profile ID +- `nonce` (string) - Security nonce + +**Response Format**: +```json +{ + "success": true, + "data": { + "certification_type": "Certified measureQuick Trainer", + "certification_color": "#5077bb" + } +} +``` + +## AJAX Endpoints + +### Trainer Profile Management + +#### `hvac_save_trainer_profile` +Saves trainer profile changes. + +**URL**: `wp-admin/admin-ajax.php` +**Method**: POST +**Auth**: Required (logged in user) + +**Parameters**: +```php +[ + 'action' => 'hvac_save_trainer_profile', + 'profile_id' => 5840, + 'nonce' => 'security_nonce', + 'trainer_first_name' => 'John', + 'trainer_last_name' => 'Smith', + 'trainer_city' => 'Chicago', + // ... other profile fields +] +``` + +**Response**: +```json +{ + "success": true, + "data": { + "message": "Profile saved successfully", + "profile_id": 5840 + } +} +``` + +#### `hvac_auto_save_profile` +Auto-saves profile during editing. + +**Parameters**: Same as `hvac_save_trainer_profile` + +**Response**: Same format with auto-save confirmation + +### Find A Trainer Directory + +#### `hvac_filter_trainers` +Filters trainers by multiple criteria. + +**URL**: `wp-admin/admin-ajax.php` +**Method**: POST +**Auth**: Not required (public) + +**Parameters**: +```php +[ + 'action' => 'hvac_filter_trainers', + 'nonce' => 'security_nonce', + 'filters' => [ + 'state' => ['Illinois', 'New York'], + 'business_type' => ['Contractor'], + 'training_format' => ['In-person', 'Virtual'] + ], + 'page' => 1, + 'per_page' => 12 +] +``` + +**Response**: +```json +{ + "success": true, + "data": { + "html": "
...
", + "pagination": "
...
", + "count": 25, + "page": 1, + "max_pages": 3 + } +} +``` + +#### `hvac_search_trainers` +Searches trainers by text query. + +**Parameters**: +```php +[ + 'action' => 'hvac_search_trainers', + 'nonce' => 'security_nonce', + 'search' => 'John Smith', + 'page' => 1, + 'per_page' => 12 +] +``` + +**Response**: Same format as filter trainers + +#### `hvac_get_filter_options` +Gets available filter options. + +**Parameters**: +```php +[ + 'action' => 'hvac_get_filter_options', + 'nonce' => 'security_nonce' +] +``` + +**Response**: +```json +{ + "success": true, + "data": { + "states": ["Illinois", "New York", "California"], + "business_types": ["Contractor", "Distributor", "Consultant"], + "training_formats": ["In-person", "Virtual", "Hybrid"], + "training_resources": ["Classroom", "Training Lab", "Online"] + } +} +``` + +### Contact Forms + +#### `hvac_submit_contact_form` +Submits contact form to trainer. + +**Parameters**: +```php +[ + 'action' => 'hvac_submit_contact_form', + 'nonce' => 'security_nonce', + 'trainer_id' => 5840, + 'contact_name' => 'Jane Doe', + 'contact_email' => 'jane@example.com', + 'contact_phone' => '555-1234', + 'message' => 'I would like to inquire about training...' +] +``` + +**Response**: +```json +{ + "success": true, + "data": { + "message": "Your message has been sent successfully" + } +} +``` + +## JavaScript APIs + +### HVAC_FindTrainer Object + +**File**: `assets/js/find-trainer.js` +**Purpose**: Client-side Find A Trainer functionality + +#### Configuration + +```javascript +var HVAC_FindTrainer = { + config: { + ajax_url: hvac_find_trainer.ajax_url, + nonce: hvac_find_trainer.nonce, + filters: {}, + current_page: 1, + per_page: 12 + } +}; +``` + +#### Methods + +##### `init(options)` +Initializes the Find A Trainer system. + +```javascript +HVAC_FindTrainer.init({ + ajax_url: '/wp-admin/admin-ajax.php', + nonce: 'security_nonce', + filters: { + state: [], + business_type: [], + training_format: [] + } +}); +``` + +**Parameters**: +- `options` (object) - Configuration options + +##### `performSearch(query)` +Performs text search for trainers. + +```javascript +HVAC_FindTrainer.performSearch('John Smith'); +``` + +**Parameters**: +- `query` (string) - Search query + +##### `applyFilters()` +Applies current filter settings. + +```javascript +HVAC_FindTrainer.config.filters.state = ['Illinois']; +HVAC_FindTrainer.applyFilters(); +``` + +##### `updateResults(response)` +Updates the display with new results. + +```javascript +HVAC_FindTrainer.updateResults({ + success: true, + data: { + html: '
...
', + pagination: '
...
', + count: 25 + } +}); +``` + +**Parameters**: +- `response` (object) - AJAX response object + +##### `showTrainerProfile(profileId)` +Shows trainer profile modal. + +```javascript +HVAC_FindTrainer.showTrainerProfile(5840); +``` + +**Parameters**: +- `profileId` (int) - Trainer profile ID + +### MapGeo Integration JavaScript + +**File**: Embedded in `class-hvac-mapgeo-integration.php` +**Purpose**: MapGeo map interaction handling + +#### Global Functions + +##### `window.hvac_show_trainer_modal(markerData)` +Custom MapGeo action handler for trainer markers. + +```javascript +window.hvac_show_trainer_modal({ + hvac_profile_id: 5840, + id: 'trainer_5840', + title: 'John Smith' +}); +``` + +**Parameters**: +- `markerData` (object) - Marker data from MapGeo + +#### Global Variables + +##### `window.hvacTrainerDataCache` +Cache object for trainer profile data. + +```javascript +// Cache structure +window.hvacTrainerDataCache = { + '5840': { + profile_id: 5840, + name: 'John Smith', + certification_type: 'Certified measureQuick Trainer' + // ... other profile data + } +}; +``` + +##### `window.hvacPendingRequests` +Tracking object for pending AJAX requests. + +```javascript +// Pending requests structure +window.hvacPendingRequests = { + '5840': true // Profile ID 5840 has pending request +}; +``` + +##### `window.lastMapGeoTrainerData` +Stores the most recent trainer data logged by MapGeo. + +```javascript +window.lastMapGeoTrainerData = { + id: 'trainer_5840', + title: 'John Smith', + latitude: 41.8781, + longitude: -87.6298 +}; +``` + +## Database Schema + +### Custom Post Type: trainer_profile + +#### wp_posts Table Fields +```sql +-- Core post fields for trainer_profile +CREATE TABLE wp_posts ( + ID bigint(20) unsigned NOT NULL AUTO_INCREMENT, + post_title tinytext NOT NULL, -- Trainer display name + post_content longtext NOT NULL, -- Profile description + post_status varchar(20) NOT NULL, -- 'publish', 'draft', 'pending' + post_type varchar(20) NOT NULL, -- 'trainer_profile' + post_date datetime NOT NULL, + post_modified datetime NOT NULL, + PRIMARY KEY (ID), + KEY type_status_date (post_type, post_status, post_date, ID) +); +``` + +#### wp_postmeta Table Fields +```sql +-- Profile metadata structure +INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES +-- Personal Information +(5840, 'trainer_first_name', 'John'), +(5840, 'trainer_last_name', 'Smith'), +(5840, 'trainer_display_name', 'John Smith'), +(5840, 'trainer_city', 'Chicago'), +(5840, 'trainer_state', 'Illinois'), +(5840, 'trainer_country', 'USA'), +(5840, 'profile_image_url', 'https://...'), +(5840, 'linkedin_profile_url', 'https://linkedin.com/...'), + +-- Certification Information +(5840, 'certification_type', 'Certified measureQuick Trainer'), +(5840, 'certification_status', 'Active'), +(5840, 'certification_color', '#5077bb'), +(5840, 'date_certified', '2024-01-15'), +(5840, 'personal_accreditation', 'Additional certifications'), + +-- System Fields +(5840, 'user_id', '31'), +(5840, 'is_public_profile', '1'), +(5840, '_last_geocode_attempt', '1691234567'), +(5840, 'geocoded_lat', '41.8781136'), +(5840, 'geocoded_lng', '-87.6297982'), + +-- Business Information +(5840, 'annual_revenue_target', '100000'), +(5840, 'application_details', 'Registration details...'); +``` + +#### Taxonomy Tables + +##### Business Type Taxonomy +```sql +-- wp_terms entries for business_type taxonomy +INSERT INTO wp_terms (term_id, name, slug) VALUES +(1, 'Manufacturer', 'manufacturer'), +(2, 'Distributor', 'distributor'), +(3, 'Contractor', 'contractor'), +(4, 'Consultant', 'consultant'), +(5, 'Educator', 'educator'), +(6, 'Government', 'government'), +(7, 'Other', 'other'); + +-- wp_term_taxonomy entries +INSERT INTO wp_term_taxonomy (term_taxonomy_id, term_id, taxonomy, parent, count) VALUES +(1, 1, 'business_type', 0, 5), +(2, 2, 'business_type', 0, 8), +(3, 3, 'business_type', 0, 12); + +-- wp_term_relationships entries (linking posts to terms) +INSERT INTO wp_term_relationships (object_id, term_taxonomy_id, term_order) VALUES +(5840, 3, 0); -- Profile 5840 is linked to Contractor business type +``` + +### Database Indexes + +#### Recommended Indexes for Performance +```sql +-- Optimize meta queries +CREATE INDEX idx_postmeta_profile_queries +ON wp_postmeta (meta_key, meta_value(50), post_id); + +-- Optimize trainer profile queries +CREATE INDEX idx_posts_trainer_profile +ON wp_posts (post_type, post_status, post_date); + +-- Optimize geocoded trainer queries +CREATE INDEX idx_geocoded_trainers +ON wp_postmeta (meta_key, post_id) +WHERE meta_key IN ('geocoded_lat', 'geocoded_lng', 'is_public_profile'); +``` + +### Common Database Queries + +#### Get All Public Trainers +```sql +SELECT p.ID, p.post_title, + lat.meta_value as latitude, + lng.meta_value as longitude, + cert.meta_value as certification_type +FROM wp_posts p +LEFT JOIN wp_postmeta pub ON p.ID = pub.post_id AND pub.meta_key = 'is_public_profile' +LEFT JOIN wp_postmeta lat ON p.ID = lat.post_id AND lat.meta_key = 'geocoded_lat' +LEFT JOIN wp_postmeta lng ON p.ID = lng.post_id AND lng.meta_key = 'geocoded_lng' +LEFT JOIN wp_postmeta cert ON p.ID = cert.post_id AND cert.meta_key = 'certification_type' +WHERE p.post_type = 'trainer_profile' +AND p.post_status = 'publish' +AND pub.meta_value = '1' +AND lat.meta_value IS NOT NULL +AND lng.meta_value IS NOT NULL; +``` + +#### Get Trainers by State +```sql +SELECT p.ID, p.post_title, state.meta_value as state +FROM wp_posts p +INNER JOIN wp_postmeta state ON p.ID = state.post_id AND state.meta_key = 'trainer_state' +INNER JOIN wp_postmeta pub ON p.ID = pub.post_id AND pub.meta_key = 'is_public_profile' +WHERE p.post_type = 'trainer_profile' +AND p.post_status = 'publish' +AND pub.meta_value = '1' +AND state.meta_value = 'Illinois' +ORDER BY p.post_title; +``` + +## Hooks & Filters + +### Action Hooks + +#### `hvac_trainer_profile_created` +Fired when a new trainer profile is created. + +```php +add_action('hvac_trainer_profile_created', function($profile_id, $profile_data) { + // Custom logic after profile creation + error_log("New trainer profile created: " . $profile_id); +}, 10, 2); +``` + +**Parameters**: +- `$profile_id` (int) - Created profile ID +- `$profile_data` (array) - Profile data used for creation + +#### `hvac_trainer_profile_updated` +Fired when a trainer profile is updated. + +```php +add_action('hvac_trainer_profile_updated', function($profile_id, $old_data, $new_data) { + // Custom logic after profile update + if ($old_data['trainer_city'] !== $new_data['trainer_city']) { + // Location changed - trigger geocoding + do_action('hvac_trigger_geocoding', $profile_id); + } +}, 10, 3); +``` + +**Parameters**: +- `$profile_id` (int) - Updated profile ID +- `$old_data` (array) - Previous profile data +- `$new_data` (array) - New profile data + +#### `hvac_trainer_profile_geocoded` +Fired when a trainer profile is successfully geocoded. + +```php +add_action('hvac_trainer_profile_geocoded', function($profile_id, $lat, $lng) { + // Custom logic after geocoding + error_log("Profile {$profile_id} geocoded to: {$lat}, {$lng}"); +}, 10, 3); +``` + +**Parameters**: +- `$profile_id` (int) - Profile ID +- `$lat` (float) - Latitude +- `$lng` (float) - Longitude + +### Filter Hooks + +#### `hvac_trainer_profile_data` +Filters profile data before saving. + +```php +add_filter('hvac_trainer_profile_data', function($data, $profile_id) { + // Modify profile data before saving + if (empty($data['trainer_display_name'])) { + $data['trainer_display_name'] = $data['trainer_first_name'] . ' ' . $data['trainer_last_name']; + } + return $data; +}, 10, 2); +``` + +**Parameters**: +- `$data` (array) - Profile data +- `$profile_id` (int) - Profile ID (0 for new profiles) + +**Returns**: `array` - Modified profile data + +#### `hvac_trainer_query_args` +Filters WP_Query arguments for trainer queries. + +```php +add_filter('hvac_trainer_query_args', function($args, $context) { + // Modify query arguments + if ($context === 'find_trainer_page') { + $args['posts_per_page'] = 20; // Show more results + } + return $args; +}, 10, 2); +``` + +**Parameters**: +- `$args` (array) - WP_Query arguments +- `$context` (string) - Query context ('find_trainer_page', 'admin_list', etc.) + +**Returns**: `array` - Modified query arguments + +#### `hvac_trainer_card_data` +Filters trainer card display data. + +```php +add_filter('hvac_trainer_card_data', function($card_data, $profile_id) { + // Add custom fields to card display + $card_data['custom_field'] = get_post_meta($profile_id, 'custom_field', true); + return $card_data; +}, 10, 2); +``` + +**Parameters**: +- `$card_data` (array) - Card display data +- `$profile_id` (int) - Profile ID + +**Returns**: `array` - Modified card data + +#### `hvac_mapgeo_marker_data` +Filters MapGeo marker data for trainers. + +```php +add_filter('hvac_mapgeo_marker_data', function($marker_data, $profile_id) { + // Customize marker appearance + $cert_type = get_post_meta($profile_id, 'certification_type', true); + if ($cert_type === 'Certified measureQuick Champion') { + $marker_data['icon'] = 'champion_icon'; + $marker_data['fill'] = '#f19a42'; + } + return $marker_data; +}, 10, 2); +``` + +**Parameters**: +- `$marker_data` (array) - Marker data +- `$profile_id` (int) - Profile ID + +**Returns**: `array` - Modified marker data + +## Constants & Configuration + +### Plugin Constants + +```php +// Plugin version +define('HVAC_PLUGIN_VERSION', '1.0.0'); + +// Plugin paths +define('HVAC_PLUGIN_PATH', plugin_dir_path(__FILE__)); +define('HVAC_PLUGIN_URL', plugin_dir_url(__FILE__)); + +// MapGeo configuration +define('HVAC_MAPGEO_MAP_ID', '5872'); + +// Performance settings +define('HVAC_TRAINER_CACHE_TIMEOUT', HOUR_IN_SECONDS); +define('HVAC_TRAINER_GEOCODING_TIMEOUT', 30); // seconds + +// Debug mode +define('HVAC_DEBUG', false); +``` + +### Configuration Options + +#### WordPress Options +```php +// Google Maps API key for geocoding +update_option('hvac_google_maps_api_key', 'your_api_key_here'); + +// Default trainer profile settings +update_option('hvac_trainer_default_settings', [ + 'auto_geocode' => true, + 'public_by_default' => false, + 'require_approval' => true +]); + +// Find A Trainer page settings +update_option('hvac_find_trainer_settings', [ + 'results_per_page' => 12, + 'enable_map' => true, + 'enable_contact_forms' => true, + 'cache_timeout' => 3600 +]); +``` + +## Error Codes + +### AJAX Error Codes + +| Code | Constant | Description | +|------|----------|-------------| +| 1001 | `HVAC_ERROR_INVALID_NONCE` | Nonce verification failed | +| 1002 | `HVAC_ERROR_INSUFFICIENT_PERMISSIONS` | User lacks required permissions | +| 1003 | `HVAC_ERROR_PROFILE_NOT_FOUND` | Trainer profile not found | +| 1004 | `HVAC_ERROR_INVALID_DATA` | Invalid or missing data | +| 1005 | `HVAC_ERROR_SAVE_FAILED` | Profile save operation failed | +| 1006 | `HVAC_ERROR_GEOCODING_FAILED` | Geocoding service error | +| 1007 | `HVAC_ERROR_EMAIL_FAILED` | Email delivery failed | + +### Error Handling Example + +```php +// In AJAX handler +public function ajax_save_trainer_profile() { + // Verify nonce + if (!wp_verify_nonce($_POST['nonce'], 'hvac_trainer_profile')) { + wp_send_json_error([ + 'code' => 1001, + 'message' => 'Security verification failed' + ]); + return; + } + + // Check permissions + if (!current_user_can('edit_hvac_profile')) { + wp_send_json_error([ + 'code' => 1002, + 'message' => 'Insufficient permissions' + ]); + return; + } + + // Validate profile ID + $profile_id = intval($_POST['profile_id']); + if (!$profile_id || get_post_type($profile_id) !== 'trainer_profile') { + wp_send_json_error([ + 'code' => 1003, + 'message' => 'Trainer profile not found' + ]); + return; + } + + // Process save operation + $result = $this->save_profile($profile_id, $_POST); + + if (is_wp_error($result)) { + wp_send_json_error([ + 'code' => 1005, + 'message' => $result->get_error_message() + ]); + return; + } + + wp_send_json_success([ + 'message' => 'Profile saved successfully', + 'profile_id' => $profile_id + ]); +} +``` + +### JavaScript Error Handling + +```javascript +// Client-side error handling +function handleAjaxError(response) { + if (!response.success && response.data.code) { + switch (response.data.code) { + case 1001: + // Nonce error - refresh page + location.reload(); + break; + case 1002: + // Permission error + alert('You do not have permission to perform this action.'); + break; + case 1003: + // Profile not found + alert('Trainer profile not found.'); + break; + default: + // Generic error + alert('An error occurred: ' + response.data.message); + } + } +} + +// Usage in AJAX calls +jQuery.ajax({ + url: hvac_trainer.ajax_url, + method: 'POST', + data: requestData, + success: function(response) { + if (response.success) { + // Handle success + } else { + handleAjaxError(response); + } + }, + error: function(jqXHR, textStatus, errorThrown) { + console.error('AJAX Error:', textStatus, errorThrown); + alert('Network error occurred. Please try again.'); + } +}); +``` + +This API reference provides comprehensive technical documentation for developers working with the HVAC Trainer system. For additional examples and use cases, refer to the main system documentation. \ No newline at end of file diff --git a/docs/TRAINER-PROFILE-SHARING-GUIDE.md b/docs/TRAINER-PROFILE-SHARING-GUIDE.md new file mode 100644 index 00000000..00c50bc0 --- /dev/null +++ b/docs/TRAINER-PROFILE-SHARING-GUIDE.md @@ -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* \ No newline at end of file diff --git a/docs/TRAINER-SYSTEM-DOCUMENTATION.md b/docs/TRAINER-SYSTEM-DOCUMENTATION.md new file mode 100644 index 00000000..f0504d40 --- /dev/null +++ b/docs/TRAINER-SYSTEM-DOCUMENTATION.md @@ -0,0 +1,1035 @@ +# HVAC Trainer System Documentation + +This document provides comprehensive documentation for all functionality related to the HVAC Trainer custom post type and Find A Trainer page in the HVAC Community Events plugin. + +## Table of Contents + +1. [System Overview](#system-overview) +2. [Trainer Profile Custom Post Type](#trainer-profile-custom-post-type) +3. [Find A Trainer Page](#find-a-trainer-page) +4. [MapGeo Integration](#mapgeo-integration) +5. [User Roles & Permissions](#user-roles--permissions) +6. [API Reference](#api-reference) +7. [Frontend Components](#frontend-components) +8. [Database Schema](#database-schema) +9. [Performance Optimizations](#performance-optimizations) +10. [Troubleshooting](#troubleshooting) + +## System Overview + +The HVAC Trainer System is a comprehensive solution for managing trainer profiles and providing a public directory with interactive map functionality. The system consists of: + +- **Custom Post Type**: `trainer_profile` for storing trainer information +- **Find A Trainer Page**: Public-facing directory with search and filtering +- **MapGeo Integration**: Interactive map with trainer markers and modals +- **Profile Management**: Complete CRUD operations for trainer profiles +- **Taxonomy System**: Categorization for business types, training formats, etc. + +### Architecture Diagram + +``` +WordPress Users (hvac_trainer/hvac_master_trainer) + ↓ +Trainer Profile Custom Post Type (trainer_profile) + ↓ +Find A Trainer Page ← MapGeo Integration → Interactive Map + ↓ +Search/Filter System → AJAX Handlers → Database Queries +``` + +## Trainer Profile Custom Post Type + +### Overview + +The `trainer_profile` custom post type stores comprehensive information about HVAC trainers, including personal details, certifications, business information, and location data. + +### Core Files + +- **Main Handler**: `includes/class-hvac-trainer-profile-manager.php` +- **Settings**: `includes/class-hvac-trainer-profile-settings.php` +- **Status Management**: `includes/class-hvac-trainer-status.php` + +### Custom Fields Reference + +#### Personal Information + +| Field Name | Type | Description | Example | +|------------|------|-------------|---------| +| `trainer_first_name` | string | First name | "John" | +| `trainer_last_name` | string | Last name | "Smith" | +| `trainer_display_name` | string | Public display name | "John Smith" | +| `trainer_city` | string | City location | "Chicago" | +| `trainer_state` | string | State/Province | "Illinois" | +| `trainer_country` | string | Country | "USA" | +| `profile_image_url` | string | Profile photo URL | "https://..." | +| `linkedin_profile_url` | string | LinkedIn profile | "https://linkedin..." | + +#### Certification Details + +| Field Name | Type | Description | Values | +|------------|------|-------------|--------| +| `certification_type` | string | Certification level | "Certified measureQuick Trainer", "Certified measureQuick Champion" | +| `certification_status` | string | Current status | "Active", "Expired", "Pending", "Disabled" | +| `certification_color` | string | Hex color for MapGeo | "#5077bb", "#f19a42", "#f0f7e8" | +| `date_certified` | string | Certification date | "2024-01-15" | +| `personal_accreditation` | string | Additional certifications | Free text | + +#### Business Information + +| Field Name | Type | Description | +|------------|------|-------------| +| `annual_revenue_target` | string | Revenue goals | +| `application_details` | string | Registration application details | + +#### System Fields + +| Field Name | Type | Description | +|------------|------|-------------| +| `user_id` | integer | WordPress user ID | +| `is_public_profile` | string | Visibility flag ("1" or "0") | +| `_last_geocode_attempt` | string | Last geocoding timestamp | +| `geocoded_lat` | string | Latitude coordinate | +| `geocoded_lng` | string | Longitude coordinate | + +### Taxonomies + +#### Business Type (`business_type`) +- Manufacturer +- Distributor +- Contractor +- Consultant +- Educator +- Government +- Other + +#### Training Audience (`training_audience`) +- Anyone (open to the public) +- Industry professionals +- Internal staff in my company +- Registered students/members of my org/institution + +#### Training Formats (`training_formats`) +- In-person +- Virtual +- Hybrid +- On-demand + +#### Training Locations (`training_locations`) +- Online +- Local +- Regional Travel +- National Travel +- International Travel + +#### Training Resources (`training_resources`) +- Classroom +- Training Lab +- Ducted Furnace(s) +- Ducted Air Handler(s) +- Ducted Air Conditioner(s) +- Ducted Heat Pump(s) +- Ductless Heat Pump(s) +- Training Manuals +- Presentation Slides +- LMS Platform / SCORM Files +- Custom Curriculum +- Other + +### Profile Creation Workflow + +1. **User Registration**: User completes registration form +2. **Role Assignment**: User assigned `hvac_trainer` or `hvac_master_trainer` role +3. **Profile Creation**: System automatically creates `trainer_profile` post +4. **Data Population**: Registration data mapped to profile fields +5. **Geocoding**: System attempts to geocode trainer location +6. **Approval Process**: Admin reviews and approves profile for public display + +### Profile Management + +#### Creating Profiles Programmatically + +```php +$profile_manager = HVAC_Trainer_Profile_Manager::get_instance(); + +$profile_data = [ + 'trainer_first_name' => 'John', + 'trainer_last_name' => 'Smith', + 'trainer_city' => 'Chicago', + 'trainer_state' => 'Illinois', + 'certification_type' => 'Certified measureQuick Trainer', + 'user_id' => 123, + 'is_public_profile' => '1' +]; + +$profile_id = $profile_manager->create_profile($profile_data); +``` + +#### Updating Profiles + +```php +$profile_manager->update_profile($profile_id, $updated_data); +``` + +#### Retrieving Profiles + +```php +// Get single profile +$profile = $profile_manager->get_profile($profile_id); + +// Get all public profiles +$public_profiles = $profile_manager->get_public_profiles(); + +// Get profiles by criteria +$filtered_profiles = $profile_manager->get_profiles_by_criteria([ + 'state' => 'Illinois', + 'certification_type' => 'Certified measureQuick Trainer' +]); +``` + +## Find A Trainer Page + +### Overview + +The Find A Trainer page (`/find-a-trainer/`) provides a public directory of approved trainer profiles with interactive search, filtering, and map functionality. + +### Core Files + +- **Page Handler**: `includes/find-trainer/class-hvac-find-trainer-page.php` +- **Directory Query**: `includes/find-trainer/class-hvac-trainer-directory-query.php` +- **Contact Handler**: `includes/find-trainer/class-hvac-contact-form-handler.php` +- **Template**: `templates/page-find-trainer.php` + +### Features + +#### Search Functionality +- **Text Search**: Search by trainer name, city, or state +- **Real-time Results**: AJAX-powered instant search results +- **Autocomplete**: Suggestions as user types + +#### Filtering System +- **State/Province Filter**: Location-based filtering +- **Business Type Filter**: Filter by trainer business type +- **Training Format Filter**: Filter by available training formats +- **Training Resources Filter**: Filter by available resources +- **Multiple Filters**: Combine multiple filter criteria + +#### Directory Display +- **Card Layout**: Clean trainer cards with essential information +- **Pagination**: Performance-optimized pagination system +- **Sorting**: Certified Trainers first, then Champions, then alphabetical +- **Champion Distinction**: Special styling for measureQuick Champions + +#### Contact Integration +- **Contact Forms**: Direct contact forms for each trainer +- **Email Integration**: Automated email delivery to trainers +- **Lead Tracking**: Track contact form submissions + +### AJAX Endpoints + +| Endpoint | Purpose | Parameters | +|----------|---------|------------| +| `hvac_filter_trainers` | Filter trainers | `filters`, `page`, `per_page` | +| `hvac_search_trainers` | Search trainers | `search`, `page`, `per_page` | +| `hvac_get_filter_options` | Get filter options | None | +| `hvac_submit_contact_form` | Submit contact form | `trainer_id`, `name`, `email`, `message` | + +### Frontend Implementation + +#### JavaScript Integration + +```javascript +// Initialize Find A Trainer functionality +jQuery(document).ready(function($) { + HVAC_FindTrainer.init({ + ajax_url: hvac_find_trainer.ajax_url, + nonce: hvac_find_trainer.nonce, + filters: { + state: [], + business_type: [], + training_format: [] + } + }); +}); +``` + +#### CSS Classes + +| Class | Purpose | +|-------|---------| +| `.hvac-trainer-card` | Individual trainer card container | +| `.hvac-trainer-card-certified` | Certified trainer styling | +| `.hvac-champion-card` | Champion trainer styling | +| `.hvac-trainer-filters` | Filter panel container | +| `.hvac-search-box` | Search input container | +| `.hvac-trainer-modal` | Trainer profile modal | + +## MapGeo Integration + +### Overview + +The MapGeo integration provides an interactive map showing trainer locations with clickable markers that open detailed trainer modals. + +### Core Files + +- **Integration Handler**: `includes/find-trainer/class-hvac-mapgeo-integration.php` +- **Map Configuration**: Integrated in Find A Trainer page template + +### Features + +#### Interactive Map +- **Map ID**: 5872 (configured in MapGeo plugin) +- **Marker Display**: Trainers with geocoded coordinates appear as markers +- **Color Coding**: Different colors based on certification type +- **Click Handlers**: Custom click actions for trainer markers + +#### Performance Optimizations +- **Request Deduplication**: Prevents duplicate AJAX calls +- **Caching System**: Caches trainer data for instant subsequent access +- **Click Throttling**: Prevents rapid-fire clicking issues +- **Fallback Handling**: Graceful degradation when data unavailable + +#### Modal System +- **Trainer Profiles**: Complete trainer information in popup modals +- **Champion Filtering**: Champions don't show modals (directory-only display) +- **Contact Integration**: Direct contact forms within modals +- **Responsive Design**: Mobile-optimized modal display + +### Technical Implementation + +#### MapGeo Marker Configuration + +```php +public function modify_map_layout($meta, $map_id = null) { + // Only process for our specific map (5872) + if ($map_id && $map_id != $this->map_id) { + return $meta; + } + + // Configure markers with trainer profile data + foreach ($meta['markers'] as &$marker) { + $trainer_profile_id = $this->find_trainer_profile_by_name($marker['title']); + if ($trainer_profile_id) { + $marker['action'] = 'hvac_show_trainer_modal'; + $marker['hvac_profile_id'] = $trainer_profile_id; + $marker['id'] = 'trainer_' . $trainer_profile_id; + } + } + + return $meta; +} +``` + +#### JavaScript Modal Handler + +```javascript +window.hvac_show_trainer_modal = function(markerData) { + var profileId = markerData.hvac_profile_id; + + // Check cache first for immediate response + if (window.hvacTrainerDataCache[profileId]) { + var cachedData = window.hvacTrainerDataCache[profileId]; + if (typeof window.showTrainerModal === 'function') { + window.showTrainerModal(cachedData); + } + return; + } + + // Fetch profile data via AJAX if not cached + // Implementation handles caching, error handling, and modal display +}; +``` + +### Certification Color Coding + +| Certification Type | Color | Usage | +|--------------------|-------|-------| +| Certified measureQuick Trainer | `#5077bb` | Primary trainer markers | +| Certified measureQuick Champion | `#f19a42` | Champion markers (no modals) | +| Default/Other | `#f0f7e8` | Fallback color | + +## User Roles & Permissions + +### HVAC Trainer (`hvac_trainer`) + +#### Core Capabilities +- `read` - Basic WordPress read access +- `upload_files` - File upload capability +- `edit_hvac_profile` - Edit own trainer profile +- `view_hvac_dashboard` - Access trainer dashboard +- `manage_hvac_events` - Manage own events +- `manage_attendees` - Manage event attendees +- `email_attendees` - Send emails to attendees + +#### Events Calendar Integration +- `publish_tribe_events` - Create and publish events +- `edit_tribe_events` - Edit events +- `delete_tribe_events` - Delete events +- `edit_published_tribe_events` - Edit published events +- `delete_published_tribe_events` - Delete published events +- `read_private_tribe_events` - View private events + +### HVAC Master Trainer (`hvac_master_trainer`) + +#### Extended Capabilities +- All trainer capabilities +- Access to master dashboard with analytics +- View aggregate trainer statistics +- Enhanced reporting capabilities + +### Permission Checks + +```php +// Check if user can edit trainer profiles +if (current_user_can('edit_hvac_profile')) { + // Allow profile editing +} + +// Check for master trainer capabilities +if (current_user_can('hvac_master_trainer')) { + // Show master trainer features +} +``` + +## API Reference + +### AJAX Handlers + +#### Get Trainer Profile +**Endpoint**: `hvac_get_trainer_profile` +**Method**: POST +**Parameters**: +- `profile_id` (integer) - Trainer profile ID +- `nonce` (string) - Security nonce + +**Response**: +```json +{ + "success": true, + "data": { + "profile_id": 5840, + "user_id": "31", + "name": "Jeremy Begley", + "city": "Knoxville", + "state": "Tennessee", + "certification_type": "Certified measureQuick Trainer", + "profile_image": "https://...", + "business_type": "Independent Contractor", + "event_count": 5, + "training_formats": "In-Person, Virtual", + "training_locations": "On-site, Remote", + "upcoming_events": [] + } +} +``` + +#### Filter Trainers +**Endpoint**: `hvac_filter_trainers` +**Method**: POST +**Parameters**: +- `filters` (array) - Filter criteria +- `page` (integer) - Page number +- `per_page` (integer) - Results per page +- `nonce` (string) - Security nonce + +**Response**: +```json +{ + "success": true, + "data": { + "html": "
...
", + "pagination": "
...
", + "count": 25, + "page": 1, + "max_pages": 3 + } +} +``` + +#### Search Trainers +**Endpoint**: `hvac_search_trainers` +**Method**: POST +**Parameters**: +- `search` (string) - Search query +- `page` (integer) - Page number +- `per_page` (integer) - Results per page +- `nonce` (string) - Security nonce + +**Response**: Same format as filter trainers + +### PHP API Methods + +#### HVAC_Trainer_Profile_Manager Methods + +```php +// Get instance +$manager = HVAC_Trainer_Profile_Manager::get_instance(); + +// Create profile +$profile_id = $manager->create_profile($data); + +// Update profile +$manager->update_profile($profile_id, $data); + +// Get profile +$profile = $manager->get_profile($profile_id); + +// Get public profiles +$profiles = $manager->get_public_profiles(); + +// Delete profile +$manager->delete_profile($profile_id); +``` + +#### HVAC_Find_Trainer_Page Methods + +```php +// Get instance +$page = HVAC_Find_Trainer_Page::get_instance(); + +// Render trainer cards +$page->render_trainer_cards($trainers); + +// Get filter options +$options = $page->get_filter_options(); + +// Build query args +$args = $page->build_query_args($filters); +``` + +## Frontend Components + +### CSS Architecture + +#### Find A Trainer Styles (`assets/css/find-trainer.css`) + +```css +/* Main container */ +.hvac-find-trainer-container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* Search and filters */ +.hvac-search-filters { + display: flex; + gap: 20px; + margin-bottom: 30px; + flex-wrap: wrap; +} + +/* Trainer cards */ +.hvac-trainer-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.hvac-trainer-card { + border: 1px solid #ddd; + border-radius: 8px; + padding: 20px; + background: white; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.hvac-trainer-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.15); +} + +/* Certified trainer styling */ +.hvac-trainer-card-certified { + border-color: #5077bb; +} + +.hvac-trainer-card-certified::before { + content: "✓ Certified Trainer"; + background: #5077bb; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + position: absolute; + top: -8px; + right: 10px; +} + +/* Champion styling */ +.hvac-champion-card { + border-color: #f19a42; + background: linear-gradient(135deg, #fff 0%, #fef9f5 100%); +} + +.hvac-champion-card .hvac-trainer-name { + color: #f19a42; + font-weight: bold; +} +``` + +#### MapGeo Integration Styles + +```css +/* MapGeo modal styling */ +.hvac-trainer-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0,0,0,0.3); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + z-index: 10000; +} + +.hvac-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 9999; +} + +/* Map markers */ +.hvac-trainer-marker { + cursor: pointer; + transition: all 0.2s; +} + +.hvac-trainer-marker:hover { + transform: scale(1.1); +} +``` + +### JavaScript Components + +#### Find A Trainer JavaScript (`assets/js/find-trainer.js`) + +```javascript +var HVAC_FindTrainer = { + // Configuration + config: { + ajax_url: '', + nonce: '', + filters: {}, + current_page: 1, + per_page: 12 + }, + + // Initialize + init: function(options) { + this.config = Object.assign(this.config, options); + this.bindEvents(); + this.loadInitialResults(); + }, + + // Event bindings + bindEvents: function() { + // Search functionality + jQuery('#hvac-trainer-search').on('input', this.handleSearch.bind(this)); + + // Filter changes + jQuery('.hvac-filter-select').on('change', this.handleFilterChange.bind(this)); + + // Pagination + jQuery(document).on('click', '.hvac-page-link', this.handlePagination.bind(this)); + + // Trainer card clicks + jQuery(document).on('click', '.hvac-open-profile', this.openTrainerModal.bind(this)); + }, + + // Handle search input + handleSearch: function(e) { + var query = jQuery(e.target).val(); + this.performSearch(query); + }, + + // Handle filter changes + handleFilterChange: function(e) { + var $filter = jQuery(e.target); + var filterType = $filter.data('filter'); + var value = $filter.val(); + + this.config.filters[filterType] = value; + this.applyFilters(); + }, + + // Perform AJAX search + performSearch: function(query) { + jQuery.ajax({ + url: this.config.ajax_url, + method: 'POST', + data: { + action: 'hvac_search_trainers', + search: query, + page: 1, + per_page: this.config.per_page, + nonce: this.config.nonce + }, + success: this.updateResults.bind(this) + }); + }, + + // Apply filters + applyFilters: function() { + jQuery.ajax({ + url: this.config.ajax_url, + method: 'POST', + data: { + action: 'hvac_filter_trainers', + filters: this.config.filters, + page: 1, + per_page: this.config.per_page, + nonce: this.config.nonce + }, + success: this.updateResults.bind(this) + }); + }, + + // Update results display + updateResults: function(response) { + if (response.success) { + jQuery('.hvac-trainer-cards').html(response.data.html); + jQuery('.hvac-pagination').html(response.data.pagination); + this.updateResultsCount(response.data.count); + } + }, + + // Open trainer modal + openTrainerModal: function(e) { + e.preventDefault(); + var profileId = jQuery(e.target).data('profile-id'); + this.showTrainerProfile(profileId); + } +}; +``` + +## Database Schema + +### Trainer Profile Posts Table +```sql +-- wp_posts table entries for trainer_profile post type +SELECT + ID, + post_title, + post_status, + post_type, + post_date +FROM wp_posts +WHERE post_type = 'trainer_profile'; +``` + +### Trainer Profile Meta Data +```sql +-- wp_postmeta table entries for trainer profiles +SELECT + post_id, + meta_key, + meta_value +FROM wp_postmeta +WHERE post_id IN ( + SELECT ID FROM wp_posts WHERE post_type = 'trainer_profile' +) +ORDER BY post_id, meta_key; +``` + +### Taxonomy Relationships +```sql +-- Get trainer profiles with business type taxonomy +SELECT + p.ID, + p.post_title, + t.name as business_type +FROM wp_posts p +LEFT JOIN wp_term_relationships tr ON p.ID = tr.object_id +LEFT JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id +LEFT JOIN wp_terms t ON tt.term_id = t.term_id +WHERE p.post_type = 'trainer_profile' +AND tt.taxonomy = 'business_type'; +``` + +### Key Database Queries + +#### Get Public Trainer Profiles +```php +$args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => 'is_public_profile', + 'value' => '1', + 'compare' => '=' + ], + [ + 'key' => 'user_id', + 'value' => $approved_user_ids, + 'compare' => 'IN' + ] + ] +]; +$query = new WP_Query($args); +``` + +#### Get Geocoded Trainers for Map +```php +$args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => 'is_public_profile', + 'value' => '1', + 'compare' => '=' + ], + [ + 'key' => 'geocoded_lat', + 'compare' => 'EXISTS' + ], + [ + 'key' => 'geocoded_lng', + 'compare' => 'EXISTS' + ] + ] +]; +``` + +## Performance Optimizations + +### MapGeo Integration Optimizations + +#### Request Deduplication +```javascript +// Prevent duplicate AJAX requests +window.hvacTrainerDataCache = {}; +window.hvacPendingRequests = {}; + +function getTrainerProfile(profileId) { + // Check cache first + if (window.hvacTrainerDataCache[profileId]) { + return Promise.resolve(window.hvacTrainerDataCache[profileId]); + } + + // Check if request already pending + if (window.hvacPendingRequests[profileId]) { + return window.hvacPendingRequests[profileId]; + } + + // Make new request and cache promise + window.hvacPendingRequests[profileId] = makeAjaxRequest(profileId) + .then(function(data) { + window.hvacTrainerDataCache[profileId] = data; + delete window.hvacPendingRequests[profileId]; + return data; + }); + + return window.hvacPendingRequests[profileId]; +} +``` + +#### Click Throttling +```javascript +// Prevent rapid-fire clicking +var lastClickTime = 0; +function handleMarkerClick(e) { + var now = Date.now(); + if (now - lastClickTime < 500) { + return; // Throttle clicks to 500ms intervals + } + lastClickTime = now; + + // Process click + processMarkerClick(e); +} +``` + +### Database Query Optimization + +#### Efficient Trainer Queries +```php +// Use meta_query for better performance with indexes +$args = [ + 'post_type' => 'trainer_profile', + 'posts_per_page' => 12, + 'paged' => $page, + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => 'is_public_profile', + 'value' => '1', + 'compare' => '=' + ] + ], + 'fields' => 'ids' // Only get IDs when possible +]; + +// Add user status filter efficiently +$user_query = new WP_User_Query([ + 'meta_query' => [ + [ + 'key' => 'account_status', + 'value' => ['approved', 'active', 'inactive'], + 'compare' => 'IN' + ] + ], + 'fields' => 'ID' +]); + +$approved_user_ids = $user_query->get_results(); +if (!empty($approved_user_ids)) { + $args['meta_query'][] = [ + 'key' => 'user_id', + 'value' => $approved_user_ids, + 'compare' => 'IN' + ]; +} +``` + +#### Caching Strategies +```php +// Cache expensive queries +$cache_key = 'hvac_public_trainers_' . md5(serialize($args)); +$trainers = wp_cache_get($cache_key, 'hvac_trainers'); + +if (false === $trainers) { + $query = new WP_Query($args); + $trainers = $query->posts; + wp_cache_set($cache_key, $trainers, 'hvac_trainers', HOUR_IN_SECONDS); +} +``` + +## Troubleshooting + +### Common Issues + +#### Trainers Not Appearing on Map +**Symptoms**: Trainers visible in directory but not on MapGeo map +**Causes**: +1. Missing geocoding data (latitude/longitude) +2. MapGeo configuration issues +3. Profile not public + +**Solutions**: +```php +// Check geocoding status +$lat = get_post_meta($profile_id, 'geocoded_lat', true); +$lng = get_post_meta($profile_id, 'geocoded_lng', true); + +if (empty($lat) || empty($lng)) { + // Trigger geocoding + $geocoding_service = HVAC_Geocoding_Service::get_instance(); + $geocoding_service->geocode_trainer($profile_id); +} + +// Check public status +$is_public = get_post_meta($profile_id, 'is_public_profile', true); +if ($is_public !== '1') { + update_post_meta($profile_id, 'is_public_profile', '1'); +} +``` + +#### Modal Not Opening +**Symptoms**: Clicking map markers doesn't open trainer modal +**Causes**: +1. JavaScript errors preventing modal system +2. Missing trainer data +3. Champion profiles (intentionally no modal) + +**Solutions**: +```javascript +// Debug modal system +console.log('MapGeo integration loaded:', typeof window.hvac_show_trainer_modal); +console.log('Modal function available:', typeof window.showTrainerModal); + +// Check if trainer is Champion (no modal should show) +if (trainerData.certification_type === 'Certified measureQuick Champion') { + console.log('Champion detected - no modal shown'); + return; +} +``` + +#### Search/Filter Not Working +**Symptoms**: Search and filters not returning results +**Causes**: +1. AJAX endpoint errors +2. Nonce verification failures +3. Database query issues + +**Solutions**: +```php +// Debug AJAX handlers +add_action('wp_ajax_hvac_filter_trainers', function() { + error_log('Filter trainers AJAX called'); + error_log('POST data: ' . print_r($_POST, true)); + + // Verify nonce + if (!wp_verify_nonce($_POST['nonce'], 'hvac_find_trainer')) { + error_log('Nonce verification failed'); + wp_send_json_error('Invalid nonce'); + return; + } + + // Continue with handler... +}); +``` + +#### Performance Issues +**Symptoms**: Slow loading times, multiple AJAX requests +**Causes**: +1. Duplicate requests not prevented +2. Missing caching +3. Inefficient database queries + +**Solutions**: +1. Implement request deduplication +2. Add caching layers +3. Optimize database queries with proper indexes +4. Use pagination for large datasets + +### Debug Mode + +Enable debug logging for troubleshooting: + +```php +// Add to wp-config.php +define('HVAC_DEBUG', true); + +// In plugin code +if (defined('HVAC_DEBUG') && HVAC_DEBUG) { + error_log('HVAC Debug: ' . $message); +} +``` + +### Performance Monitoring + +Monitor system performance: + +```javascript +// Track AJAX request timing +var startTime = performance.now(); +jQuery.ajax({ + // ... ajax config + success: function(response) { + var endTime = performance.now(); + console.log('AJAX request took:', (endTime - startTime), 'milliseconds'); + } +}); +``` + +## Conclusion + +The HVAC Trainer System provides a comprehensive solution for managing trainer profiles and presenting them through an interactive Find A Trainer directory. The system is designed for performance, scalability, and user experience, with extensive customization options and robust error handling. + +For additional support or feature requests, refer to the main plugin documentation or contact the development team. \ No newline at end of file diff --git a/docs/TRAINER-TROUBLESHOOTING.md b/docs/TRAINER-TROUBLESHOOTING.md new file mode 100644 index 00000000..0628ae4d --- /dev/null +++ b/docs/TRAINER-TROUBLESHOOTING.md @@ -0,0 +1,1313 @@ +# HVAC Trainer System Troubleshooting Guide + +This guide provides solutions for common issues with the HVAC Trainer custom post type and Find A Trainer page functionality. + +## Table of Contents + +1. [Trainer Profile Issues](#trainer-profile-issues) +2. [Find A Trainer Page Issues](#find-a-trainer-page-issues) +3. [MapGeo Integration Issues](#mapgeo-integration-issues) +4. [Performance Issues](#performance-issues) +5. [Database Issues](#database-issues) +6. [API & AJAX Issues](#api--ajax-issues) +7. [Geocoding Issues](#geocoding-issues) +8. [Permission Issues](#permission-issues) +9. [Debug Tools](#debug-tools) +10. [Known Issues](#known-issues) + +## Trainer Profile Issues + +### Trainer Profiles Not Appearing + +**Symptoms**: Trainer profiles exist but don't show in directory or admin list + +**Common Causes**: +1. Profile not set to public +2. User account not approved +3. Missing required meta fields +4. Wrong post status + +**Solutions**: + +```php +// Check profile visibility +$profile_id = 5840; +$is_public = get_post_meta($profile_id, 'is_public_profile', true); +if ($is_public !== '1') { + update_post_meta($profile_id, 'is_public_profile', '1'); + echo "Profile set to public"; +} + +// Check user approval status +$user_id = get_post_meta($profile_id, 'user_id', true); +$user_status = get_user_meta($user_id, 'account_status', true); +if (!in_array($user_status, ['approved', 'active', 'inactive'])) { + update_user_meta($user_id, 'account_status', 'approved'); + echo "User approved"; +} + +// Check post status +$post = get_post($profile_id); +if ($post->post_status !== 'publish') { + wp_update_post([ + 'ID' => $profile_id, + 'post_status' => 'publish' + ]); + echo "Profile published"; +} +``` + +### Profile Data Not Saving + +**Symptoms**: Changes to trainer profiles don't persist + +**Common Causes**: +1. Permission issues +2. Nonce verification failures +3. Invalid data validation +4. Database errors + +**Solutions**: + +```php +// Debug profile save operation +add_action('hvac_trainer_profile_updated', function($profile_id, $old_data, $new_data) { + error_log("Profile {$profile_id} updated"); + error_log("Old data: " . print_r($old_data, true)); + error_log("New data: " . print_r($new_data, true)); +}, 10, 3); + +// Check user permissions +if (!current_user_can('edit_hvac_profile')) { + echo "User lacks edit_hvac_profile capability"; + // Grant capability temporarily for testing + $user = wp_get_current_user(); + $user->add_cap('edit_hvac_profile'); +} + +// Test direct meta update +$result = update_post_meta($profile_id, 'trainer_city', 'Test City'); +if ($result === false) { + echo "Failed to update meta field"; +} else { + echo "Meta field updated successfully"; +} +``` + +### Profile Images Not Displaying + +**Symptoms**: Profile images appear broken or don't load + +**Common Causes**: +1. Invalid image URLs +2. Missing image files +3. Permission issues on uploads +4. SSL/HTTPS conflicts + +**Solutions**: + +```php +// Validate image URLs +$profile_id = 5840; +$image_url = get_post_meta($profile_id, 'profile_image_url', true); + +if (!empty($image_url)) { + // Check if URL is accessible + $response = wp_remote_head($image_url); + if (is_wp_error($response)) { + echo "Image URL not accessible: " . $response->get_error_message(); + } else { + $response_code = wp_remote_retrieve_response_code($response); + if ($response_code !== 200) { + echo "Image URL returns HTTP {$response_code}"; + } else { + echo "Image URL is valid and accessible"; + } + } + + // Fix HTTP/HTTPS issues + if (is_ssl() && strpos($image_url, 'http://') === 0) { + $fixed_url = str_replace('http://', 'https://', $image_url); + update_post_meta($profile_id, 'profile_image_url', $fixed_url); + echo "Fixed HTTPS issue in image URL"; + } +} else { + echo "No profile image URL set"; +} +``` + +## Find A Trainer Page Issues + +### Search Not Working + +**Symptoms**: Search functionality returns no results or doesn't respond + +**Common Causes**: +1. AJAX endpoints not registered +2. JavaScript errors +3. Nonce verification issues +4. Database query problems + +**Solutions**: + +```php +// Check AJAX endpoint registration +add_action('wp_ajax_hvac_search_trainers', function() { + echo "AJAX endpoint registered for logged-in users"; + wp_die(); +}); + +add_action('wp_ajax_nopriv_hvac_search_trainers', function() { + echo "AJAX endpoint registered for non-logged-in users"; + wp_die(); +}); + +// Debug search query +add_action('wp_ajax_hvac_search_trainers', function() { + error_log('Search AJAX called with data: ' . print_r($_POST, true)); + + // Test basic query + $args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => 5 + ]; + + $query = new WP_Query($args); + error_log("Basic query found {$query->found_posts} profiles"); + + wp_send_json_success([ + 'found_posts' => $query->found_posts, + 'debug' => true + ]); +}); +``` + +**JavaScript Debug**: + +```javascript +// Test AJAX endpoint +jQuery.ajax({ + url: hvac_find_trainer.ajax_url, + method: 'POST', + data: { + action: 'hvac_search_trainers', + search: 'test', + nonce: hvac_find_trainer.nonce + }, + success: function(response) { + console.log('Search test successful:', response); + }, + error: function(jqXHR, textStatus, errorThrown) { + console.error('Search test failed:', textStatus, errorThrown); + console.error('Response text:', jqXHR.responseText); + } +}); +``` + +### Filters Not Working + +**Symptoms**: Filter dropdowns don't affect results + +**Common Causes**: +1. Taxonomy terms not assigned +2. Filter value mismatches +3. Query argument issues +4. Cache problems + +**Solutions**: + +```php +// Check taxonomy assignments +$profile_id = 5840; +$business_types = wp_get_post_terms($profile_id, 'business_type', ['fields' => 'names']); + +if (empty($business_types)) { + echo "No business type assigned to profile {$profile_id}"; + + // Assign a business type for testing + wp_set_post_terms($profile_id, ['Contractor'], 'business_type'); + echo "Assigned 'Contractor' business type"; +} else { + echo "Business types: " . implode(', ', $business_types); +} + +// Debug filter query +function debug_trainer_filter_query($args, $filters) { + error_log("Filter args: " . print_r($args, true)); + error_log("Applied filters: " . print_r($filters, true)); + + // Test the query + $query = new WP_Query($args); + error_log("Filter query found {$query->found_posts} results"); + + return $args; +} + +add_filter('hvac_trainer_query_args', 'debug_trainer_filter_query', 10, 2); +``` + +### Pagination Not Working + +**Symptoms**: Pagination links don't work or show incorrect page counts + +**Common Causes**: +1. Incorrect pagination calculations +2. JavaScript event handling issues +3. AJAX parameter problems + +**Solutions**: + +```php +// Debug pagination calculations +function debug_pagination($query) { + if ($query->get('post_type') === 'trainer_profile') { + error_log("Pagination debug:"); + error_log("Found posts: " . $query->found_posts); + error_log("Posts per page: " . $query->get('posts_per_page')); + error_log("Max pages: " . $query->max_num_pages); + error_log("Current page: " . $query->get('paged')); + } +} + +add_action('pre_get_posts', 'debug_pagination'); +``` + +**JavaScript Debug**: + +```javascript +// Test pagination AJAX +jQuery(document).on('click', '.hvac-page-link', function(e) { + e.preventDefault(); + var page = jQuery(this).data('page'); + + console.log('Pagination clicked, page:', page); + + // Test the AJAX call + jQuery.ajax({ + url: hvac_find_trainer.ajax_url, + method: 'POST', + data: { + action: 'hvac_filter_trainers', + filters: HVAC_FindTrainer.config.filters, + page: page, + per_page: 12, + nonce: hvac_find_trainer.nonce + }, + success: function(response) { + console.log('Pagination AJAX success:', response); + }, + error: function(jqXHR, textStatus, errorThrown) { + console.error('Pagination AJAX failed:', textStatus, errorThrown); + } + }); +}); +``` + +## MapGeo Integration Issues + +### Markers Not Appearing on Map + +**Symptoms**: Map loads but no trainer markers are visible + +**Common Causes**: +1. Missing geocoding data (latitude/longitude) +2. MapGeo configuration issues +3. Profile visibility settings +4. Map ID mismatch + +**Solutions**: + +```php +// Check geocoding status for all trainers +function check_geocoding_status() { + $args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => -1 + ]; + + $query = new WP_Query($args); + $total = $query->found_posts; + $geocoded = 0; + $missing_coords = []; + + while ($query->have_posts()) { + $query->the_post(); + $profile_id = get_the_ID(); + + $lat = get_post_meta($profile_id, 'geocoded_lat', true); + $lng = get_post_meta($profile_id, 'geocoded_lng', true); + + if (!empty($lat) && !empty($lng)) { + $geocoded++; + } else { + $missing_coords[] = [ + 'id' => $profile_id, + 'title' => get_the_title(), + 'city' => get_post_meta($profile_id, 'trainer_city', true), + 'state' => get_post_meta($profile_id, 'trainer_state', true) + ]; + } + } + + wp_reset_postdata(); + + echo "Geocoding Status Report:\n"; + echo "Total profiles: {$total}\n"; + echo "Geocoded profiles: {$geocoded}\n"; + echo "Missing coordinates: " . count($missing_coords) . "\n"; + + if (!empty($missing_coords)) { + echo "\nProfiles missing coordinates:\n"; + foreach ($missing_coords as $profile) { + echo "- {$profile['title']} (ID: {$profile['id']}) - {$profile['city']}, {$profile['state']}\n"; + } + } +} + +// Run the check +check_geocoding_status(); + +// Trigger geocoding for profiles missing coordinates +function fix_missing_geocoding() { + $args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'meta_query' => [ + 'relation' => 'OR', + [ + 'key' => 'geocoded_lat', + 'compare' => 'NOT EXISTS' + ], + [ + 'key' => 'geocoded_lng', + 'compare' => 'NOT EXISTS' + ] + ] + ]; + + $query = new WP_Query($args); + + if (class_exists('HVAC_Geocoding_Service')) { + $geocoding = HVAC_Geocoding_Service::get_instance(); + + while ($query->have_posts()) { + $query->the_post(); + $profile_id = get_the_ID(); + + echo "Attempting to geocode profile {$profile_id}..."; + $result = $geocoding->geocode_trainer($profile_id); + + if ($result) { + echo " SUCCESS\n"; + } else { + echo " FAILED\n"; + } + + // Rate limiting + sleep(1); + } + } + + wp_reset_postdata(); +} +``` + +### Map Modal Not Opening + +**Symptoms**: Clicking map markers doesn't open trainer modals + +**Common Causes**: +1. JavaScript errors preventing modal system +2. Missing trainer data in modal correlation +3. Champions (intentionally don't show modals) +4. MapGeo custom action configuration + +**Solutions**: + +```javascript +// Debug modal system +console.log('MapGeo Integration Debug:'); +console.log('hvac_show_trainer_modal function:', typeof window.hvac_show_trainer_modal); +console.log('showTrainerModal function:', typeof window.showTrainerModal); +console.log('Cache object exists:', typeof window.hvacTrainerDataCache); + +// Test modal function directly +if (typeof window.hvac_show_trainer_modal === 'function') { + // Test with sample data + window.hvac_show_trainer_modal({ + hvac_profile_id: '5840', + id: 'trainer_5840', + title: 'Test Trainer' + }); +} else { + console.error('hvac_show_trainer_modal function not found'); +} + +// Check for JavaScript errors +window.addEventListener('error', function(e) { + if (e.filename && e.filename.includes('mapgeo')) { + console.error('MapGeo JavaScript error:', e.message, e.filename, e.lineno); + } +}); + +// Debug marker click events +jQuery(document).on('click', '.imapsSprite-group, .imapsMapObject-group, [class*="imaps"], circle', function(e) { + console.log('Marker clicked:', this); + console.log('Last MapGeo data:', window.lastMapGeoTrainerData); +}); +``` + +**PHP Debug**: + +```php +// Check MapGeo map configuration +add_filter('igm_add_meta', function($meta, $map_id) { + if ($map_id == '5872') { // Your map ID + error_log("MapGeo meta data for map {$map_id}:"); + error_log(print_r($meta, true)); + + // Check if markers have trainer data + if (isset($meta['markers'])) { + foreach ($meta['markers'] as $i => $marker) { + if (isset($marker['hvac_profile_id'])) { + error_log("Marker {$i} has trainer profile ID: " . $marker['hvac_profile_id']); + } else { + error_log("Marker {$i} missing trainer profile ID"); + } + } + } + } + return $meta; +}, 10, 2); +``` + +### Modal Performance Issues + +**Symptoms**: Modal takes >5 seconds to load or shows multiple AJAX requests + +**Common Causes**: +1. Duplicate AJAX requests +2. Missing caching +3. Complex fallback logic + +**Solutions**: + +```javascript +// Check for duplicate requests +var requestTracker = {}; + +// Override jQuery.ajax to track requests +var originalAjax = jQuery.ajax; +jQuery.ajax = function(options) { + if (options.data && options.data.action === 'hvac_get_trainer_profile') { + var profileId = options.data.profile_id; + var requestKey = 'profile_' + profileId; + + if (requestTracker[requestKey]) { + console.warn('Duplicate AJAX request prevented for profile:', profileId); + return requestTracker[requestKey]; + } + + console.log('New AJAX request for profile:', profileId); + requestTracker[requestKey] = originalAjax.call(this, options); + + // Clean up after request completes + requestTracker[requestKey].always(function() { + delete requestTracker[requestKey]; + }); + + return requestTracker[requestKey]; + } + + return originalAjax.call(this, options); +}; + +// Monitor cache usage +setInterval(function() { + if (window.hvacTrainerDataCache) { + var cacheSize = Object.keys(window.hvacTrainerDataCache).length; + console.log('Trainer data cache size:', cacheSize); + } +}, 5000); +``` + +## Performance Issues + +### Slow Page Loading + +**Symptoms**: Find A Trainer page loads slowly + +**Common Causes**: +1. Large number of trainer profiles +2. Inefficient database queries +3. Missing indexes +4. Large images + +**Solutions**: + +```php +// Add database indexes for better performance +function add_trainer_performance_indexes() { + global $wpdb; + + // Index for trainer profile queries + $wpdb->query(" + CREATE INDEX IF NOT EXISTS idx_postmeta_trainer_queries + ON {$wpdb->postmeta} (meta_key, meta_value(50), post_id) + "); + + // Index for geocoded trainer queries + $wpdb->query(" + CREATE INDEX IF NOT EXISTS idx_posts_trainer_type_status + ON {$wpdb->posts} (post_type, post_status, post_date) + "); + + echo "Performance indexes added"; +} + +// Optimize trainer queries with caching +function get_cached_trainers($args) { + $cache_key = 'hvac_trainers_' . md5(serialize($args)); + $trainers = wp_cache_get($cache_key, 'hvac_trainers'); + + if (false === $trainers) { + $query = new WP_Query($args); + $trainers = []; + + while ($query->have_posts()) { + $query->the_post(); + $trainers[] = [ + 'id' => get_the_ID(), + 'title' => get_the_title(), + 'city' => get_post_meta(get_the_ID(), 'trainer_city', true), + // ... other fields + ]; + } + wp_reset_postdata(); + + wp_cache_set($cache_key, $trainers, 'hvac_trainers', HOUR_IN_SECONDS); + } + + return $trainers; +} + +// Profile query optimization +function optimize_trainer_profile_queries($args) { + // Use 'fields' => 'ids' when only IDs are needed + if (!isset($args['fields'])) { + $args['fields'] = 'ids'; + } + + // Add specific meta_query structure for better index usage + if (isset($args['meta_query'])) { + // Ensure is_public_profile check is first for index efficiency + usort($args['meta_query'], function($a, $b) { + if (isset($a['key']) && $a['key'] === 'is_public_profile') { + return -1; + } + if (isset($b['key']) && $b['key'] === 'is_public_profile') { + return 1; + } + return 0; + }); + } + + return $args; +} + +add_filter('hvac_trainer_query_args', 'optimize_trainer_profile_queries'); +``` + +### Memory Issues + +**Symptoms**: PHP memory limit exceeded or slow performance + +**Solutions**: + +```php +// Monitor memory usage +function check_memory_usage($context = '') { + $memory_used = memory_get_usage(true); + $memory_peak = memory_get_peak_usage(true); + $memory_limit = ini_get('memory_limit'); + + error_log("Memory usage {$context}: " . + "Current: " . size_format($memory_used) . + ", Peak: " . size_format($memory_peak) . + ", Limit: {$memory_limit}"); +} + +// Optimize large trainer queries +function get_trainers_in_batches($total_needed = -1, $batch_size = 50) { + $trainers = []; + $page = 1; + + do { + $args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => $batch_size, + 'paged' => $page, + 'fields' => 'ids' + ]; + + $query = new WP_Query($args); + + foreach ($query->posts as $profile_id) { + $trainers[] = $profile_id; + + if ($total_needed > 0 && count($trainers) >= $total_needed) { + break 2; + } + } + + wp_reset_postdata(); + $page++; + + check_memory_usage("after batch {$page}"); + + } while ($query->have_posts() && ($total_needed < 0 || count($trainers) < $total_needed)); + + return $trainers; +} +``` + +## Database Issues + +### Missing Meta Fields + +**Symptoms**: Trainer data appears incomplete or missing + +**Solutions**: + +```php +// Audit trainer profile meta fields +function audit_trainer_meta_fields() { + $required_fields = [ + 'user_id', + 'trainer_display_name', + 'trainer_city', + 'trainer_state', + 'is_public_profile', + 'certification_type' + ]; + + $args = [ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'fields' => 'ids' + ]; + + $profiles = get_posts($args); + $issues = []; + + foreach ($profiles as $profile_id) { + $profile_issues = []; + + foreach ($required_fields as $field) { + $value = get_post_meta($profile_id, $field, true); + if (empty($value)) { + $profile_issues[] = $field; + } + } + + if (!empty($profile_issues)) { + $issues[$profile_id] = [ + 'title' => get_the_title($profile_id), + 'missing_fields' => $profile_issues + ]; + } + } + + if (!empty($issues)) { + echo "Profiles with missing meta fields:\n"; + foreach ($issues as $profile_id => $issue) { + echo "Profile {$profile_id} ({$issue['title']}): " . + implode(', ', $issue['missing_fields']) . "\n"; + } + } else { + echo "All profiles have required meta fields\n"; + } + + return $issues; +} + +// Fix common meta field issues +function fix_trainer_meta_issues() { + $profiles = get_posts([ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'fields' => 'ids' + ]); + + foreach ($profiles as $profile_id) { + $updated = false; + + // Fix missing display name + $display_name = get_post_meta($profile_id, 'trainer_display_name', true); + if (empty($display_name)) { + $post_title = get_the_title($profile_id); + if (!empty($post_title)) { + update_post_meta($profile_id, 'trainer_display_name', $post_title); + $updated = true; + } + } + + // Fix missing public profile flag + $is_public = get_post_meta($profile_id, 'is_public_profile', true); + if ($is_public === '') { + update_post_meta($profile_id, 'is_public_profile', '1'); + $updated = true; + } + + // Fix missing certification type + $cert_type = get_post_meta($profile_id, 'certification_type', true); + if (empty($cert_type)) { + update_post_meta($profile_id, 'certification_type', 'HVAC Trainer'); + $updated = true; + } + + if ($updated) { + echo "Fixed meta fields for profile {$profile_id}\n"; + } + } +} +``` + +### Taxonomy Issues + +**Symptoms**: Filters not working, missing taxonomy terms + +**Solutions**: + +```php +// Check taxonomy registration +function check_trainer_taxonomies() { + $taxonomies = ['business_type', 'training_formats', 'training_resources', 'training_audience']; + + foreach ($taxonomies as $taxonomy) { + if (taxonomy_exists($taxonomy)) { + $terms = get_terms([ + 'taxonomy' => $taxonomy, + 'hide_empty' => false + ]); + + echo "Taxonomy '{$taxonomy}': " . count($terms) . " terms\n"; + + if (empty($terms)) { + echo " WARNING: No terms found for {$taxonomy}\n"; + } + } else { + echo "ERROR: Taxonomy '{$taxonomy}' not registered\n"; + } + } +} + +// Create missing taxonomy terms +function create_default_taxonomy_terms() { + $taxonomies = [ + 'business_type' => [ + 'Manufacturer', + 'Distributor', + 'Contractor', + 'Consultant', + 'Educator', + 'Government', + 'Other' + ], + 'training_formats' => [ + 'In-person', + 'Virtual', + 'Hybrid', + 'On-demand' + ], + 'training_resources' => [ + 'Classroom', + 'Training Lab', + 'Online Platform', + 'Custom Curriculum' + ] + ]; + + foreach ($taxonomies as $taxonomy => $terms) { + if (taxonomy_exists($taxonomy)) { + foreach ($terms as $term) { + if (!term_exists($term, $taxonomy)) { + $result = wp_insert_term($term, $taxonomy); + if (!is_wp_error($result)) { + echo "Created term '{$term}' in taxonomy '{$taxonomy}'\n"; + } + } + } + } + } +} +``` + +## API & AJAX Issues + +### AJAX Endpoints Not Working + +**Symptoms**: AJAX requests return 404 or 400 errors + +**Solutions**: + +```php +// Debug AJAX endpoint registration +function debug_ajax_endpoints() { + global $wp_filter; + + $ajax_actions = [ + 'hvac_get_trainer_profile', + 'hvac_filter_trainers', + 'hvac_search_trainers', + 'hvac_get_trainer_certification' + ]; + + foreach ($ajax_actions as $action) { + // Check logged-in user endpoints + $logged_in_hook = "wp_ajax_{$action}"; + if (isset($wp_filter[$logged_in_hook])) { + echo "✓ {$logged_in_hook} registered\n"; + } else { + echo "✗ {$logged_in_hook} NOT registered\n"; + } + + // Check non-logged-in user endpoints + $public_hook = "wp_ajax_nopriv_{$action}"; + if (isset($wp_filter[$public_hook])) { + echo "✓ {$public_hook} registered\n"; + } else { + echo "✗ {$public_hook} NOT registered\n"; + } + } +} + +// Test AJAX endpoint manually +function test_ajax_endpoint($action, $data = []) { + // Simulate AJAX request + $_POST = array_merge([ + 'action' => $action, + 'nonce' => wp_create_nonce('hvac_find_trainer') + ], $data); + + // Capture output + ob_start(); + do_action("wp_ajax_{$action}"); + do_action("wp_ajax_nopriv_{$action}"); + $output = ob_get_clean(); + + echo "AJAX Test for '{$action}':\n"; + echo "Output: {$output}\n"; + + // Clean up + $_POST = []; +} + +// Usage +test_ajax_endpoint('hvac_get_trainer_profile', ['profile_id' => 5840]); +``` + +### Nonce Verification Failures + +**Symptoms**: AJAX requests fail with "Invalid nonce" errors + +**Solutions**: + +```php +// Debug nonce issues +function debug_nonce_verification() { + $action = 'hvac_find_trainer'; + $nonce = wp_create_nonce($action); + + echo "Generated nonce: {$nonce}\n"; + echo "Nonce action: {$action}\n"; + echo "Current user ID: " . get_current_user_id() . "\n"; + + // Test verification + $verify_result = wp_verify_nonce($nonce, $action); + echo "Verification result: " . ($verify_result ? 'PASS' : 'FAIL') . "\n"; + + // Check nonce in JavaScript context + echo "JavaScript nonce check:\n"; + ?> + + 'Nonce verification failed', + 'debug' => [ + 'received_nonce' => $nonce, + 'expected_action' => $action, + 'user_id' => get_current_user_id() + ] + ]); + return; + } + + // Continue with normal processing... + }); +} +``` + +## Geocoding Issues + +### Trainers Not Being Geocoded + +**Symptoms**: Trainer profiles missing latitude/longitude coordinates + +**Solutions**: + +```php +// Check Google Maps API configuration +function check_geocoding_setup() { + $api_key = get_option('hvac_google_maps_api_key'); + + if (empty($api_key)) { + echo "ERROR: Google Maps API key not configured\n"; + echo "Set it with: update_option('hvac_google_maps_api_key', 'your_key_here');\n"; + return false; + } + + echo "Google Maps API key configured: " . substr($api_key, 0, 10) . "...\n"; + + // Test API key with a simple request + $test_address = 'Chicago, IL'; + $response = wp_remote_get('https://maps.googleapis.com/maps/api/geocode/json?' . http_build_query([ + 'address' => $test_address, + 'key' => $api_key + ])); + + if (is_wp_error($response)) { + echo "ERROR: API request failed: " . $response->get_error_message() . "\n"; + return false; + } + + $data = json_decode(wp_remote_retrieve_body($response), true); + + if (isset($data['status'])) { + echo "API test result: {$data['status']}\n"; + + if ($data['status'] === 'OK') { + echo "✓ Geocoding API is working correctly\n"; + return true; + } else { + echo "✗ API Error: " . ($data['error_message'] ?? 'Unknown error') . "\n"; + return false; + } + } + + return false; +} + +// Manual geocoding test +function test_geocoding_for_profile($profile_id) { + echo "Testing geocoding for profile {$profile_id}...\n"; + + $city = get_post_meta($profile_id, 'trainer_city', true); + $state = get_post_meta($profile_id, 'trainer_state', true); + + if (empty($city) || empty($state)) { + echo "ERROR: Missing city or state data\n"; + return false; + } + + $address = "{$city}, {$state}"; + echo "Address to geocode: {$address}\n"; + + $api_key = get_option('hvac_google_maps_api_key'); + if (empty($api_key)) { + echo "ERROR: No API key configured\n"; + return false; + } + + $response = wp_remote_get('https://maps.googleapis.com/maps/api/geocode/json?' . http_build_query([ + 'address' => $address, + 'key' => $api_key + ])); + + if (is_wp_error($response)) { + echo "ERROR: " . $response->get_error_message() . "\n"; + return false; + } + + $data = json_decode(wp_remote_retrieve_body($response), true); + + if ($data['status'] === 'OK' && !empty($data['results'][0])) { + $location = $data['results'][0]['geometry']['location']; + $lat = $location['lat']; + $lng = $location['lng']; + + echo "✓ Geocoding successful: {$lat}, {$lng}\n"; + + // Save coordinates + update_post_meta($profile_id, 'geocoded_lat', $lat); + update_post_meta($profile_id, 'geocoded_lng', $lng); + update_post_meta($profile_id, '_last_geocode_attempt', time()); + + echo "✓ Coordinates saved to profile\n"; + return true; + } else { + echo "✗ Geocoding failed: {$data['status']}\n"; + if (isset($data['error_message'])) { + echo "Error: {$data['error_message']}\n"; + } + return false; + } +} + +// Batch geocoding with rate limiting +function batch_geocode_missing_profiles($limit = 10) { + $profiles = get_posts([ + 'post_type' => 'trainer_profile', + 'post_status' => 'publish', + 'posts_per_page' => $limit, + 'meta_query' => [ + 'relation' => 'OR', + [ + 'key' => 'geocoded_lat', + 'compare' => 'NOT EXISTS' + ], + [ + 'key' => 'geocoded_lng', + 'compare' => 'NOT EXISTS' + ] + ], + 'fields' => 'ids' + ]); + + echo "Found " . count($profiles) . " profiles needing geocoding\n"; + + foreach ($profiles as $profile_id) { + $title = get_the_title($profile_id); + echo "\nProcessing: {$title} (ID: {$profile_id})\n"; + + test_geocoding_for_profile($profile_id); + + // Rate limiting - wait 1 second between requests + sleep(1); + } + + echo "\nBatch geocoding complete\n"; +} +``` + +## Permission Issues + +### Users Can't Edit Profiles + +**Symptoms**: Trainers can't access or edit their profiles + +**Solutions**: + +```php +// Check user capabilities +function check_trainer_capabilities($user_id = null) { + if (!$user_id) { + $user_id = get_current_user_id(); + } + + $user = get_user_by('id', $user_id); + if (!$user) { + echo "User not found\n"; + return; + } + + echo "User: {$user->display_name} (ID: {$user_id})\n"; + echo "Roles: " . implode(', ', $user->roles) . "\n"; + + $required_caps = [ + 'read', + 'edit_hvac_profile', + 'view_hvac_dashboard', + 'manage_hvac_events' + ]; + + foreach ($required_caps as $cap) { + $has_cap = user_can($user_id, $cap); + echo "Capability '{$cap}': " . ($has_cap ? '✓' : '✗') . "\n"; + + if (!$has_cap) { + // Grant capability for testing + $user->add_cap($cap); + echo " → Granted capability '{$cap}'\n"; + } + } +} + +// Fix role capabilities +function fix_trainer_role_capabilities() { + $trainer_role = get_role('hvac_trainer'); + $master_role = get_role('hvac_master_trainer'); + + if (!$trainer_role) { + echo "Creating hvac_trainer role...\n"; + $trainer_role = add_role('hvac_trainer', 'HVAC Trainer', [ + 'read' => true, + 'edit_hvac_profile' => true, + 'view_hvac_dashboard' => true, + 'manage_hvac_events' => true, + 'upload_files' => true + ]); + } + + if (!$master_role) { + echo "Creating hvac_master_trainer role...\n"; + $master_role = add_role('hvac_master_trainer', 'HVAC Master Trainer', [ + 'read' => true, + 'edit_hvac_profile' => true, + 'view_hvac_dashboard' => true, + 'manage_hvac_events' => true, + 'upload_files' => true, + 'hvac_master_trainer' => true + ]); + } + + // Add missing capabilities + $trainer_caps = [ + 'edit_hvac_profile', + 'view_hvac_dashboard', + 'manage_hvac_events', + 'manage_attendees', + 'email_attendees' + ]; + + foreach ($trainer_caps as $cap) { + if ($trainer_role && !$trainer_role->has_cap($cap)) { + $trainer_role->add_cap($cap); + echo "Added '{$cap}' to hvac_trainer role\n"; + } + + if ($master_role && !$master_role->has_cap($cap)) { + $master_role->add_cap($cap); + echo "Added '{$cap}' to hvac_master_trainer role\n"; + } + } +} +``` + +## Debug Tools + +### Enable Debug Logging + +```php +// Add to wp-config.php +define('WP_DEBUG', true); +define('WP_DEBUG_LOG', true); +define('WP_DEBUG_DISPLAY', false); +define('HVAC_DEBUG', true); + +// Custom debug logging +function hvac_debug_log($message, $context = '') { + if (defined('HVAC_DEBUG') && HVAC_DEBUG) { + $timestamp = date('Y-m-d H:i:s'); + $log_message = "[{$timestamp}] HVAC Debug"; + + if (!empty($context)) { + $log_message .= " ({$context})"; + } + + $log_message .= ": {$message}"; + + error_log($log_message); + } +} + +// Usage +hvac_debug_log('Trainer profile created: ' . $profile_id, 'Profile Manager'); +``` + +### Debug Information Panel + +```php +// Add debug info to admin +function hvac_add_debug_panel() { + if (current_user_can('manage_options') && isset($_GET['hvac_debug'])) { + echo '
'; + echo '

HVAC Trainer System Debug Info

'; + + // Plugin version + echo '

Plugin Version: ' . (defined('HVAC_PLUGIN_VERSION') ? HVAC_PLUGIN_VERSION : 'Unknown') . '

'; + + // Active trainers + $trainer_count = wp_count_posts('trainer_profile'); + echo '

Total Trainer Profiles: ' . $trainer_count->publish . '

'; + + // Geocoded trainers + $geocoded = get_posts([ + 'post_type' => 'trainer_profile', + 'posts_per_page' => -1, + 'meta_query' => [ + 'relation' => 'AND', + ['key' => 'geocoded_lat', 'compare' => 'EXISTS'], + ['key' => 'geocoded_lng', 'compare' => 'EXISTS'] + ], + 'fields' => 'ids' + ]); + echo '

Geocoded Trainers: ' . count($geocoded) . '

'; + + // MapGeo integration + echo '

MapGeo Active: ' . (class_exists('HVAC_MapGeo_Integration') ? 'Yes' : 'No') . '

'; + + // Google Maps API + $api_key = get_option('hvac_google_maps_api_key'); + echo '

Google Maps API: ' . (!empty($api_key) ? 'Configured' : 'Not Configured') . '

'; + + echo '
'; + } +} + +add_action('admin_notices', 'hvac_add_debug_panel'); +``` + +## Known Issues + +### MapGeo Certification Color Field Bug + +**Issue**: When `certification_color` field is mapped to MapGeo "Fill Colour", markers disappear completely. + +**Workaround**: Remove `certification_color` from MapGeo Fill Colour mapping. Colors are handled programmatically instead. + +**Status**: Documented as known bug. MapGeo plugin has compatibility issues with custom color fields. + +### Champions Don't Show Modals + +**Issue**: measureQuick Champions don't show modal popups when clicked on map. + +**Status**: This is intentional behavior. Only Certified measureQuick Trainers show modals. Champions are directory-only display. + +### High Memory Usage with Large Datasets + +**Issue**: Sites with 500+ trainer profiles may experience memory issues. + +**Workaround**: +- Implement pagination with smaller page sizes +- Use query optimization techniques +- Consider caching strategies +- Use `fields => 'ids'` in queries when possible + +This troubleshooting guide covers the most common issues encountered with the HVAC Trainer system. For additional support, enable debug logging and use the provided debug tools to gather more specific information about your particular issue. \ No newline at end of file diff --git a/docs/WELCOME-POPUP-SYSTEM.md b/docs/WELCOME-POPUP-SYSTEM.md new file mode 100644 index 00000000..b4e2640d --- /dev/null +++ b/docs/WELCOME-POPUP-SYSTEM.md @@ -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. \ No newline at end of file diff --git a/hvac-community-events.php b/hvac-community-events.php index 5cee6140..4ffd9181 100644 --- a/hvac-community-events.php +++ b/hvac-community-events.php @@ -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+ diff --git a/includes/class-hvac-astra-integration.php b/includes/class-hvac-astra-integration.php index 7af81b95..39a879db 100644 --- a/includes/class-hvac-astra-integration.php +++ b/includes/class-hvac-astra-integration.php @@ -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,66 +184,118 @@ class HVAC_Astra_Integration { */ public function add_hvac_dynamic_css($css) { if ($this->is_hvac_page()) { - $hvac_css = ' - /* HVAC Full-width overrides for Astra */ - .hvac-astra-integrated .ast-container { - max-width: 100% !important; - width: 100% !important; - padding-left: 40px !important; - padding-right: 40px !important; - } - - .hvac-astra-integrated .site-content .ast-container { - max-width: 100% !important; - } - - .hvac-astra-integrated .hvac-page-wrapper { - max-width: 1920px; - margin: 0 auto; - } - - /* Remove sidebar completely */ - .hvac-astra-integrated .widget-area, - .hvac-astra-integrated .ast-sidebar, - .hvac-astra-integrated #secondary, - .hvac-astra-integrated aside.widget-area, - .hvac-astra-integrated .sidebar-main { - display: none !important; - width: 0 !important; - height: 0 !important; - visibility: hidden !important; - position: absolute !important; - left: -9999px !important; - } - - /* Full-width content area */ - .hvac-astra-integrated #primary, - .hvac-astra-integrated .site-main, - .hvac-astra-integrated .content-area { - width: 100% !important; - max-width: 100% !important; - margin: 0 !important; - float: none !important; - display: block !important; - } - - /* Force single column layout */ - .hvac-astra-integrated .ast-container > .ast-row { - display: block !important; - } - - .hvac-astra-integrated .ast-col-md-8, - .hvac-astra-integrated .ast-col-lg-8 { - width: 100% !important; - max-width: 100% !important; - } - - /* Ensure content takes full width */ - .hvac-astra-integrated .entry-content { - width: 100% !important; - max-width: 100% !important; - } - '; + 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 { + max-width: 100% !important; + width: 100% !important; + padding-left: 40px !important; + padding-right: 40px !important; + } + + .hvac-astra-integrated .site-content .ast-container { + max-width: 100% !important; + } + + .hvac-astra-integrated .hvac-page-wrapper { + max-width: 1920px; + margin: 0 auto; + } + + /* Remove sidebar completely */ + .hvac-astra-integrated .widget-area, + .hvac-astra-integrated .ast-sidebar, + .hvac-astra-integrated #secondary, + .hvac-astra-integrated aside.widget-area, + .hvac-astra-integrated .sidebar-main { + display: none !important; + width: 0 !important; + height: 0 !important; + visibility: hidden !important; + position: absolute !important; + left: -9999px !important; + } + + /* Full-width content area */ + .hvac-astra-integrated #primary, + .hvac-astra-integrated .site-main, + .hvac-astra-integrated .content-area { + width: 100% !important; + max-width: 100% !important; + margin: 0 !important; + float: none !important; + display: block !important; + } + + /* Force single column layout */ + .hvac-astra-integrated .ast-container > .ast-row { + display: block !important; + } + + .hvac-astra-integrated .ast-col-md-8, + .hvac-astra-integrated .ast-col-lg-8 { + width: 100% !important; + max-width: 100% !important; + } + + /* Ensure content takes full width */ + .hvac-astra-integrated .entry-content { + width: 100% !important; + 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 */ diff --git a/includes/class-hvac-help-system.php b/includes/class-hvac-help-system.php index 72eeb8b4..2a174851 100644 --- a/includes/class-hvac-help-system.php +++ b/includes/class-hvac-help-system.php @@ -245,91 +245,143 @@ class HVAC_Help_System { return '

Getting Started

+
+

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.

+

1. Your Dashboard is Home Base

-

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!

- Go to Dashboard +

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.

+ Go to Dashboard
-

2. Create Your First Event

-

Click "Create Event" from any page. Fill in the simple form - event title, description, date, and pricing. Your event saves as a draft automatically.

- Create Event +

2. Update Your Profile First

+

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.

+ Edit Profile
-

3. Complete Your Profile

-

Add your credentials and business info to build trust with trainees. A complete profile helps your events get found and booked faster.

- Edit Profile +

3. Create Your First Event

+

Ready to host training? Create events with pricing, capacity limits, and venue information. Your events integrate with The Events Calendar for seamless management.

+ Create Event
-

Managing Events

+

Managing Events & Training

-

Creating Events is Simple

-
    -
  1. Click "Create Event" from your dashboard or navigation menu
  2. -
  3. Fill the form: Title, description, date/time, venue, and pricing
  4. -
  5. Save as Draft: Review and edit anytime before publishing
  6. -
  7. Publish: Your event goes live immediately
  8. -
-
-
-

What You Can Do

+

Integrated Event System

+

Our platform integrates with The Events Calendar to provide comprehensive event management:

    -
  • Edit Events: Click any event title to modify details
  • -
  • Set Capacity: Control how many can register
  • -
  • Track Sales: See registrations in real-time
  • -
  • Quick Actions: View, edit, or check attendees with one click
  • +
  • Training Events: Sessions with pricing, capacity, and location
  • +
  • Venues: Physical or virtual training locations
  • +
  • Organizers: Business entities with logos and contact info
  • +
  • Revenue Tracking: Monitor training income and attendance
-

Event Summary Page

-

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.

+

Quick Event Management

+
    +
  1. Dashboard Overview: See all your events at a glance
  2. +
  3. New Event: Click to create from navigation menu
  4. +
  5. Event Management: Access manage page for detailed controls
  6. +
  7. Analytics: View performance metrics and revenue data
  8. +
+
+
+

Venues & Organizers

+

Manage your training infrastructure:

+
    +
  • Training Venues: Add locations where you conduct training
  • +
  • Training Organizers: Set up business entities with branding
  • +
  • Auto-Creation: We\'ve tried to auto-create these for you!
  • +
-
-

Attendee Management

+
+

Your Professional Profile

-

See Who\'s Coming

-

Your dashboard shows registration counts. Click "View Attendees" on any event to see the full list with names, emails, and check-in status.

+

Directory Listing

+

Your profile appears in our "Find A Trainer" directory where potential students can discover you:

+
    +
  • Map Integration: Shows your location and service areas
  • +
  • Contact Form: Students can reach out directly
  • +
  • QR Code Sharing: Easy sharing at events and conferences
  • +
  • Professional Showcase: Display credentials and experience
  • +
-

Easy Email Communication

-

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.

+

Training Leads

+

When potential students find you in the directory, they can submit contact requests that appear in your Training Leads page:

+
    +
  • Lead Management: Track contact requests from potential clients
  • +
  • Status Tracking: Mark leads as read, replied, or archived
  • +
  • Direct Contact: Email and phone information included
  • +
  • Message History: View full inquiry details
  • +
+ View Training Leads
-

Quick Check-In

-

During your event, use the attendee list to check people in. This helps track completion for certificates and keeps accurate records.

+

Pro Tip: Share Your Profile

+

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.

-

Professional Certificates - NEW!

+

Certificate Generation

-

Beautiful Certificates Automatically

-

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.

-
-
-

Simple Generation Process

+

Professional Certificates

+

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.

    -
  • Go to "Generate Certificates" from the menu
  • -
  • Select your event from the dropdown
  • -
  • Choose attendees (or select all)
  • -
  • Click Generate - certificates are created instantly!
  • -
  • Click "Certificate Issued" text to view any certificate
  • +
  • Automated Generation: Create certificates for event attendees
  • +
  • Professional Design: Branded with Upskill HVAC styling
  • +
  • Unique Verification: Each certificate has a verification number
  • +
  • Easy Distribution: Download and share with attendees
-

Track Everything

-

The Certificate Reports page shows all certificates you\'ve issued. Filter by event, search by name, and download certificates anytime.

+

Certificate Workflow

+
    +
  1. Navigate to Certificates: Use the navigation menu
  2. +
  3. Generate New Certificates: Select event and attendees
  4. +
  5. View Certificate Reports: Track all issued certificates
  6. +
  7. Download & Share: Distribute to your trainees
  8. +
+ +
+
+
+ + @@ -339,39 +391,48 @@ class HVAC_Help_System {

Where do I start?

-

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.

+

Start by updating your trainer profile to appear in our directory, then create your first training event. Your dashboard provides an overview of everything.

-

How do I edit an event?

-

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.

+

How do I get more training leads?

+

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.

+
+
+

What\'s the difference between venues and organizers?

+

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.

How do certificates work?

-

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.

+

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.

-

Can attendees view their certificates?

-

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.

+

Can I track my training revenue?

+

Yes! Your dashboard shows revenue tracking and analytics for all your training events. You can monitor your training business performance over time.

-

What\'s the revenue target on my dashboard?

-

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!

+

What if I need help?

+

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.

-
-

How do I email my attendees?

-

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.

+
+
+ +
+

Getting Support

+
+
+

Quick Start Checklist

+
    +
  1. ✓ Update Your Profile - Make sure your information is complete
  2. +
  3. ✓ Add Training Venues - Set up your training locations
  4. +
  5. ✓ Create Training Organizer - Add your business entity
  6. +
  7. ✓ Create First Event - Host your first training session
  8. +
  9. ✓ Share Your Profile - Start generating training leads
  10. +
-
-

Do I need to use WordPress admin?

-

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.

-
-
-

How do payments work?

-

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.

-
-
-

Need more help?

-

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.

+
+

Remember

+

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.

+

Pro tip: The more complete your profile, the more professional you appear to potential students and the more training leads you\'ll generate!

'; diff --git a/includes/class-hvac-menu-system.php b/includes/class-hvac-menu-system.php index fb50f334..7ea48dbe 100644 --- a/includes/class-hvac-menu-system.php +++ b/includes/class-hvac-menu-system.php @@ -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' ) ) + ), + array( + 'title' => 'Logout', + 'url' => wp_logout_url(home_url('/training-login/')), + 'icon' => 'dashicons-exit' ) ) ); - // Help section + // Help section (moved to end, with question mark icon) $menu[] = array( - 'title' => 'Help', + 'title' => '?', 'url' => home_url('/trainer/documentation/'), - 'icon' => 'dashicons-sos' - ); - - // Logout (always last) - $menu[] = array( - 'title' => 'Logout', - 'url' => wp_logout_url(home_url('/training-login/')), - 'icon' => 'dashicons-exit' + '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 '
  • '; + // 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 '' . $icon . esc_html($item['title']) . ''; + echo '' . $icon . $title_content . ''; } else { - echo '' . $icon . esc_html($item['title']) . ''; + echo '' . $icon . $title_content . ''; } if ($has_children) { diff --git a/includes/class-hvac-page-manager.php b/includes/class-hvac-page-manager.php index 78242ae9..79979f31 100644 --- a/includes/class-hvac-page-manager.php +++ b/includes/class-hvac-page-manager.php @@ -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]', diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php index d4ab168e..144fc07a 100644 --- a/includes/class-hvac-plugin.php +++ b/includes/class-hvac-plugin.php @@ -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', diff --git a/includes/class-hvac-qr-generator.php b/includes/class-hvac-qr-generator.php new file mode 100644 index 00000000..73c87714 --- /dev/null +++ b/includes/class-hvac-qr-generator.php @@ -0,0 +1,337 @@ +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(); + ?> +
    + + + + + + + + + + + + +
    + '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(); \ No newline at end of file diff --git a/includes/class-hvac-registration.php b/includes/class-hvac-registration.php index 2c6b58cf..ee03c0ca 100644 --- a/includes/class-hvac-registration.php +++ b/includes/class-hvac-registration.php @@ -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')) { diff --git a/includes/class-hvac-scripts-styles.php b/includes/class-hvac-scripts-styles.php index 3803b41f..544c97ef 100644 --- a/includes/class-hvac-scripts-styles.php +++ b/includes/class-hvac-scripts-styles.php @@ -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); + } + } } \ No newline at end of file diff --git a/includes/class-hvac-training-leads.php b/includes/class-hvac-training-leads.php new file mode 100644 index 00000000..acbc4667 --- /dev/null +++ b/includes/class-hvac-training-leads.php @@ -0,0 +1,735 @@ +Please log in to view your training leads.

    '; + } + + // Check user capabilities + if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) { + return '

    You do not have permission to view this page.

    '; + } + + // Get current user + $current_user = wp_get_current_user(); + + // Get submissions for this trainer + $submissions = $this->get_trainer_submissions($current_user->ID); + + ob_start(); + ?> +
    +
    +

    Training Leads

    +

    Manage contact requests from potential training clients

    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DateNameEmailPhoneLocationMessageStatusActions
    + submission_date))); ?> + + first_name . ' ' . $submission->last_name); ?> + + + email); ?> + + + phone) : ?> + + phone); ?> + + + + + + city, + $submission->state_province + ]); + if (!empty($location_parts)) { + echo esc_html(implode(', ', $location_parts)); + } else { + echo ''; + } + ?> + + message) : ?> +
    + message, 8, '...')); ?> +
    + message) > 50) : ?> + + + + + +
    + + status)); ?> + + +
    + status === 'new') : ?> + + + + status !== 'replied') : ?> + + + + +
    +
    +
    + +
    +
    +
    + +
    +

    No inbound training requests

    +

    When potential clients contact you through the "Find a Trainer" directory, their messages will appear here.

    +
    +
    + + +
    +
    +

    Want more training leads?

    +

    Share your profile with the world to attract more potential clients!

    + + Share your profile with the world! + +
    +
    +
    + + + + + + + + $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(); \ No newline at end of file diff --git a/includes/class-hvac-welcome-popup.php b/includes/class-hvac-welcome-popup.php new file mode 100644 index 00000000..c95a2665 --- /dev/null +++ b/includes/class-hvac-welcome-popup.php @@ -0,0 +1,298 @@ +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(); \ No newline at end of file diff --git a/scripts/cleanup-test-data.sh b/scripts/cleanup-test-data.sh new file mode 100755 index 00000000..5810115d --- /dev/null +++ b/scripts/cleanup-test-data.sh @@ -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' +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" \ No newline at end of file diff --git a/scripts/force-cleanup-test-data.sh b/scripts/force-cleanup-test-data.sh new file mode 100755 index 00000000..3e4289ab --- /dev/null +++ b/scripts/force-cleanup-test-data.sh @@ -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' +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!" \ No newline at end of file diff --git a/scripts/manual-cleanup.sh b/scripts/manual-cleanup.sh new file mode 100755 index 00000000..6669a753 --- /dev/null +++ b/scripts/manual-cleanup.sh @@ -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' +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!" \ No newline at end of file diff --git a/scripts/simple-cleanup.sh b/scripts/simple-cleanup.sh new file mode 100755 index 00000000..3b322176 --- /dev/null +++ b/scripts/simple-cleanup.sh @@ -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' +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!" \ No newline at end of file diff --git a/scripts/verify-test-data.sh b/scripts/verify-test-data.sh new file mode 100755 index 00000000..a8361f89 --- /dev/null +++ b/scripts/verify-test-data.sh @@ -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' + '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!" \ No newline at end of file diff --git a/scripts/wp-cli-cleanup.sh b/scripts/wp-cli-cleanup.sh new file mode 100755 index 00000000..72f023c9 --- /dev/null +++ b/scripts/wp-cli-cleanup.sh @@ -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 \ No newline at end of file diff --git a/templates/page-documentation.php b/templates/page-documentation.php index b8c2c42e..77ec0a9c 100644 --- a/templates/page-documentation.php +++ b/templates/page-documentation.php @@ -1,12 +1,57 @@ set_custom_breadcrumb([ + ['title' => 'Trainer', 'url' => home_url('/trainer/')], + ['title' => 'Documentation', 'url' => ''] + ]); +} -get_footer(); \ No newline at end of file +?> + +
    + + render_trainer_menu(); + } + ?> + + render(); + } + ?> + +
    + Documentation is not available. Please contact an administrator.

    '; + } + ?> +
    + +
    + + \ No newline at end of file diff --git a/templates/page-find-trainer.php b/templates/page-find-trainer.php index 4e065846..57b973c8 100644 --- a/templates/page-find-trainer.php +++ b/templates/page-find-trainer.php @@ -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', [
    -

    Find a Trainer

    +

    - + + +
    + + + +
    + + +
    +
    + + <?php echo esc_attr($direct_profile_data['trainer_name']); ?> + +
    + +
    + + + + +
    + measureQuick Certified Trainer +
    + +
    + +
    +

    +

    + +

    +

    + +

    + +

    Total Training Events:

    +
    +
    + + +
    +

    Training Information

    +
    + +
    + Training Formats: + +
    +
    + Training Locations: + +
    + +
    + Training Audience: + +
    + + +
    + Years Experience: + years +
    + +
    +
    + + +
    +

    Upcoming Events

    + + + +

    No upcoming events scheduled

    + +
    + + + +
    +

    About

    +
    + +
    +
    + + + +
    +

    Contact

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + +
    + + + +
    + + + + +
    + +
    +
    + + + +
    -

    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.

    +

    Upskill HVAC is proud to be the only training body offering Certified measureQuick training.

    + +

    Certified measureQuick Trainers have demonstrated their skills and mastery of HVAC science and the measureQuick app, and are authorized to provide measureQuick training to the industry.

    + +

    measureQuick Certified Champions have also demonstrated mastery of HVAC science and the measureQuick app, but they do not offer public training.

    + +

    Use the interactive map and filters below to discover trainers who match your specific needs. Click on any Certified measureQuick Trainer to view their profile and contact them directly about training opportunities.

    + - + +
    @@ -220,8 +454,10 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
    + - + +
    @@ -313,11 +549,14 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [
    - + + +

    Are you an HVAC Trainer that wants to be listed in our directory?

    Become A Trainer
    +
    @@ -409,6 +648,7 @@ wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [ +

    Trainer Profile

    - Edit Profile +
    + Edit Profile + +
    @@ -272,5 +277,57 @@ get_header();
    + + + localize_sharing_data([ + 'profile_id' => $profile->ID + ]); +} + get_footer(); diff --git a/templates/page-trainer-training-leads.php b/templates/page-trainer-training-leads.php new file mode 100644 index 00000000..706b1ded --- /dev/null +++ b/templates/page-trainer-training-leads.php @@ -0,0 +1,58 @@ +set_custom_breadcrumb([ + ['title' => 'Trainer', 'url' => home_url('/trainer/')], + ['title' => 'Profile', 'url' => home_url('/trainer/profile/')], + ['title' => 'Training Leads', 'url' => ''] + ]); +} + +?> + +
    + + render_trainer_menu(); + } + ?> + + render(); + } + ?> + +
    + Training Leads functionality is not available. Please contact an administrator.

    '; + } + ?> +
    + +
    + + \ No newline at end of file