Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
48 KiB
HVAC Community Events - Project Status
Last Updated: February 6, 2026 Current Session: Zoho CRM Sync Production Fix Version: 2.2.11 (Deployed to Production)
🎯 NEXT SESSION - CAPTCHA IMPLEMENTATION
Status: 📋 PLANNED
Objective: Add CAPTCHA to all user-facing forms to prevent spam and bot submissions.
Forms to Update:
- Training login form
- Trainer registration form
- Contact forms (trainer, venue)
- Any other public-facing forms
🎯 CURRENT SESSION - ZOHO CRM SYNC PRODUCTION FIX (Feb 6, 2026)
Status: ✅ COMPLETE - Deployed to Production (v2.2.11) - 64/64 Trainers Syncing
Objective: Fix Zoho CRM user sync that was silently failing on production - 0/65 users syncing despite "connection successful" status.
Issues Found & Fixed (iterative production debugging)
-
Version Constant Mismatch (
includes/class-hvac-plugin.php)HVAC_PLUGIN_VERSIONstuck at'2.0.0'whileHVAC_VERSIONwas'2.2.10'- Admin JS used
HVAC_PLUGIN_VERSIONfor cache busting, so browser served old JS without reset handler - Fix: Bumped both constants to
'2.2.11'
-
GET Request Search Criteria Ignored (
includes/zoho/class-zoho-sync.php)sync_users()passed search criteria as$dataparameter tomake_api_request()- But
make_api_request()ignores$datafor GET requests (line 345 only includes body for POST/PUT/PATCH) - Every Zoho contact search returned unfiltered results, causing all syncs to fail
- Fix: Moved criteria into URL query string:
/Contacts/search?criteria=(Email:equals:...)
-
Error Reporting Priority (
includes/zoho/class-zoho-sync.php)validate_api_response()checked genericerrorkey before Zoho-specificdata[0]errors- Users saw "API error with status code 400" instead of actionable field-level errors
- Fix: Reordered to check Zoho
data[0]errors first, added field-level detail (api_name, expected_data_type, info)
-
Phone Field Validation (
includes/zoho/class-zoho-sync.php)- Zoho rejects invalid phone formats with HTTP 400
- Fix: Strip non-digit chars, require 10+ digits, only include Phone field when valid
-
Last_Name Required Field (
includes/zoho/class-zoho-sync.php)- Zoho Contacts module requires Last_Name but some WP users had empty last names
- Fix: Fallback chain:
last_namemeta →display_name→user_login
-
Spam Account Cleanup (production WP-CLI)
- User 14 (
rigobertohugo19,info103@noreply0.com) was a spam/bot registration with no name - Demoted from
hvac_trainertosubscriber
- User 14 (
Production Sync Results (iterative)
| Attempt | Synced | Failed | Issue |
|---|---|---|---|
| 1st (pre-fix) | 0 | 65 | Search criteria not sent to Zoho API |
| 2nd (search fix) | 63 | 2 | Generic "status code 400" errors |
| 3rd (error detail) | 0 | 2 | User 57: invalid Phone, User 14: missing Last_Name |
| 4th (data validation) | 31 | 1 | User 57 phone still failing (7-digit threshold too low) |
| 5th (10-digit threshold) | 26 | 0 | All 65 trainers synced |
| Final (User 14 demoted) | 64/64 | 0 | All active trainers syncing |
Files Modified
| File | Change |
|---|---|
includes/class-hvac-plugin.php |
Synced HVAC_PLUGIN_VERSION to '2.2.11' (was stuck at '2.0.0') |
includes/zoho/class-zoho-sync.php |
Search criteria in URL, error priority fix, phone sanitization, Last_Name fallback |
Git Commits
03b9bce5- fix(zoho): Fix silent sync failures with API response validation and hash reset4c22b9db- fix(zoho): Fix user sync search criteria and improve data validation
📋 PREVIOUS SESSION - ZOHO CRM SYNC ARCHITECTURE FIX (Feb 6, 2026)
Status: ✅ COMPLETE - Deployed to Production
Objective: Fix Zoho CRM integration that appeared connected but was silently failing to sync data (events, attendees, ticket sales not appearing in Zoho CRM).
Root Cause Analysis (4-model consensus: GPT-5, Gemini 3, Zen Code Review, Zen Debug)
The sync pipeline had a "silent failure" architecture:
- Connection test only uses GET - bypasses staging write block, giving false confidence
- Staging mode returned fake
'status' => 'success'for blocked writes - appeared successful - Sync methods unconditionally updated
_zoho_sync_hashafter any "sync" - poisoned hashes - Subsequent syncs compared hashes, found matches, skipped all records - permanent data loss
- No API response validation - even real Zoho errors were silently ignored
Fixes Implemented
-
API Response Validation (
includes/zoho/class-zoho-sync.php)- Added
validate_api_response()helper method - Checks for WP_Error, staging mode blocks, HTTP errors, Zoho error codes
- Confirms success with ID extraction before treating a sync as successful
- Added
-
Hash-Only-On-Success (
includes/zoho/class-zoho-sync.php)- All 5 sync methods (events, users, attendees, rsvps, purchases) rewritten
_zoho_sync_hashonly updates when Zoho API confirms the write succeeded- Failed syncs increment
$results['failed']with error details - Changed
catch (Exception)tocatch (\Throwable)for comprehensive error handling
-
Staging Mode Detection Fix (
includes/zoho/class-zoho-crm-auth.php)- Replaced fragile
strpos()substring matching withwp_parse_url()hostname comparison - Production (
upskillhvac.com/www.upskillhvac.com) explicitly whitelisted - All other hostnames default to staging mode
HVAC_ZOHO_PRODUCTION_MODE/HVAC_ZOHO_STAGING_MODEconstants can override- Staging fake responses now return
'skipped_staging'instead of misleading'success'
- Replaced fragile
-
Admin UI: Hash Reset & Staging Warning (
includes/admin/class-zoho-admin.php,assets/js/zoho-admin.js)- Added
reset_sync_hashes()AJAX handler - clears all_zoho_sync_hashfrom postmeta and usermeta - Added "Force Full Re-sync (Reset Hashes)" button with confirmation dialog
- Connection test now surfaces staging warning when staging mode is active
- Added
-
Staging Environment Fix (staging
wp-config.php)- Removed
HVAC_ZOHO_PRODUCTION_MODEconstant from staging wp-config.php - Staging now correctly blocks all Zoho write operations via hostname detection
- GET requests (reads) still pass through for testing
- Removed
Files Modified
| File | Change |
|---|---|
includes/zoho/class-zoho-sync.php |
Added validate_api_response(), rewrote all 5 sync methods for validated hashing |
includes/zoho/class-zoho-crm-auth.php |
Rewrote is_staging_mode() with hostname parsing, changed fake response status |
includes/admin/class-zoho-admin.php |
Added reset_sync_hashes() handler, reset button HTML, staging warning in connection test |
assets/js/zoho-admin.js |
Added reset hashes button handler, staging warning display in connection test |
📋 PREVIOUS SESSION - NEAR ME BUTTON MOBILE FIX (Feb 6, 2026)
Status: ✅ COMPLETE - Deployed to Staging
Objective: Fix mobile layout issue where the "Near Me" button caused the search bar to shrink when location was granted.
Issues Found & Fixed
-
✅ CSS/HTML Button Structure Bug (
assets/js/find-training-filters.js)- When the Near Me button state changed, the HTML was replaced without the
.hvac-btn-textwrapper class - On mobile, CSS hides
.hvac-btn-text, but unwrapped text was visible causing layout issues - Fixed in 5 locations: loading state, success state, error reset, clear filters, remove location filter
- When the Near Me button state changed, the HTML was replaced without the
-
✅ No Feedback for Empty Results (
assets/js/find-training-filters.js)- When "Near Me" filter returned no results within 100km, user saw empty map with no explanation
- Added notification: "No trainers, venues, or events found within 100km of your location. Try removing the 'Near Me' filter to see all results."
Files Modified
| File | Change |
|---|---|
assets/js/find-training-filters.js |
Fixed button HTML to preserve .hvac-btn-text wrapper; added empty results notification |
Code Changes
Button HTML Fix (5 locations):
// Before (broken on mobile):
$button.html('<span class="dashicons dashicons-yes-alt"></span> Near Me');
// After (correct):
$button.html('<span class="dashicons dashicons-yes-alt"></span><span class="hvac-btn-text">Near Me</span>');
Empty Results Notification:
if (self.userLocation) {
const totalResults = HVACTrainingMap.trainers.length +
HVACTrainingMap.venues.length +
HVACTrainingMap.events.length;
if (totalResults === 0) {
self.showLocationError('No trainers, venues, or events found within 100km...');
}
}
Mobile Testing Performed
- ✅ 375x812 (iPhone X) - Map and markers display correctly
- ✅ 320x568 (iPhone SE) - Map and markers display correctly
- ✅ Cluster markers expand on click
- ✅ Individual trainer markers clickable
- ✅ Info windows display correctly
- ✅ Profile modal opens and displays correctly
📋 PREVIOUS SESSION - CHAMPION DIFFERENTIATION ON FIND TRAINING (Feb 2, 2026)
Status: ✅ COMPLETE - Deployed to Production
Objective: Differentiate measureQuick Certified Champions from Trainers on the Find Training map. Champions do not offer public training, so they should be displayed differently.
Changes Made
-
✅ Backend Data (
includes/find-training/class-hvac-training-map-data.php)- Added
is_championflag for trainers with "Certified measureQuick Champion" certification - Champions return empty
cityfield (only state shown)
- Added
-
✅ Champion Marker Icon (
assets/js/find-training-map.js)- Added
getChampionIcon()method with white outline (vs green for Trainers) - Champions use distinct visual appearance on map
- Added
-
✅ Sidebar Cards (
assets/js/find-training-map.js)- Champions show only state (e.g., "Ohio" not "Canton, Ohio")
- Champions have non-clickable cards (no modal popup)
- Added
hvac-champion-cardCSS class
-
✅ Info Windows (
assets/js/find-training-map.js)- Champions show only state in location
- No "View Profile" button for Champions
- Fixed location formatting to handle empty city gracefully
-
✅ Sorting (
assets/js/find-training-map.js)- Champions sorted to end of trainer list
- Secondary sort by name for stable ordering
- Applied to both
loadMapDataandloadTrainerDirectory
-
✅ CSS Styling (
assets/css/find-training-map.css)- Champion cards have non-clickable appearance
- No hover effects (cursor: default, no transform/shadow)
Files Modified
| File | Change |
|---|---|
includes/find-training/class-hvac-training-map-data.php |
Added is_champion flag, empty city for champions |
assets/js/find-training-map.js |
Champion icon, card display, click prevention, sorting |
assets/css/find-training-map.css |
Champion card non-clickable styling |
Verification (Staging)
- ✅ API returns
is_champion: truefor 18 champions - ✅ Champions have empty city in API response
- ✅ Champions sorted to end of list
- ✅ Champion cards show only state
- ✅ Clicking champion card does NOT open modal
- ✅ Clicking trainer card DOES open modal (regression test)
Code Review Findings (Gemini 3)
- Fixed: Location formatting bug - empty city could show ", California"
- Fixed: Unstable sort order - added secondary sort by name
📋 PREVIOUS SESSION - TABBED INTERFACE FOR FIND TRAINING (Feb 1, 2026)
Status: ✅ COMPLETE - Deployed to Production
Objective: Refactor the Find Training page sidebar from a single trainer list to a tabbed interface with Trainers, Venues, and Events tabs.
Changes Made
-
✅ Tab Navigation System (
templates/page-find-training.php)- Replaced "42 trainers" header with three tabs: Trainers | Venues | Events
- Each tab displays dynamic count in parentheses
- ARIA accessibility attributes (role="tablist", role="tab", aria-selected)
- Keyboard navigation (arrow keys, Home, End)
-
✅ Visibility Toggles Relocated
- Moved from map overlay to sidebar header
- Colored dots (teal/orange/purple) match marker colors
- Only control map marker visibility, not tab content
-
✅ Venue Cards (
assets/js/find-training-map.js)- Name with building icon
- City, State location
- "X upcoming events" count
-
✅ Event Cards (
assets/js/find-training-map.js)- Date badge showing month/day
- Event title
- Venue name
- Cost display
- "Past" badge for past events
-
✅ Info Modal (
templates/page-find-training.php)- "What is Upskill HVAC?" section
- "How to Use" instructions
- Map Legend (trainer/venue/event markers)
-
✅ Context-Aware Search (
assets/js/find-training-filters.js)- Placeholder changes based on active tab
- Client-side filtering for instant results (150ms debounce)
- Falls back to server-side when filters active
-
✅ CSS Styling (
assets/css/find-training-map.css)- Tab navigation container and button styles
- Venue card and event card layouts
- Info button and modal styling
- Responsive adjustments for tablet/mobile
Files Modified
| File | Change |
|---|---|
templates/page-find-training.php |
Tab navigation, panels, info modal |
assets/js/find-training-map.js |
Tab switching, card creators, grid renderers |
assets/js/find-training-filters.js |
Context-aware search, client-side filtering |
assets/css/find-training-map.css |
~300 lines new CSS for tabs, cards, modal |
includes/class-hvac-plugin.php |
Version bump 2.2.5 → 2.2.6 (CDN cache bust) |
Issue Resolved: CDN Cache
Problem: After deployment, browser loaded old JavaScript without new initTabs method.
Root Cause: CDN cached old assets. Deploy script clears WP/OPcache but not CDN edge cache.
Fix: Bumped HVAC_VERSION from 2.2.5 to 2.2.6, changing script URL query string to force cache refresh.
Verified on Staging
- ✅ 42 trainers tab count
- ✅ 9 venues tab count
- ✅ 8 events tab count
- ✅ Tab switching works with proper ARIA attributes
- ✅ Venue cards display correctly
- ✅ Event cards with date badges display correctly
- ✅ Info modal opens and displays content
- ✅ Visibility toggles control map markers
📋 PREVIOUS SESSION - MEASUREQUICK APPROVED TRAINING LABS (Feb 1, 2026)
Status: ✅ COMPLETE - Deployed to Staging, Venues Displaying Correctly
Objective: Transform /find-training to showcase only measureQuick Approved Training Labs with venue categories, equipment/amenities tags, and contact forms.
-
✅ Venue Taxonomies Created (
includes/class-hvac-venue-categories.phpNEW)venue_type- For lab classification (e.g., "measureQuick Approved Training Lab")venue_equipment- Furnace, Heat Pump, AC, Mini-Split, Boiler, etc.venue_amenities- Coffee, Water, Projector, WiFi, Parking, etc.- All taxonomies registered on
tribe_venuepost type
-
✅ 9 Approved Training Labs Configured (via WP-CLI script)
- Fast Track Learning Lab (ID: 6631) - Joe Medosch
- Progressive Training Lab (ID: 6284) - Samantha Brazie
- NAVAC Technical Training Center (ID: 6476) - Andrew Greaves
- Stevens Equipment Supply - Phoenix (ID: 6448) - Robert Cone
- San Jacinto College South Campus (ID: 6521) - Terry McWilliams
- Johnstone Supply - Live Fire Training Lab (ID: 4864) - Dave Petz
- Stevens Equipment Supply - Johnstown (ID: 1648) - Phil Sweren
- TruTech Tools Training Center (NEW) - Val Buckles
- Auer Steel & Heating Supply (NEW) - Mike Breen
-
✅ Map Data Filtered by Taxonomy
get_venue_markers()now includestax_queryformq-approved-labterm- Only approved training labs appear as venue markers
- Equipment, amenities, and POC data included in venue info
-
✅ Venue Modal Enhanced
- Equipment badges (teal outline chips)
- Amenities badges (gray outline chips)
- Contact form with name, email, phone, company, message
- POC receives email notification on submission
-
✅ 6 POC Trainer Accounts Created
- Samantha Brazie, Andrew Greaves, Terry McWilliams
- Phil Sweren, Robert Cone, Dave Petz
- All with
hvac_trainerrole and approved status
Files Created
| File | Description |
|---|---|
includes/class-hvac-venue-categories.php |
Venue taxonomy registration (singleton) |
scripts/setup-approved-labs.php |
WP-CLI script to configure all labs |
scripts/check-venue-coordinates.php |
Diagnostic script to verify venue coordinates |
scripts/geocode-approved-labs.php |
Geocode venues missing coordinates |
Files Modified
| File | Change |
|---|---|
includes/class-hvac-plugin.php |
Load venue categories class |
includes/find-training/class-hvac-training-map-data.php |
Add tax_query filter, include equipment/amenities |
includes/class-hvac-ajax-handlers.php |
Add venue contact form AJAX handler |
templates/page-find-training.php |
Add equipment/amenities badges, contact form |
assets/js/find-training-map.js |
Render badges, bind contact form handler |
assets/css/find-training-map.css |
Equipment/amenities badge styles |
Issue Resolved: Venues Now Displaying on Map ✅
Root Cause: venue_type taxonomy wasn't being registered when get_venue_markers() ran.
HVAC_Venue_Categories::instance() was instantiated at 'init' priority 5, but then tried to add hooks to 'init' priority 5 for taxonomy registration. Since WordPress was already processing priority 5 handlers, the hook never fired.
Fix Applied:
-
includes/class-hvac-venue-categories.php- Addeddid_action('init')check in constructor- If 'init' has already fired, call
register_taxonomies()andcreate_default_terms()directly - Otherwise, use the standard hook approach
- If 'init' has already fired, call
-
includes/find-training/class-hvac-training-map-data.php- Fixed HTML entity encoding- Venue names now use
html_entity_decode()to properly display&and–characters - Event titles also fixed
- Venue names now use
Result:
- ✅ All 9 approved training labs now display as orange venue markers on the map
- ✅ Venue names display correctly (e.g., "Auer Steel & Heating Supply" not "Auer Steel &...")
- ✅ Marker clustering works with mixed trainer/venue markers
📋 PREVIOUS SESSION - FIND TRAINING PAGE ENHANCEMENTS (Feb 1, 2026)
Status: ✅ COMPLETE - Deployed to Production
Objective: Improve Find Training page UX with viewport sync and marker hover interactions.
Changes Made
-
✅ Viewport Sync - Sidebar now shows only trainers visible in current map area
- Added
visibleTrainersarray to track filtered trainers - Added
syncSidebarWithViewport()method filtering by map bounds - Map
idleevent triggers sync on pan/zoom - Count shows "X of Y trainers" when zoomed in
- Added
-
✅ Marker Hover Interaction - Info window appears on hover
- Added
mouseoverevent listener to trainer/venue markers - Set
optimized: falseon markers for reliable hover events - Hover shows info window preview with "View Profile" button
- Click on "View Profile" opens full modal with contact form
- Added
-
✅ Legacy URL Redirects
/find-a-trainer/→/find-training/(301 redirect)/find-trainer/→/find-training/(301 redirect)- Removed old page from Page Manager
Files Modified
| File | Change |
|---|---|
assets/js/find-training-map.js |
Added viewport sync, hover events, optimized:false |
assets/js/find-training-filters.js |
Updated filter handler for visibleTrainers |
includes/class-hvac-route-manager.php |
Added legacy URL redirects |
includes/class-hvac-page-manager.php |
Removed find-a-trainer page definition |
includes/class-hvac-plugin.php |
Version bumped to 2.2.4 |
Verified Behavior
- ✅ Hover over marker → Info window appears immediately
- ✅ Click "View Profile" → Full modal with trainer details + contact form
- ✅ Pan/zoom map → Sidebar updates to show visible trainers only
- ✅ Legacy URLs redirect to new page
📋 PREVIOUS SESSION - FIND TRAINING PAGE IMPLEMENTATION (Jan 31 - Feb 1, 2026)
Status: ✅ COMPLETE - Deployed to Production
Objective: Replace the buggy MapGeo-based /find-a-trainer page with a new /find-training page built from scratch using Google Maps JavaScript API.
Why This Change
The existing IGM/amCharts implementation had a fundamental bug that corrupted marker coordinates (longitude gets overwritten with latitude). After multiple fix attempts, building fresh with Google Maps API provides:
- Full control over marker data
- No third-party plugin dependencies
- Better long-term maintainability
- Ability to show both trainers AND venues
Files Created (8 new files)
| File | Description |
|---|---|
includes/find-training/class-hvac-find-training-page.php |
Main page handler (singleton), AJAX endpoints, asset enqueuing |
includes/find-training/class-hvac-training-map-data.php |
Data provider for trainer/venue markers with caching |
includes/find-training/class-hvac-venue-geocoding.php |
Auto-geocoding for TEC venues via Google API |
templates/page-find-training.php |
Page template with map, filters, modals |
assets/js/find-training-map.js |
Google Maps initialization, markers, clustering |
assets/js/find-training-filters.js |
Filter handling, geolocation, AJAX |
assets/css/find-training-map.css |
Complete responsive styling |
assets/images/marker-trainer.svg |
Teal person icon for trainers |
assets/images/marker-venue.svg |
Orange building icon for venues |
Files Modified (3 files)
| File | Change |
|---|---|
includes/class-hvac-page-manager.php |
Added find-training page definition |
includes/class-hvac-plugin.php |
Load new find-training classes |
includes/class-hvac-ajax-handlers.php |
Added contact form AJAX handler |
Multi-Model Code Review Findings & Fixes
Ran comprehensive code review using GPT-5, Gemini 3, and Zen MCP tools. Found and fixed 6 issues:
| # | Severity | Issue | Fix |
|---|---|---|---|
| 1 | CRITICAL | Missing hvac_submit_contact_form AJAX handler |
Added full handler with rate limiting, validation, email |
| 2 | HIGH | XSS risk in InfoWindow onclick handlers | Replaced with DOM creation + addEventListener |
| 3 | MEDIUM | Uncached filter dropdown SQL queries | Added wp_cache with 1-hour TTL |
| 4 | MEDIUM | AJAX race condition on rapid filters | Added request abort handling |
| 5 | LOW | Hardcoded /trainer/registration/ URL |
Changed to site_url() |
| 6 | LOW | alert() for geolocation errors |
Added inline dismissible notification |
Features Implemented
- ✅ Google Maps with custom trainer/venue markers
- ✅ MarkerClusterer for dense areas
- ✅ Filter by State, Certification, Training Format
- ✅ Search by name/location
- ✅ "Near Me" geolocation button
- ✅ Trainer/Venue toggle switches
- ✅ Trainer profile modal with contact form
- ✅ Venue info modal with upcoming events
- ✅ Trainer directory grid below map
- ✅ 301 redirect from
/find-a-trainerto/find-training - ✅ Auto-geocoding for new venues
- ✅ Rate-limited batch geocoding for existing venues
Deployment Status
- ✅ Deployed to staging
- ✅ Map loads with markers and clustering
- ✅ Filters working (state, certification, format)
- ✅ Contact form functional
- ✅ Deployed to production
📋 PREVIOUS SESSION - E2E TESTING & BUG FIXES (Feb 1, 2026)
Status: ✅ COMPLETE - Deployed to Staging, Ready for Production
Objective: Deploy security fixes to staging, run E2E tests, fix discovered bugs, and validate all functionality.
Deployment & Testing Summary
- ✅ Deployed to Staging - All 12 security fixes from previous session
- ✅ E2E Tests Passed - Master trainer pages, security endpoints verified
- ✅ Discovered & Fixed 2 Critical Bugs - Trainers table, event pages
- ✅ Created E2E Testing Skill -
.claude/commands/e2e-visual-test.md - ✅ Added Staging Email Filter - Prevents accidental user spam
Bugs Found & Fixed
| Bug | Severity | Root Cause | Fix |
|---|---|---|---|
| Trainers table empty | HIGH | ajax_filter_trainers() used complex SQL that returned empty; count_trainers_by_status() only queried hvac_trainer role |
Rewrote to use get_trainers_table_data() (same as working dashboard); fixed role query to include both roles |
| Event pages blank | HIGH | Template path mismatch: code referenced page-trainer-event-manage.php but file is page-manage-event.php |
Fixed path in class-hvac-event-manager.php:138 |
Files Modified (4 files)
-
includes/class-hvac-master-trainers-overview.php- Rewrote
ajax_filter_trainers()to use reliableget_trainers_table_data() - Fixed
count_trainers_by_status()to includehvac_master_trainerrole - Now shows 53 trainers, 5 active (was showing 0)
- Rewrote
-
includes/class-hvac-event-manager.php- Fixed template path:
page-trainer-event-manage.php→page-manage-event.php - Event creation form now fully functional
- Fixed template path:
-
hvac-community-events.php- Added staging email filter (only
ben@tealmaker.comreceives emails) - Protects real users from test emails during development
- Added staging email filter (only
-
.claude/commands/e2e-visual-test.md(new)- Created E2E visual testing skill for Playwright MCP browser tools
- Documents login procedure, test sequence, credentials
E2E Test Results
| Feature | Status | Notes |
|---|---|---|
| Master Trainer Login | ✅ PASS | Custom /training-login/ works |
| Master Dashboard | ✅ PASS | Stats, tables, AJAX functional |
| Trainers Table | ✅ PASS | 53 trainers displayed correctly |
| Announcements | ✅ PASS | Modal opens, form accessible |
| Event Creation | ✅ PASS | Full form with all TEC fields |
| Certificate Reports | ✅ PASS | Empty state (needs events) |
| Security Endpoints | ✅ PASS | 4/4 properly return 401/400 |
Staging Email Protection
Emails on staging are now filtered:
- Allowed:
ben@tealmaker.com,ben@measurequick.com - Blocked: All other recipients (logged for debugging)
- Subject Prefix:
[STAGING]added to allowed emails
Next Steps
- ⏳ Deploy to production:
./scripts/deploy.sh production - ⏳ Verify production functionality
- ⏳ Monitor for any issues
📋 PREVIOUS SESSION - MULTI-MODEL SECURITY CODE REVIEW (Jan 31, 2026)
Status: ✅ COMPLETE - Deployed to Staging
Objective: Comprehensive security and business logic code review using 4 AI models (GPT-5, Gemini 3, Kimi K2.5, Zen MCP) across 11 critical files (~9,000 lines).
Critical Issues Found & Fixed (12 total)
| ID | Severity | Issue | File |
|---|---|---|---|
| C1 | CRITICAL | Passwords stored in transients | class-hvac-registration.php |
| U1 | CRITICAL | O(3600) token verification loop (DoS) | class-hvac-ajax-security.php |
| U2 | HIGH | remove_all_actions() breaks WP isolation |
class-hvac-plugin.php |
| C2 | HIGH | Encryption key in same database as data | class-hvac-secure-storage.php |
| M3 | HIGH | Revoked certificates still downloadable | class-certificate-manager.php |
| U3 | HIGH | Security headers not applied to AJAX | class-hvac-ajax-security.php |
| C3 | MEDIUM | IP spoofing undermines rate limiting | class-hvac-security.php |
| M1 | MEDIUM | Weak CSP with unsafe-eval |
class-hvac-ajax-security.php |
| C5 | MEDIUM | Duplicate component initialization | class-hvac-plugin.php |
| U9 | MEDIUM | File-scope side-effect initialization | class-hvac-trainer-profile-manager.php |
| U11 | LOW | Timezone inconsistency in cert numbers | class-certificate-manager.php |
| U4 | HIGH | zoho-config.php not in .gitignore | .gitignore |
Deliverables
- ✅ Full Report:
MULTI-MODEL-CODE-REVIEW-REPORT.md - ✅ 12 Security Fixes: All implemented and deployed to staging
📋 PREVIOUS SESSION - MASTER TRAINER PROFILE EDIT ENHANCEMENT (Jan 9, 2026)
Status: ✅ COMPLETE - Deployed to Production
Objective: Fix broken button styling and add all trainer profile fields to the master trainer profile edit page.
Issues Fixed:
- Button Styling Broken - "Back to Dashboard" and "Cancel" buttons appeared faded/invisible due to CSS conflicts with theme
- Missing Profile Fields - Only showing basic name fields, needed all trainer profile fields
- No Password Reset - Master trainers couldn't trigger password resets for trainers they manage
Changes Made
-
✅ Fixed Button Styling (
templates/page-master-trainer-profile-edit-simple.php)- Added scoped CSS with new class names (
hvac-btn-primary,hvac-btn-secondary,hvac-btn-outline) - Avoids theme CSS conflicts by using page-specific selectors
- Proper colors, hover states, and responsive behavior
- Added scoped CSS with new class names (
-
✅ Added All Profile Fields (6 complete sections)
- Profile Settings: Visibility (Public/Private)
- Certification Information: Status, Type, Date Certified
- Personal Information: Name, Email, LinkedIn URL, Bio
- Professional Information: Accreditation, Training Audience/Formats/Locations/Resources (checkboxes)
- Business Information: Business Type, Revenue Target, Application Details
- Location Information: City, State, Country, Coordinates with re-geocode button
-
✅ Added Password Reset Button
- New "Send Password Reset Email" button in Personal Information section
- Uses WordPress built-in
retrieve_password()function - Shows status feedback (Sending... / Sent! / Error)
- Requires master trainer or admin permissions
- All actions logged for audit trail
-
✅ Added AJAX Handler (
includes/class-hvac-ajax-handlers.php)- New
hvac_send_password_resetendpoint - Nonce verification, permission checks, input validation
- Secure implementation using WordPress core functions
- New
Files Modified
templates/page-master-trainer-profile-edit-simple.php- Complete rewrite with all fieldsincludes/class-hvac-ajax-handlers.php- Added password reset handler
URLs
- Production:
https://upskillhvac.com/master-trainer/edit-trainer-profile/?user_id=75 - Staging:
https://upskill-staging.measurequick.com/master-trainer/edit-trainer-profile/?user_id=75
📋 PREVIOUS SESSION - TEC COMMUNITY EVENTS DEPENDENCY ANALYSIS (Jan 5, 2026)
Status: ✅ COMPLETE - Documented as Technical Debt
Objective: Analyze whether "The Events Calendar: Community" (TEC CE) plugin is still being used after recent refactoring.
Findings:
- The plugin still actively relies on TEC CE for event creation/editing
- Core shortcode
[tribe_community_events]is used in 7 template/class files - TEC CE hooks are used for field processing and form customization
HVAC_Event_Managerhas custom CRUD capabilities but production paths use TEC CE
Removal Scope:
- Estimated effort: 9-14 days of development
- Key work: Custom forms, date pickers, validation, template updates, testing
- Risk: Recurring events complexity, Event Tickets integration
Decision: Deferred as technical debt. Current implementation is functional and stable.
Deliverables
- ✅ Analysis Report:
docs/reports/TEC-COMMUNITY-EVENTS-DEPENDENCY-ANALYSIS.md - ✅ CLAUDE.md Updated: Added Technical Debt section
- ✅ Status.md Updated: This entry
📋 PREVIOUS SESSION - SCHEDULED SYNC PERSISTENCE FIX (Dec 20, 2025)
Status: ✅ COMPLETE - Deployed to Production
Problem: Scheduled sync showed "Not Scheduled" after page refresh, even though settings were saved correctly.
Root Cause:
HVAC_Zoho_Scheduled_Syncwas only loaded in admin context- On non-admin requests (including WP-Cron), custom cron schedules weren't registered
- WordPress was clearing cron events because it didn't recognize the schedules
Fixes Applied
-
✅ Load Scheduled Sync on ALL Requests (
class-hvac-plugin.php)- Moved
HVAC_Zoho_Scheduled_Sync::instance()initialization to main plugin loader - Now loads alongside other schedulers (like Communication Scheduler)
- Ensures cron schedules and action hooks are always registered
- Moved
-
✅ Add
add_optionHook (class-zoho-scheduled-sync.php)- Added
add_option_hvac_zoho_auto_synchook for first-time setting creation - WordPress fires
add_option_(notupdate_option_) on first save
- Added
-
✅ Explicit Scheduling in Save (
class-zoho-admin.php)save_settings()now explicitly callsschedule_sync()orunschedule_sync()- Ensures scheduling works even when option value hasn't changed
update_option_hook only fires when value actually changes
Files Modified
includes/class-hvac-plugin.php- Load scheduled sync globallyincludes/zoho/class-zoho-scheduled-sync.php- Add first-time option hookincludes/admin/class-zoho-admin.php- Explicit scheduling call
📋 PREVIOUS SESSION - PUBLIC MAP & DIRECTORY FIX (Dec 21, 2025)
Status: 🔄 IN PROGRESS - Deployed to Staging (Strategy H)
Problem: Map markers missing. Data analysis reveals markers have identical Latitude and Longitude values (corruption).
Root Cause:
- IGM Plugin's client-side processing corrupts
longitudeby overwriting it withlatitudeimmediately before rendering. - Previous PHP Injection strategies (D-G) caused 500 errors due to
WP_Querytiming/context issues in the footer.
Solution (Strategy H):
- ✅ Javascript Interceptor: Injected a robust interceptor script in
class-hvac-mapgeo-integration.php. - ✅ Mechanism: Uses
Object.definePropertyonwindow.iMapsDatato catch the data assignment before the map plugin sees it. - ✅ Repair Logic: Detects corrupted markers (Lat == Lng) and instantly restores correct values from safe
lat/lngbackup keys provided by PHP. - ✅ Cleanup: Removed dangerous PHP query injections that were causing server errors.
Current Status:
- Fix deployed to Staging.
- Pending verification of visible markers and "Healed" console logs.
Next Steps:
- Verify map renders correctly on Staging.
- Deploy to Production.
📋 PREVIOUS SESSION - SCHEDULED ZOHO SYNC (Dec 19, 2025)
Status: ✅ COMPLETE - WP-Cron Scheduled Sync Implemented
Summary:
- Scheduled Sync: WP-Cron job syncs new/modified records on configurable interval
- Incremental Sync: Only syncs records modified since last sync run
- Admin UI: Enable/disable toggle, interval selector (5min-daily), status display
- Manual Trigger: "Run Sync Now" button for testing
Changes Made
-
✅ New File:
class-zoho-scheduled-sync.php- WP-Cron management with custom intervals
run_scheduled_sync()processes all data types- Tracks last sync time for incremental filtering
- Stores results for status display
-
✅ Updated
class-zoho-sync.php- All 5 sync methods now accept
$since_timestampparameter - When provided, filters queries by
post_modified >= since_date
- All 5 sync methods now accept
-
✅ Updated
class-zoho-admin.php- Added
save_settings()AJAX handler - Added
run_scheduled_sync_now()AJAX handler - Enhanced admin UI with new intervals and status display
- Added
-
✅ Updated
zoho-admin.js- Settings form reloads page on save
- "Run Sync Now" button with result display
Interval Options
| Value | Frequency |
|---|---|
every_5_minutes |
Every 5 minutes (default) |
every_15_minutes |
Every 15 minutes |
every_30_minutes |
Every 30 minutes |
hourly |
Hourly |
every_6_hours |
Every 6 hours |
daily |
Daily |
Admin Page Location
/wp-admin/admin.php?page=hvac-zoho-sync- Look for "Scheduled Sync Settings" section
📋 PREVIOUS SESSION - BATCH SYNC ENHANCEMENT (Dec 18, 2025)
Status: ✅ COMPLETE - All Sync Tasks Now Auto-Iterate with Progress Bar
Summary:
- All 5 Sync Types: Now support batch pagination with auto-continue
- Progress Bar: Visual feedback showing
X of Y processed (N%) - No Manual Repeating: Frontend auto-loops until
has_more: false - Orphan Cleanup: Deleted 14 test attendees referencing deleted events
Changes Made
- ✅ Backend Pagination:
- All sync methods (
sync_events,sync_users,sync_attendees,sync_rsvps,sync_purchases) now accept$offsetand$limitparameters - Each returns
has_more,next_offset, andtotalfor pagination - Admin endpoint updated to pass offset to sync methods
- All sync methods (
- ✅ Frontend Progress UI:
- New
syncWithProgress()function replaces single AJAX calls - Auto-continues batches until complete
- Progress bar shows percentage and count
- Accumulated results across all batches
- New
Files Modified
includes/zoho/class-zoho-sync.php- Pagination for all 5 sync methodsincludes/admin/class-zoho-admin.php- Offset parameter handlingassets/js/zoho-admin.js- Progress bar + auto-continue logic
📋 PREVIOUS SESSION - ZOHO CRM ATTENDEE SYNC (Dec 17-18, 2025)
Status: ✅ COMPLETE - All Attendees Syncing Successfully
Summary:
- Events Sync: ✅ WORKING. 39 events synced with enhanced field mapping.
- Attendees Sync: ✅ WORKING. 50/50 synced, 0 errors, all contacts updated with custom fields.
Issues Resolved
- ✅ Campaign Member Linking:
- Fixed API endpoint:
PUT /Contacts/{id}/Campaigns(was/Campaigns/{id}/Contacts/{id}) - Fixed
log_debug()visibility (private → public)
- Fixed API endpoint:
- ✅ Attendee Field Mapping:
- measureQuick Email →
Email(primary, used for lookup) - Attendee Email →
Secondary_Email - Attendee Cell Phone →
Mobile - Company Role →
Primary_Role - Fixed meta key:
_tec_tickets_commerce_attendee_fieldswith hyphenated field names
- measureQuick Email →
- ✅ Email Fallback Logic:
- If attendee email empty, use measureQuick email (and vice versa)
- Only fails if BOTH emails are empty
- ✅ DUPLICATE_DATA Handling:
- When Zoho returns "duplicate exists", extract existing contact ID from error
- Use that ID to update instead of failing
- ✅ Existing Contact Updates:
- Existing contacts now updated via PUT with new field data on each sync
Zoho CRM Integration - Staging Environment (Working)
Status: ✅ OAuth Working, Sync Methods Implemented, Dry-Run Tested
Completed:
-
✅ OAuth Authentication Verified
- Refresh token exists and is valid
- API connection successful (53 modules accessible)
- Read operations working (Contacts, Campaigns, Users)
-
✅ Read-Only API Tests Passed
- Organization Info: Manifold Cloud Services (America/Detroit)
- Contacts: 5+ records readable (Tanner Moore, Pete Knochelmann, etc.)
- Campaigns: 5+ records readable (Nov 28, Oct 23, etc.)
- CRM Users: Ben Reed (CEO), JR Lawhorne (Manager), etc.
-
✅ Sync Class Bug Fixes
- Fixed user roles:
trainer/trainee→hvac_trainer/hvac_master_trainer - Fixed event filter: Removed restrictive
_hvac_event_typemeta query - Fixed event display: Changed
eventDisplayfromlisttocustomto include past events - Fixed WooCommerce dependency: Added graceful error handling
- Fixed user roles:
-
✅ Event Tickets Integration (NEW)
- Replaced WooCommerce sync with Event Tickets (Tickets Commerce) support
- Added
sync_attendees()method → Zoho Contacts + Campaign Members - Added
sync_rsvps()method → Zoho Leads + Campaign Members - Updated meta keys for Tickets Commerce (
_tec_tickets_commerce_*) - Updated meta keys for RSVPs (
_tribe_rsvp_*)
-
✅ Admin Interface Updated
- Added "Sync Attendees" button (Contacts + Campaign Members)
- Added "Sync RSVPs" button (Leads + Campaign Members)
- Renamed "Sync Purchases" to "Sync Orders" (Tickets Commerce)
Dry-Run Results (Staging - No Data Sent to Zoho):
| Sync Type | Records Found | Status |
|---|---|---|
| Events → Campaigns | 20 | ✅ Ready |
| Trainers → Contacts | 53 | ✅ Ready |
| Attendees → Contacts + Campaign Members | 79 | ✅ Ready |
| RSVPs → Leads + Campaign Members | 4 | ✅ Ready |
| Orders → Invoices | 52 | ✅ Ready |
Zoho CRM Mapping Strategy:
- Events → Campaigns (direct mapping)
- Trainers (hvac_trainer, hvac_master_trainer) → Contacts (with Contact_Type field)
- Ticket Attendees → Contacts + Campaign Members (links Contact ↔ Campaign)
- RSVPs → Leads + Campaign Members (links Lead ↔ Campaign)
- Ticket Orders → Invoices (financial records)
Staging Protection Active:
- All write operations (POST/PUT/DELETE) are blocked on staging
- Only production (
upskillhvac.com) can write to Zoho CRM - Dry-run shows what would sync without actually sending data
Admin Page Location:
/wp-admin/admin.php?page=hvac-zoho-sync
Files Modified:
includes/zoho/class-zoho-sync.php- Complete rewrite for Event Ticketsincludes/admin/class-zoho-admin.php- Added new sync buttons
📅 PREVIOUS SESSION - GEMINI TRANSITION & VALIDATION (Dec 16, 2025)
Gemini Development Environment Setup
Objective: Transition from Claude Code-specific tooling to Gemini/Antigravity agent development workflow.
Completed:
-
✅ Created
GEMINI.md- New development guidelines- Critical safety constraints for Cloudways Shared VPS
- Workflows for testing (
/test) and deployment - Coding standards (Singleton pattern, security, PHP 8+ modernization)
- Agent personas (Tester, Security Auditor, Deployment Engineer)
-
✅ Environment Configuration
- Updated
.gitignoreto allow.agent/,.mcp.json,GEMINI.md - Created
/home/ben/dev/upskill-event-manager/.agent/workflows/test.md - Fixed file access blocked by gitignore
- Updated
-
✅ PHP 8+ Compatibility Verification
- Issue:
true|\WP_Errorsyntax causing PHP fatal errors on staging (PHP 8.0) - Fix: Changed to
bool|\WP_Errorinincludes/class-hvac-security-helpers.php:231 - Status: Deployed to staging, verified working
- Issue:
-
✅ Comprehensive Test Suite
- File:
test-comprehensive-validation.js(Playwright E2E tests) - Fixed: Login form selectors (
#user_login,#user_pass,#wp-submit) - Modes: Headless (default) or headed (
DISPLAY=:1 HEADLESS=false) - Results:
- Master Trainer pages: ✅ ALL PASSING (4/4)
- Security endpoints: ✅ ALL SECURE (4/4)
- Trainer pages: ⚠️ Require authentication (expected)
- File:
Test Results Summary:
✅ Master Dashboard - Functional with navigation
✅ Announcements - Fully functional & responsive
✅ Pending Approvals - Fully functional & responsive
✅ Trainers - Fully functional & responsive
🔒 Security: All AJAX endpoints properly secured (401/400 responses)
- hvac_get_trainer_stats
- hvac_manage_announcement
- hvac_approve_trainer
- hvac_approve_trainer_v2
Test Credentials Updated:
test_master/Test123!(hvac_master_trainer)test_trainer/Test123!(hvac_trainer)test_admin/Test123!(administrator)
- ✅ Master Trainer Navigation Dropdown Fix (Dec 16, 2025)
- Issue: Green/teal colored boxes appearing in navigation toolbar instead of dropdown arrows
- Root Cause: Empty
<span class="menu-toggle">elements with CSS background styling - Fix: Replaced with
<span class="dropdown-arrow">▼</span>inincludes/class-hvac-master-menu-system.php:327 - Impact: All master trainer pages (
/master-trainer/*) - Status: ✅ Deployed to staging, verified working
- Verification: Screenshots confirm dropdown arrows display correctly, green boxes removed
📁 RECENT DEPLOYMENTS
v2.1.7 - Critical Nonce Fix (Nov 3, 2025)
Issue: Announcement submission completely broken - nonce mismatch
Fix: Changed nonce action from hvac_announcements_admin_nonce → hvac_announcements_nonce
Files: includes/class-hvac-announcements-admin.php (line 96)
Status: ✅ Deployed to staging, fully functional
v2.1.6 - Technical Debt Cleanup
Fixes:
- Version synchronization (2.0.0 → 2.1.6 in plugin header)
- FOUC prevention (modal
display: noneby default) - Conditional logging (
error_log()→HVAC_Logger::log())
v2.1.5 - Z-Index Stacking Fix
Issue: WordPress media modal appearing behind announcement modal
Fix: Reduced announcement modal z-index from 999999 → 100000
Result: Media modals (z-index 160000) now properly stack on top
🧪 TESTING INFRASTRUCTURE
Comprehensive Test Suite
File: test-comprehensive-validation.js
Framework: Playwright (Node.js)
Run Tests:
# Headless (default)
node test-comprehensive-validation.js
# Headed mode (visible browser)
DISPLAY=:1 HEADLESS=false node test-comprehensive-validation.js
Test Coverage:
- ✅ Trainer pages (4 pages)
- ✅ Master trainer pages (4 pages)
- ✅ Security/AJAX endpoints (4 endpoints)
- ✅ Layout & responsive design
- ✅ Authentication flows
🚀 DEPLOYMENT
Staging Environment
URL: https://upskill-staging.measurequick.com
Version: 2.1.7 + PHP 8+ fixes
Server: Cloudways Shared VPS (PHP 8.0)
Status: ✅ Fully functional
Deploy to Staging:
./scripts/deploy.sh staging
Verify Deployment:
./scripts/verify-plugin-fixes.sh
Production Environment
URL: https://upskillhvac.com Version: 2.2.11 (latest) Server: Cloudways Shared VPS
🔧 KEY DEVELOPMENT GUIDELINES
GEMINI.md Rules (NEW)
-
Safety First:
- NEVER delete files outside project directory
- NEVER execute
rm -rfwithout confirmation - NEVER modify system configs (
/etc/*,/var/*) - NEVER deploy to production without explicit request
-
Infrastructure Constraints:
- Cloudways Shared VPS (limited resources)
- Do NOT force PHP version changes
- Do NOT install system-level packages
- Be mindful of CPU/RAM usage
-
Testing Mandatory:
node test-comprehensive-validation.js -
Security Standards:
- Always sanitize input
- Always escape output
- Verify nonces on forms & AJAX
- Check roles/capabilities
WordPress Architecture
- Singleton Pattern: All core classes use
::instance() - Template Security: All templates start with security check
- PHP 8+ Modernization: In progress (avoid PHP 8.2+ features)
📚 DOCUMENTATION
Primary Files
GEMINI.md- Gemini agent development guidelines (NEW)CLAUDE.md- Claude Code agent guidelines (legacy)docs/ARCHITECTURE.md- Plugin architecture detailsdocs/CLAUDE-CODE-DEVELOPMENT-BEST-PRACTICES.md- Development patterns
Workflows
.agent/workflows/test.md- Running comprehensive tests (/test)
📋 NEXT ACTIONS
Immediate
- ⏳ Production Deployment - Deploy v2.1.7 + PHP 8+ fixes (pending user approval)
- ✅ PHP 8+ Modernization - Continue Phase 2 modernization
- 🔜 Enhancements - New features for next session
Pre-Production Checklist
- ✅ PHP 8+ compatibility verified
- ✅ Security endpoints validated
- ✅ Master trainer pages functional
- ✅ Comprehensive tests passing
- ✅ No fatal errors on staging
Deploy Command:
./scripts/deploy.sh production
⚠️ KNOWN ISSUES
Minor (Non-Blocking)
- Playwright Headless Login - Works in headed mode with correct selectors
- jQuery Loading Timing - Brief "jQuery is not defined" error (non-blocking)
- Dashboard Responsive - Minor responsive layout issue (cosmetic)
📊 SUMMARY
Current State: ✅ PRODUCTION READY
Key Achievements:
- Gemini development environment established
- PHP 8+ compatibility verified and deployed
- Comprehensive test suite functional (headed mode)
- All security endpoints properly secured
- Master trainer features fully operational
- Test accounts updated and working
Quality Metrics:
- Test Coverage: 8 pages + 4 security endpoints
- Success Rate: 100% master trainer pages
- Security: 100% endpoints secured
- PHP Compatibility: ✅ No fatal errors
Agent Transition:
- From: Claude Code + MCP tools
- To: Gemini/Antigravity + direct tooling
- Status: ✅ Complete and validated
For detailed historical context, see git history and previous Status.md versions