feat: Major registration refactor and new trainer management pages

- Refactored registration form:
  * Moved Application Details to Personal Information section
  * Renamed Business Information to Training Organization Information
  * Added required Organization Logo upload with media library integration
  * Added Headquarters location fields (City, State/Province, Country)
  * Moved training-related fields into Organization section
  * Created conditional Training Venue Information section with auto-population

- Created comprehensive venue management system:
  * Training Venues List page (/trainer/venue/list) with filtering and pagination
  * Manage Venue page (/trainer/venue/manage) for create/edit operations
  * Full integration with The Events Calendar venue post type
  * AJAX-powered forms with real-time validation

- Created trainer profile system:
  * Trainer Profile view page (/trainer/profile) with stats and certifications
  * Profile Edit page (/trainer/profile/edit) with photo upload
  * Years of experience tracking and professional information
  * Integration with user meta and custom fields

- Created training organizers management:
  * Organizers List page (/trainer/organizer/list) with search functionality
  * Manage Organizer page (/trainer/organizer/manage) for CRUD operations
  * Organization logo upload and headquarters tracking
  * Full integration with The Events Calendar organizer post type

- Technical improvements:
  * Modular PHP class architecture for each feature
  * Comprehensive AJAX handlers with security nonces
  * Responsive CSS design for all new pages
  * JavaScript form validation and dynamic behavior
  * Proper WordPress and TEC API integration

All new features follow hierarchical URL structure and include breadcrumb navigation.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
bengizmo 2025-07-30 16:29:51 -03:00
parent 00b3b2008b
commit e4f079a89c
22 changed files with 7074 additions and 123 deletions

View file

@ -28,5 +28,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Plugin Architecture Refactoring (2025-07-28)**: Implemented modular architecture with single-responsibility classes. Created HVAC_Shortcodes for centralized shortcode management, HVAC_Scripts_Styles for asset management, and HVAC_Route_Manager for URL routing. Eliminated duplicate functionality between HVAC_Plugin and HVAC_Community_Events. All components now use singleton pattern to prevent duplicate initialization. Fixed jQuery selector errors and duplicate content issues. See docs/ARCHITECTURE.md for details.
- **Master Dashboard URL Fix (2025-07-29)**: Fixed critical issue where master dashboard was showing trainer dashboard content. Root cause: Both trainer and master dashboards had the same page slug "dashboard", causing WordPress to load the wrong page. Solution: Changed master dashboard URL from `/master-trainer/dashboard/` to `/master-trainer/master-dashboard/`, updated all code references, removed conflicting legacy redirects. Master dashboard now correctly displays master trainer content with aggregate statistics and trainer performance analytics.
- **Event Manage Page CSS and Header Fix (2025-07-30)**: Resolved persistent CSS override and duplicate header issues on the trainer/event/manage/ page. Root causes: CSS specificity conflicts with theme styles, header being added via both template and tribe hook. Solution: Scoped all CSS rules to `.hvac-event-manage-wrapper`, moved navigation header directly into page template, disabled duplicate tribe hook, added theme override styles. Page now displays correctly with single header, proper 1200px max-width layout, 20px padding, and consistent styling matching other dashboard pages.
- **Major Plugin Update - Registration Refactor and New Trainer Pages (2025-07-30)**: Implemented comprehensive updates to HVAC plugin. Registration form refactored: moved Application Details to Personal Information, renamed Business Information to Training Organization Information with TEC integration, added required Organization Logo upload, added Headquarters location fields, created conditional Training Venue section. New trainer pages created: Training Venues system (/trainer/venue/list, /trainer/venue/manage) with full CRUD operations and TEC venue integration; Trainer Profile system (/trainer/profile, /trainer/profile/edit) with photo upload, certifications, stats tracking; Training Organizers system (/trainer/organizer/list, /trainer/organizer/manage) with logo upload and headquarters tracking. All systems use AJAX forms, real-time validation, responsive design, and proper WordPress/TEC integration.
[... rest of the existing content remains unchanged ...]

View file

@ -0,0 +1,434 @@
/**
* HVAC Organizers Styles
*
* @package HVAC_Community_Events
* @version 2.0.0
*/
/* Page Layout */
.hvac-organizers-list,
.hvac-organizer-manage {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.hvac-page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e0e0e0;
}
.hvac-page-header h1 {
margin: 0;
color: #0274be;
font-size: 2rem;
}
/* Breadcrumb */
.hvac-breadcrumb {
margin-bottom: 1.5rem;
color: #666;
font-size: 0.9rem;
}
.hvac-breadcrumb a {
color: #0274be;
text-decoration: none;
}
.hvac-breadcrumb a:hover {
text-decoration: underline;
}
/* Filters */
.hvac-organizers-filters {
background-color: #f5f5f5;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.hvac-filter-form {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: flex-end;
}
.hvac-filter-row {
display: flex;
gap: 1rem;
width: 100%;
}
.hvac-filter-group {
flex: 1;
min-width: 200px;
}
.hvac-filter-group input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
/* Table */
.hvac-organizers-table-wrapper {
overflow-x: auto;
margin-bottom: 2rem;
}
.hvac-organizers-table {
width: 100%;
border-collapse: collapse;
background-color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.hvac-organizers-table th {
background-color: #f8f9fa;
padding: 1rem;
text-align: left;
font-weight: 600;
color: #333;
border-bottom: 2px solid #e0e0e0;
}
.hvac-organizers-table td {
padding: 1rem;
border-bottom: 1px solid #e0e0e0;
vertical-align: middle;
}
.hvac-organizers-table tbody tr:hover {
background-color: #f8f9fa;
}
.hvac-no-results {
text-align: center;
color: #666;
font-style: italic;
padding: 3rem !important;
}
/* Logo Styles */
.hvac-org-logo {
width: 60px;
}
.hvac-org-logo img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 4px;
}
.hvac-logo-placeholder {
width: 50px;
height: 50px;
background-color: #0274be;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
font-weight: 600;
border-radius: 4px;
}
.hvac-logo-placeholder-large {
width: 200px;
height: 200px;
background-color: #f0f0f0;
color: #999;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
border-radius: 8px;
border: 2px dashed #ddd;
}
/* Logo Upload */
.hvac-org-logo-upload {
display: flex;
align-items: flex-start;
gap: 2rem;
}
.hvac-current-logo img {
max-width: 200px;
max-height: 200px;
object-fit: contain;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.hvac-logo-actions {
display: flex;
flex-direction: column;
gap: 1rem;
}
.hvac-help-text {
color: #666;
font-size: 0.875rem;
margin-top: 0.5rem;
}
/* Buttons */
.hvac-button {
display: inline-block;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
}
.hvac-button-primary {
background-color: #0274be;
color: white;
}
.hvac-button-primary:hover {
background-color: #005fa3;
}
.hvac-button-secondary {
background-color: #6c757d;
color: white;
}
.hvac-button-secondary:hover {
background-color: #5a6268;
}
.hvac-button-danger {
background-color: #dc3545;
color: white;
}
.hvac-button-danger:hover {
background-color: #c82333;
}
.hvac-button-danger-outline {
background-color: transparent;
color: #dc3545;
border: 1px solid #dc3545;
}
.hvac-button-danger-outline:hover {
background-color: #dc3545;
color: white;
}
.hvac-button-small {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.hvac-text-muted {
color: #6c757d;
font-style: italic;
}
/* Pagination */
.hvac-pagination {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 2rem;
}
.hvac-pagination a,
.hvac-pagination span {
padding: 0.5rem 1rem;
border: 1px solid #ddd;
border-radius: 4px;
text-decoration: none;
color: #333;
}
.hvac-pagination a:hover {
background-color: #f5f5f5;
}
.hvac-pagination .current {
background-color: #0274be;
color: white;
border-color: #0274be;
}
/* Form Styles */
.hvac-form {
background-color: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.hvac-form-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #e0e0e0;
}
.hvac-form-section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.hvac-form-section h3 {
margin-bottom: 1.5rem;
color: #333;
font-size: 1.25rem;
}
.hvac-form-row {
margin-bottom: 1.5rem;
}
.hvac-form-row label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
.hvac-form-row input,
.hvac-form-row select,
.hvac-form-row textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.hvac-form-row input:focus,
.hvac-form-row select:focus,
.hvac-form-row textarea:focus {
outline: none;
border-color: #0274be;
box-shadow: 0 0 0 3px rgba(2, 116, 190, 0.1);
}
.hvac-form-row-half {
display: flex;
gap: 1rem;
}
.hvac-form-row-half > div {
flex: 1;
}
.hvac-form-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #e0e0e0;
}
.hvac-form-actions .hvac-button-danger {
margin-left: auto;
}
/* Messages */
.hvac-message {
padding: 1rem 1.5rem;
border-radius: 4px;
margin-bottom: 1.5rem;
font-weight: 500;
}
.hvac-message-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.hvac-message-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Form Error Styles */
.hvac-form-error {
border-color: #dc3545 !important;
}
.hvac-error-message {
display: block;
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/* Responsive */
@media (max-width: 768px) {
.hvac-organizers-list,
.hvac-organizer-manage {
padding: 1rem;
}
.hvac-page-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.hvac-filter-row {
flex-direction: column;
}
.hvac-filter-group {
width: 100%;
}
.hvac-form-row-half {
flex-direction: column;
}
.hvac-form-actions {
flex-wrap: wrap;
}
.hvac-form-actions .hvac-button-danger {
margin-left: 0;
width: 100%;
}
.hvac-organizers-table {
font-size: 0.875rem;
}
.hvac-organizers-table th,
.hvac-organizers-table td {
padding: 0.5rem;
}
.hvac-org-logo-upload {
flex-direction: column;
align-items: center;
}
.hvac-logo-actions {
width: 100%;
align-items: stretch;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,419 @@
/**
* HVAC Trainer Profile Styles
*
* @package HVAC_Community_Events
* @version 2.0.0
*/
/* Page Layout */
.hvac-trainer-profile-view,
.hvac-trainer-profile-edit {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.hvac-page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e0e0e0;
}
.hvac-page-header h1 {
margin: 0;
color: #0274be;
font-size: 2rem;
}
/* Breadcrumb */
.hvac-breadcrumb {
margin-bottom: 1.5rem;
color: #666;
font-size: 0.9rem;
}
.hvac-breadcrumb a {
color: #0274be;
text-decoration: none;
}
.hvac-breadcrumb a:hover {
text-decoration: underline;
}
/* Profile Content Layout */
.hvac-profile-content {
display: flex;
gap: 2rem;
}
/* Profile Sidebar */
.hvac-profile-sidebar {
flex: 0 0 300px;
}
/* Profile Photo */
.hvac-profile-photo {
margin-bottom: 2rem;
text-align: center;
}
.hvac-profile-photo img {
width: 200px;
height: 200px;
border-radius: 50%;
object-fit: cover;
border: 5px solid #f0f0f0;
}
.hvac-profile-photo-placeholder {
width: 200px;
height: 200px;
border-radius: 50%;
background-color: #0274be;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
font-weight: 600;
margin: 0 auto;
}
/* Profile Stats */
.hvac-profile-stats {
background-color: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
}
.hvac-stat-item {
text-align: center;
margin-bottom: 1.5rem;
}
.hvac-stat-item:last-child {
margin-bottom: 0;
}
.hvac-stat-value {
display: block;
font-size: 2rem;
font-weight: 700;
color: #0274be;
margin-bottom: 0.5rem;
}
.hvac-stat-label {
display: block;
font-size: 0.875rem;
color: #666;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Profile Main Content */
.hvac-profile-main {
flex: 1;
}
.hvac-profile-section {
background-color: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 2rem;
}
.hvac-profile-section h2 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #333;
font-size: 1.5rem;
padding-bottom: 0.75rem;
border-bottom: 2px solid #e0e0e0;
}
/* Profile Details */
.hvac-profile-details {
display: grid;
gap: 1rem;
}
.hvac-detail-row {
display: grid;
grid-template-columns: 150px 1fr;
align-items: center;
}
.hvac-detail-label {
font-weight: 600;
color: #666;
}
.hvac-detail-value {
color: #333;
}
.hvac-detail-value a {
color: #0274be;
text-decoration: none;
}
.hvac-detail-value a:hover {
text-decoration: underline;
}
/* Profile Bio */
.hvac-profile-bio {
color: #333;
line-height: 1.6;
}
/* Certifications List */
.hvac-certifications-list {
display: grid;
gap: 0.75rem;
}
.hvac-certification-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background-color: #f8f9fa;
border-radius: 4px;
}
.hvac-certification-item .dashicons {
color: #0274be;
width: 20px;
height: 20px;
}
/* Edit Form Styles */
.hvac-form {
background-color: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.hvac-form-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #e0e0e0;
}
.hvac-form-section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.hvac-form-section h3 {
margin-bottom: 1.5rem;
color: #333;
font-size: 1.25rem;
}
.hvac-form-row {
margin-bottom: 1.5rem;
}
.hvac-form-row label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
.hvac-form-row input,
.hvac-form-row select,
.hvac-form-row textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.hvac-form-row input:focus,
.hvac-form-row select:focus,
.hvac-form-row textarea:focus {
outline: none;
border-color: #0274be;
box-shadow: 0 0 0 3px rgba(2, 116, 190, 0.1);
}
.hvac-form-row-half {
display: flex;
gap: 1rem;
}
.hvac-form-row-half > div {
flex: 1;
}
/* Profile Photo Upload */
.hvac-profile-photo-upload {
display: flex;
align-items: center;
gap: 2rem;
}
.hvac-current-photo img {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
}
.hvac-photo-placeholder {
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 0.875rem;
}
.hvac-photo-actions {
display: flex;
gap: 1rem;
}
/* Buttons */
.hvac-button {
display: inline-block;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
}
.hvac-button-primary {
background-color: #0274be;
color: white;
}
.hvac-button-primary:hover {
background-color: #005fa3;
}
.hvac-button-secondary {
background-color: #6c757d;
color: white;
}
.hvac-button-secondary:hover {
background-color: #5a6268;
}
.hvac-button-danger-outline {
background-color: transparent;
color: #dc3545;
border: 1px solid #dc3545;
}
.hvac-button-danger-outline:hover {
background-color: #dc3545;
color: white;
}
/* Form Actions */
.hvac-form-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #e0e0e0;
}
/* Messages */
.hvac-message {
padding: 1rem 1.5rem;
border-radius: 4px;
margin-bottom: 1.5rem;
font-weight: 500;
}
.hvac-message-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.hvac-message-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Form Errors */
.hvac-form-error {
border-color: #dc3545 !important;
}
.hvac-error-message {
display: block;
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/* Responsive */
@media (max-width: 768px) {
.hvac-trainer-profile-view,
.hvac-trainer-profile-edit {
padding: 1rem;
}
.hvac-page-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.hvac-profile-content {
flex-direction: column;
}
.hvac-profile-sidebar {
flex: none;
width: 100%;
}
.hvac-detail-row {
grid-template-columns: 1fr;
gap: 0.25rem;
}
.hvac-detail-label {
font-size: 0.875rem;
}
.hvac-form-row-half {
flex-direction: column;
}
.hvac-profile-photo-upload {
flex-direction: column;
align-items: flex-start;
}
.hvac-photo-actions {
flex-wrap: wrap;
}
}

357
assets/css/hvac-venues.css Normal file
View file

@ -0,0 +1,357 @@
/**
* HVAC Venues Styles
*
* @package HVAC_Community_Events
* @version 2.0.0
*/
/* Page Layout */
.hvac-venues-list,
.hvac-venue-manage {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.hvac-page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e0e0e0;
}
.hvac-page-header h1 {
margin: 0;
color: #0274be;
font-size: 2rem;
}
/* Breadcrumb */
.hvac-breadcrumb {
margin-bottom: 1.5rem;
color: #666;
font-size: 0.9rem;
}
.hvac-breadcrumb a {
color: #0274be;
text-decoration: none;
}
.hvac-breadcrumb a:hover {
text-decoration: underline;
}
/* Filters */
.hvac-venues-filters {
background-color: #f5f5f5;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.hvac-filter-form {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: flex-end;
}
.hvac-filter-group {
flex: 1;
min-width: 200px;
}
.hvac-filter-group input,
.hvac-filter-group select {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
/* Table */
.hvac-venues-table-wrapper {
overflow-x: auto;
margin-bottom: 2rem;
}
.hvac-venues-table {
width: 100%;
border-collapse: collapse;
background-color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.hvac-venues-table th {
background-color: #f8f9fa;
padding: 1rem;
text-align: left;
font-weight: 600;
color: #333;
border-bottom: 2px solid #e0e0e0;
}
.hvac-venues-table td {
padding: 1rem;
border-bottom: 1px solid #e0e0e0;
}
.hvac-venues-table tbody tr:hover {
background-color: #f8f9fa;
}
.hvac-no-results {
text-align: center;
color: #666;
font-style: italic;
padding: 3rem !important;
}
/* Badges */
.hvac-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
margin-left: 0.5rem;
}
.hvac-badge-owner {
background-color: #e3f2fd;
color: #1976d2;
}
/* Buttons */
.hvac-button {
display: inline-block;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
}
.hvac-button-primary {
background-color: #0274be;
color: white;
}
.hvac-button-primary:hover {
background-color: #005fa3;
}
.hvac-button-secondary {
background-color: #6c757d;
color: white;
}
.hvac-button-secondary:hover {
background-color: #5a6268;
}
.hvac-button-danger {
background-color: #dc3545;
color: white;
}
.hvac-button-danger:hover {
background-color: #c82333;
}
.hvac-button-small {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.hvac-text-muted {
color: #6c757d;
font-style: italic;
}
/* Pagination */
.hvac-pagination {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 2rem;
}
.hvac-pagination a,
.hvac-pagination span {
padding: 0.5rem 1rem;
border: 1px solid #ddd;
border-radius: 4px;
text-decoration: none;
color: #333;
}
.hvac-pagination a:hover {
background-color: #f5f5f5;
}
.hvac-pagination .current {
background-color: #0274be;
color: white;
border-color: #0274be;
}
/* Form Styles */
.hvac-form {
background-color: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.hvac-form-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #e0e0e0;
}
.hvac-form-section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.hvac-form-section h3 {
margin-bottom: 1.5rem;
color: #333;
font-size: 1.25rem;
}
.hvac-form-row {
margin-bottom: 1.5rem;
}
.hvac-form-row label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
.hvac-form-row input,
.hvac-form-row select,
.hvac-form-row textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.hvac-form-row input:focus,
.hvac-form-row select:focus,
.hvac-form-row textarea:focus {
outline: none;
border-color: #0274be;
box-shadow: 0 0 0 3px rgba(2, 116, 190, 0.1);
}
.hvac-form-row-half {
display: flex;
gap: 1rem;
}
.hvac-form-row-half > div {
flex: 1;
}
.hvac-form-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #e0e0e0;
}
.hvac-form-actions .hvac-button-danger {
margin-left: auto;
}
/* Responsive */
@media (max-width: 768px) {
.hvac-venues-list,
.hvac-venue-manage {
padding: 1rem;
}
.hvac-page-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.hvac-filter-form {
flex-direction: column;
}
.hvac-filter-group {
width: 100%;
}
.hvac-form-row-half {
flex-direction: column;
}
.hvac-form-actions {
flex-wrap: wrap;
}
.hvac-form-actions .hvac-button-danger {
margin-left: 0;
width: 100%;
}
.hvac-venues-table {
font-size: 0.875rem;
}
.hvac-venues-table th,
.hvac-venues-table td {
padding: 0.5rem;
}
}
/* Messages */
.hvac-message {
padding: 1rem 1.5rem;
border-radius: 4px;
margin-bottom: 1.5rem;
font-weight: 500;
}
.hvac-message-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.hvac-message-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Form Error Styles */
.hvac-form-error {
border-color: #dc3545 !important;
}
.hvac-error-message {
display: block;
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
}

View file

@ -0,0 +1,364 @@
/**
* HVAC Organizers JavaScript
*
* @package HVAC_Community_Events
* @version 2.0.0
*/
jQuery(document).ready(function($) {
// Cache DOM elements
const $organizerForm = $('#hvac-organizer-form');
const $uploadButton = $('#hvac-upload-logo');
const $removeButton = $('#hvac-remove-logo');
const $logoIdField = $('#org_logo_id');
const $currentLogo = $('.hvac-current-logo');
const $deleteButton = $('#hvac-delete-organizer');
// Form validation
function validateOrganizerForm() {
let isValid = true;
const errors = [];
// Clear previous errors
$('.hvac-form-error').removeClass('hvac-form-error');
$('.hvac-error-message').remove();
// Required fields
const requiredFields = [
{ id: 'org_name', label: 'Organization Name' },
{ id: 'hq_city', label: 'Headquarters City' },
{ id: 'hq_state', label: 'Headquarters State/Province' },
{ id: 'hq_country', label: 'Headquarters Country' }
];
requiredFields.forEach(field => {
const $field = $('#' + field.id);
const value = $field.val();
if (!value || value.trim() === '') {
isValid = false;
errors.push(field.label + ' is required');
$field.addClass('hvac-form-error');
$field.after('<span class="hvac-error-message">' + field.label + ' is required</span>');
}
});
// Validate email if provided
const email = $('#org_email').val();
if (email && !isValidEmail(email)) {
isValid = false;
errors.push('Please enter a valid email address');
$('#org_email').addClass('hvac-form-error');
$('#org_email').after('<span class="hvac-error-message">Please enter a valid email address</span>');
}
// Validate phone format if provided
const phone = $('#org_phone').val();
if (phone && !isValidPhone(phone)) {
isValid = false;
errors.push('Please enter a valid phone number');
$('#org_phone').addClass('hvac-form-error');
$('#org_phone').after('<span class="hvac-error-message">Please enter a valid phone number</span>');
}
// Validate website URL if provided
const website = $('#org_website').val();
if (website && !isValidURL(website)) {
isValid = false;
errors.push('Please enter a valid website URL');
$('#org_website').addClass('hvac-form-error');
$('#org_website').after('<span class="hvac-error-message">Please enter a valid website URL</span>');
}
return isValid;
}
// Email validation
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// Phone validation
function isValidPhone(phone) {
const digits = phone.replace(/\D/g, '');
return digits.length >= 10 && digits.length <= 15;
}
// URL validation
function isValidURL(url) {
try {
new URL(url);
return true;
} catch (_) {
return false;
}
}
// Show message
function showMessage(message, type = 'success') {
const messageClass = type === 'success' ? 'hvac-message-success' : 'hvac-message-error';
const $message = $('<div class="hvac-message ' + messageClass + '">' + message + '</div>');
// Remove any existing messages
$('.hvac-message').remove();
// Add new message
$('.hvac-page-header').after($message);
// Auto-hide success messages after 5 seconds
if (type === 'success') {
setTimeout(function() {
$message.fadeOut(function() {
$(this).remove();
});
}, 5000);
}
// Scroll to top
$('html, body').animate({
scrollTop: $('.hvac-page-header').offset().top - 100
}, 300);
}
// Handle organizer form submission
if ($organizerForm.length) {
$organizerForm.on('submit', function(e) {
e.preventDefault();
// Validate form
if (!validateOrganizerForm()) {
return false;
}
// Disable submit button
const $submitButton = $organizerForm.find('button[type="submit"]');
const originalText = $submitButton.text();
$submitButton.prop('disabled', true).text('Saving...');
// Gather form data
const formData = {
action: 'hvac_save_organizer',
nonce: hvacOrganizers.nonce,
organizer_id: $('input[name="organizer_id"]').val(),
org_name: $('#org_name').val(),
org_description: $('#org_description').val(),
hq_city: $('#hq_city').val(),
hq_state: $('#hq_state').val(),
hq_country: $('#hq_country').val(),
org_phone: $('#org_phone').val(),
org_email: $('#org_email').val(),
org_website: $('#org_website').val(),
org_logo_id: $('#org_logo_id').val()
};
// Send AJAX request
$.ajax({
url: hvacOrganizers.ajax_url,
type: 'POST',
data: formData,
success: function(response) {
if (response.success) {
showMessage(response.data.message, 'success');
// If creating new organizer, update form to edit mode
if (!formData.organizer_id && response.data.organizer_id) {
$('input[name="organizer_id"]').val(response.data.organizer_id);
$('.hvac-page-header h1').text('Edit Organizer');
$('.hvac-breadcrumb').html('<a href="/trainer/dashboard/">Trainer</a> &gt; <a href="/trainer/organizer/list/">Organizers</a> &gt; Edit');
// Add delete button if not present
if (!$('#hvac-delete-organizer').length) {
const deleteButton = '<button type="button" id="hvac-delete-organizer" class="hvac-button hvac-button-danger" data-organizer-id="' + response.data.organizer_id + '">Delete Organizer</button>';
$('.hvac-form-actions').append(deleteButton);
}
}
// Update button text
$submitButton.text('Update Organizer');
} else {
showMessage(response.data || 'An error occurred while saving the organizer.', 'error');
}
},
error: function() {
showMessage('An error occurred. Please try again.', 'error');
},
complete: function() {
// Re-enable submit button
$submitButton.prop('disabled', false).text(originalText);
}
});
});
}
// Handle logo upload
if ($uploadButton.length) {
let mediaUploader;
$uploadButton.on('click', function(e) {
e.preventDefault();
// If the media uploader already exists, open it
if (mediaUploader) {
mediaUploader.open();
return;
}
// Create the media uploader
mediaUploader = wp.media({
title: 'Choose Organization Logo',
button: {
text: 'Use this logo'
},
multiple: false,
library: {
type: 'image'
}
});
// When an image is selected, run a callback
mediaUploader.on('select', function() {
const attachment = mediaUploader.state().get('selection').first().toJSON();
// Update the logo preview
$currentLogo.html('<img src="' + attachment.sizes.medium.url + '" alt="Organization logo" />');
// Update the hidden field
$logoIdField.val(attachment.id);
// Update button text
$uploadButton.text('Change Logo');
// Show remove button if not already visible
if (!$('#hvac-remove-logo').length) {
const removeBtn = '<button type="button" id="hvac-remove-logo" class="hvac-button hvac-button-danger-outline">Remove Logo</button>';
$uploadButton.after(removeBtn);
}
});
// Open the media uploader
mediaUploader.open();
});
}
// Handle logo removal
$(document).on('click', '#hvac-remove-logo', function(e) {
e.preventDefault();
// Clear the logo preview
$currentLogo.html('<div class="hvac-logo-placeholder-large"><span>No logo uploaded</span></div>');
// Clear the hidden field
$logoIdField.val('');
// Update button text
$uploadButton.text('Upload Logo');
// Remove the remove button
$(this).remove();
});
// Handle organizer deletion
$(document).on('click', '#hvac-delete-organizer', function(e) {
e.preventDefault();
const $deleteButton = $(this);
const organizerId = $deleteButton.data('organizer-id');
if (!organizerId) {
showMessage('Invalid organizer ID', 'error');
return;
}
// Confirm deletion
if (!confirm('Are you sure you want to delete this organizer? This action cannot be undone.')) {
return;
}
// Disable button
$deleteButton.prop('disabled', true).text('Deleting...');
// Send delete request
$.ajax({
url: hvacOrganizers.ajax_url,
type: 'POST',
data: {
action: 'hvac_delete_organizer',
nonce: hvacOrganizers.nonce,
organizer_id: organizerId
},
success: function(response) {
if (response.success) {
showMessage(response.data || 'Organizer deleted successfully.', 'success');
// Redirect to organizers list after 2 seconds
setTimeout(function() {
window.location.href = '/trainer/organizer/list/';
}, 2000);
} else {
showMessage(response.data || 'Failed to delete organizer.', 'error');
// Re-enable button
$deleteButton.prop('disabled', false).text('Delete Organizer');
}
},
error: function() {
showMessage('An error occurred. Please try again.', 'error');
// Re-enable button
$deleteButton.prop('disabled', false).text('Delete Organizer');
}
});
});
// Real-time validation
$('#org_email').on('blur', function() {
const email = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (email && !isValidEmail(email)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid email address</span>');
}
});
$('#org_phone').on('blur', function() {
const phone = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (phone && !isValidPhone(phone)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid phone number</span>');
}
});
$('#org_website').on('blur', function() {
const website = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (website && !isValidURL(website)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid website URL</span>');
}
});
// Auto-format phone number
$('#org_phone').on('input', function() {
let value = $(this).val().replace(/\D/g, '');
if (value.length > 0) {
if (value.length <= 3) {
value = value;
} else if (value.length <= 6) {
value = value.slice(0, 3) + '-' + value.slice(3);
} else if (value.length <= 10) {
value = value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6);
} else {
value = value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6, 10);
}
}
$(this).val(value);
});
});

View file

@ -0,0 +1,363 @@
jQuery(document).ready(function($) {
const $countrySelect = $('#user_country');
const $stateSelect = $('#user_state');
const $stateOtherInput = $('#user_state_other');
const $registrationForm = $('#hvac-registration-form');
// Venue fields
const $createVenue = $('input[name="create_venue"]');
const $venueDetails = $('#venue-details');
const $venueName = $('#venue_name');
const $businessName = $('#business_name');
const $userCity = $('#user_city');
const $venuePhone = $('#venue_phone');
const $venueWebsite = $('#venue_website');
const $businessPhone = $('#business_phone');
const $businessWebsite = $('#business_website');
// Form validation helpers
function showFieldError(fieldId, message) {
const $field = $('#' + fieldId);
const $existingError = $field.siblings('.error-message');
if ($existingError.length) {
$existingError.text(message);
} else {
$field.after('<p class="error-message" id="' + fieldId + '_error">' + message + '</p>');
}
$field.addClass('error');
}
function clearFieldError(fieldId) {
const $field = $('#' + fieldId);
$field.siblings('.error-message').remove();
$field.removeClass('error');
}
// Real-time email validation
$('#user_email').on('blur', function() {
const email = $(this).val();
if (email && !isValidEmail(email)) {
showFieldError('user_email', 'Please enter a valid email address.');
} else {
clearFieldError('user_email');
}
});
$('#business_email').on('blur', function() {
const email = $(this).val();
if (email && !isValidEmail(email)) {
showFieldError('business_email', 'Please enter a valid business email address.');
} else {
clearFieldError('business_email');
}
});
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// Real-time password validation
$('#user_pass').on('input blur', function() {
const password = $(this).val();
if (password) {
const errors = [];
if (password.length < 8) {
errors.push('at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('one number');
}
if (errors.length > 0) {
showFieldError('user_pass', 'Password must contain ' + errors.join(', ') + '.');
} else {
clearFieldError('user_pass');
// Check confirm password if it has a value
const confirmPass = $('#confirm_password').val();
if (confirmPass) {
$('#confirm_password').trigger('blur');
}
}
}
});
// Confirm password validation
$('#confirm_password').on('blur', function() {
const password = $('#user_pass').val();
const confirmPassword = $(this).val();
if (confirmPassword && password !== confirmPassword) {
showFieldError('confirm_password', 'Passwords do not match.');
} else {
clearFieldError('confirm_password');
}
});
// URL validation for optional fields
function isValidURL(url) {
try {
new URL(url);
return true;
} catch (_) {
return false;
}
}
$('#user_url, #user_linkedin, #business_website, #venue_website').on('blur', function() {
const url = $(this).val();
const fieldId = $(this).attr('id');
if (url && !isValidURL(url)) {
const fieldName = fieldId === 'user_url' ? 'personal website' :
fieldId === 'user_linkedin' ? 'LinkedIn profile' :
fieldId === 'business_website' ? 'organization website' :
'venue website';
showFieldError(fieldId, 'Please enter a valid URL for your ' + fieldName + '.');
} else {
clearFieldError(fieldId);
}
});
// Handle venue creation toggle
$createVenue.on('change', function() {
if ($(this).val() === 'Yes') {
$venueDetails.slideDown();
// Auto-populate venue name if empty
updateVenueName();
// Auto-populate venue phone and website
if (!$venuePhone.val() && $businessPhone.val()) {
$venuePhone.val($businessPhone.val());
}
if (!$venueWebsite.val() && $businessWebsite.val()) {
$venueWebsite.val($businessWebsite.val());
}
} else {
$venueDetails.slideUp();
}
});
// Auto-populate venue name
function updateVenueName() {
if (!$venueName.val()) {
const businessName = $businessName.val();
const city = $userCity.val();
if (businessName && city) {
$venueName.val(businessName + ' of ' + city);
} else if (businessName) {
$venueName.val(businessName + ' Training Venue');
}
}
}
// Update venue name when business name or city changes
$businessName.on('blur', updateVenueName);
$userCity.on('blur', updateVenueName);
// Copy organization info to venue fields
$businessPhone.on('blur', function() {
if (!$venuePhone.val() && $(this).val()) {
$venuePhone.val($(this).val());
}
});
$businessWebsite.on('blur', function() {
if (!$venueWebsite.val() && $(this).val()) {
$venueWebsite.val($(this).val());
}
});
// Form submission validation
$registrationForm.on('submit', function(e) {
let hasErrors = false;
const errors = [];
// Check required fields
const requiredFields = [
{ id: 'user_email', name: 'Email' },
{ id: 'user_pass', name: 'Password' },
{ id: 'confirm_password', name: 'Confirm Password' },
{ id: 'first_name', name: 'First Name' },
{ id: 'last_name', name: 'Last Name' },
{ id: 'display_name', name: 'Display Name' },
{ id: 'description', name: 'Biographical Info' },
{ id: 'business_name', name: 'Organization Name' },
{ id: 'business_phone', name: 'Organization Phone' },
{ id: 'business_email', name: 'Organization Email' },
{ id: 'business_description', name: 'Organization Description' },
{ id: 'application_details', name: 'Application Details' }
];
// Check venue fields if creating venue
if ($('input[name="create_venue"]:checked').val() === 'Yes') {
requiredFields.push(
{ id: 'venue_name', name: 'Venue Name' },
{ id: 'venue_address', name: 'Street Address' },
{ id: 'user_country', name: 'Country' },
{ id: 'user_city', name: 'City' },
{ id: 'user_zip', name: 'Zip/Postal Code' }
);
}
requiredFields.forEach(field => {
const $field = $('#' + field.id);
if (!$field.val() || $field.val().trim() === '') {
hasErrors = true;
errors.push(field.name + ' is required.');
showFieldError(field.id, field.name + ' is required.');
}
});
// Check org logo
const $orgLogo = $('#org_logo');
if ($orgLogo.length && !$orgLogo[0].files.length) {
hasErrors = true;
errors.push('Organization Logo is required.');
showFieldError('org_logo', 'Organization Logo is required.');
}
// Check state/province for venue
if ($('input[name="create_venue"]:checked').val() === 'Yes') {
const country = $('#user_country').val();
if (country) {
if (country === 'United States' || country === 'Canada') {
const state = $('#user_state').val();
if (!state || state === '') {
hasErrors = true;
errors.push('State/Province is required.');
showFieldError('user_state', 'State/Province is required.');
}
} else {
const otherState = $('#user_state_other').val();
if (!otherState || otherState.trim() === '') {
hasErrors = true;
errors.push('State/Province is required.');
showFieldError('user_state_other', 'State/Province is required.');
}
}
}
}
// Check radio buttons
if (!$('input[name="create_venue"]:checked').length) {
hasErrors = true;
errors.push('Please select whether to create a training venue profile.');
}
if (!$('input[name="business_type"]:checked').length) {
hasErrors = true;
errors.push('Business Type is required.');
}
// Check checkbox groups
const checkboxGroups = [
{ name: 'training_audience[]', label: 'Training Audience' },
{ name: 'training_formats[]', label: 'Training Formats' },
{ name: 'training_locations[]', label: 'Training Locations' },
{ name: 'training_resources[]', label: 'Training Resources' }
];
checkboxGroups.forEach(group => {
if (!$('input[name="' + group.name + '"]:checked').length) {
hasErrors = true;
errors.push('Please select at least one option for ' + group.label + '.');
}
});
if (hasErrors) {
e.preventDefault();
// Show error summary at the top
let $errorSummary = $('.hvac-form-errors');
if (!$errorSummary.length) {
$errorSummary = $('<div class="hvac-form-errors" role="alert"><h3>Please correct the following errors:</h3><ul></ul></div>');
$registrationForm.prepend($errorSummary);
}
const $errorList = $errorSummary.find('ul');
$errorList.empty();
errors.forEach(error => {
$errorList.append('<li>' + error + '</li>');
});
// Scroll to top of form
$('html, body').animate({
scrollTop: $('.hvac-registration-form').offset().top - 100
}, 500);
}
});
// Function to populate states/provinces
function loadStates(country) {
console.log(`Loading states/provinces for ${country}`); // Keep log for debugging
$stateSelect.find('option').not('[value=""],[value="Other"]').remove(); // Clear existing options except defaults
let options = {};
if (country === 'United States' && typeof hvacRegistrationData !== 'undefined' && hvacRegistrationData.states) {
options = hvacRegistrationData.states;
} else if (country === 'Canada' && typeof hvacRegistrationData !== 'undefined' && hvacRegistrationData.provinces) {
options = hvacRegistrationData.provinces;
} else {
// If country is not US/CA or data is missing, ensure 'Other' is selected and input shown
$stateSelect.val('Other').trigger('change'); // Trigger change to show 'Other' input if needed
return;
}
// Append new options
$.each(options, function(value, label) {
// Append before the 'Other' option if it exists, otherwise just append
const $otherOption = $stateSelect.find('option[value="Other"]');
const $newOption = $('<option></option>').val(value).text(label);
if ($otherOption.length > 0) {
$newOption.insertBefore($otherOption);
} else {
$stateSelect.append($newOption);
}
});
// Ensure the 'Other' input is hidden initially when states/provinces are loaded
$stateOtherInput.hide().val('');
// Reset state selection to default prompt
$stateSelect.val('');
}
// Handle state/province field visibility based on 'Other' selection
$stateSelect.change(function() {
if ($(this).val() === 'Other') {
$stateOtherInput.show().prop('required', true); // Make required if Other is selected
} else {
$stateOtherInput.hide().val('').prop('required', false); // Hide and make not required
}
}).trigger('change'); // Trigger on load to set initial visibility
// Handle country change to show/hide/populate state field
$countrySelect.change(function() {
const country = $(this).val();
if (country === 'United States' || country === 'Canada') {
loadStates(country);
$stateSelect.show().prop('required', true); // Show and require state select
$stateOtherInput.prop('required', false); // Ensure 'Other' input is not required initially
} else if (country) {
// For other countries, hide state select, select 'Other', show/require 'Other' input
$stateSelect.hide().val('Other').prop('required', false); // Hide and make not required
$stateOtherInput.show().prop('required', true); // Show and require 'Other' input
} else {
// No country selected
$stateSelect.hide().val('').prop('required', false); // Hide and make not required
$stateOtherInput.hide().val('').prop('required', false); // Hide and make not required
}
}).trigger('change'); // Trigger on load to set initial state based on pre-selected country (if any)
// Initialize venue visibility on load
$createVenue.filter(':checked').trigger('change');
});

View file

@ -0,0 +1,317 @@
/**
* HVAC Trainer Profile JavaScript
*
* @package HVAC_Community_Events
* @version 2.0.0
*/
jQuery(document).ready(function($) {
// Cache DOM elements
const $profileForm = $('#hvac-profile-form');
const $uploadButton = $('#hvac-upload-photo');
const $removeButton = $('#hvac-remove-photo');
const $photoIdField = $('#profile_photo_id');
const $currentPhoto = $('.hvac-current-photo');
// Form validation
function validateProfileForm() {
let isValid = true;
const errors = [];
// Clear previous errors
$('.hvac-form-error').removeClass('hvac-form-error');
$('.hvac-error-message').remove();
// Required fields
const requiredFields = [
{ id: 'first_name', label: 'First Name' },
{ id: 'last_name', label: 'Last Name' },
{ id: 'display_name', label: 'Display Name' },
{ id: 'email', label: 'Email Address' }
];
requiredFields.forEach(field => {
const $field = $('#' + field.id);
const value = $field.val();
if (!value || value.trim() === '') {
isValid = false;
errors.push(field.label + ' is required');
$field.addClass('hvac-form-error');
$field.after('<span class="hvac-error-message">' + field.label + ' is required</span>');
}
});
// Validate email
const email = $('#email').val();
if (email && !isValidEmail(email)) {
isValid = false;
errors.push('Please enter a valid email address');
$('#email').addClass('hvac-form-error');
$('#email').after('<span class="hvac-error-message">Please enter a valid email address</span>');
}
// Validate phone format if provided
const phone = $('#phone').val();
if (phone && !isValidPhone(phone)) {
isValid = false;
errors.push('Please enter a valid phone number');
$('#phone').addClass('hvac-form-error');
$('#phone').after('<span class="hvac-error-message">Please enter a valid phone number</span>');
}
// Validate URLs if provided
const website = $('#website').val();
if (website && !isValidURL(website)) {
isValid = false;
errors.push('Please enter a valid website URL');
$('#website').addClass('hvac-form-error');
$('#website').after('<span class="hvac-error-message">Please enter a valid website URL</span>');
}
const linkedin = $('#linkedin').val();
if (linkedin && !isValidURL(linkedin)) {
isValid = false;
errors.push('Please enter a valid LinkedIn URL');
$('#linkedin').addClass('hvac-form-error');
$('#linkedin').after('<span class="hvac-error-message">Please enter a valid LinkedIn URL</span>');
}
// Validate years of experience
const years = $('#years_experience').val();
if (years && (years < 0 || years > 50)) {
isValid = false;
errors.push('Years of experience must be between 0 and 50');
$('#years_experience').addClass('hvac-form-error');
$('#years_experience').after('<span class="hvac-error-message">Must be between 0 and 50</span>');
}
return isValid;
}
// Email validation
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// Phone validation
function isValidPhone(phone) {
const digits = phone.replace(/\D/g, '');
return digits.length >= 10 && digits.length <= 15;
}
// URL validation
function isValidURL(url) {
try {
new URL(url);
return true;
} catch (_) {
return false;
}
}
// Show message
function showMessage(message, type = 'success') {
const messageClass = type === 'success' ? 'hvac-message-success' : 'hvac-message-error';
const $message = $('<div class="hvac-message ' + messageClass + '">' + message + '</div>');
// Remove any existing messages
$('.hvac-message').remove();
// Add new message
$('.hvac-page-header').after($message);
// Auto-hide success messages after 5 seconds
if (type === 'success') {
setTimeout(function() {
$message.fadeOut(function() {
$(this).remove();
});
}, 5000);
}
// Scroll to top
$('html, body').animate({
scrollTop: $('.hvac-page-header').offset().top - 100
}, 300);
}
// Handle profile form submission
if ($profileForm.length) {
$profileForm.on('submit', function(e) {
e.preventDefault();
// Validate form
if (!validateProfileForm()) {
return false;
}
// Disable submit button
const $submitButton = $profileForm.find('button[type="submit"]');
const originalText = $submitButton.text();
$submitButton.prop('disabled', true).text('Saving...');
// Gather form data
const formData = {
action: 'hvac_update_profile',
nonce: hvacProfile.nonce,
first_name: $('#first_name').val(),
last_name: $('#last_name').val(),
display_name: $('#display_name').val(),
email: $('#email').val(),
phone: $('#phone').val(),
description: $('#description').val(),
city: $('#city').val(),
state: $('#state').val(),
country: $('#country').val(),
years_experience: $('#years_experience').val(),
certifications: $('#certifications').val(),
website: $('#website').val(),
linkedin: $('#linkedin').val(),
profile_photo_id: $('#profile_photo_id').val()
};
// Send AJAX request
$.ajax({
url: hvacProfile.ajax_url,
type: 'POST',
data: formData,
success: function(response) {
if (response.success) {
showMessage(response.data || 'Profile updated successfully.', 'success');
} else {
showMessage(response.data || 'An error occurred while updating your profile.', 'error');
}
},
error: function() {
showMessage('An error occurred. Please try again.', 'error');
},
complete: function() {
// Re-enable submit button
$submitButton.prop('disabled', false).text(originalText);
}
});
});
}
// Handle photo upload
if ($uploadButton.length) {
let mediaUploader;
$uploadButton.on('click', function(e) {
e.preventDefault();
// If the media uploader already exists, open it
if (mediaUploader) {
mediaUploader.open();
return;
}
// Create the media uploader
mediaUploader = wp.media({
title: 'Choose Profile Photo',
button: {
text: 'Use this photo'
},
multiple: false,
library: {
type: 'image'
}
});
// When an image is selected, run a callback
mediaUploader.on('select', function() {
const attachment = mediaUploader.state().get('selection').first().toJSON();
// Update the photo preview
$currentPhoto.html('<img src="' + attachment.sizes.thumbnail.url + '" alt="Profile photo" />');
// Update the hidden field
$photoIdField.val(attachment.id);
// Update button text
$uploadButton.text('Change Photo');
// Show remove button if not already visible
if (!$removeButton.length) {
const removeBtn = '<button type="button" id="hvac-remove-photo" class="hvac-button hvac-button-danger-outline">Remove Photo</button>';
$uploadButton.after(removeBtn);
}
});
// Open the media uploader
mediaUploader.open();
});
}
// Handle photo removal
$(document).on('click', '#hvac-remove-photo', function(e) {
e.preventDefault();
// Clear the photo preview
$currentPhoto.html('<div class="hvac-photo-placeholder">No photo uploaded</div>');
// Clear the hidden field
$photoIdField.val('');
// Update button text
$uploadButton.text('Upload Photo');
// Remove the remove button
$(this).remove();
});
// Real-time validation
$('#email').on('blur', function() {
const email = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (email && !isValidEmail(email)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid email address</span>');
}
});
$('#phone').on('blur', function() {
const phone = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (phone && !isValidPhone(phone)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid phone number</span>');
}
});
$('#website, #linkedin').on('blur', function() {
const url = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (url && !isValidURL(url)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid URL</span>');
}
});
// Auto-format phone number
$('#phone').on('input', function() {
let value = $(this).val().replace(/\D/g, '');
if (value.length > 0) {
if (value.length <= 3) {
value = value;
} else if (value.length <= 6) {
value = value.slice(0, 3) + '-' + value.slice(3);
} else if (value.length <= 10) {
value = value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6);
} else {
value = value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6, 10);
}
}
$(this).val(value);
});
});

283
assets/js/hvac-venues.js Normal file
View file

@ -0,0 +1,283 @@
/**
* HVAC Venues JavaScript
*
* @package HVAC_Community_Events
* @version 2.0.0
*/
jQuery(document).ready(function($) {
// Cache DOM elements
const $venueForm = $('#hvac-venue-form');
const $deleteButton = $('#hvac-delete-venue');
const $filterForm = $('.hvac-filter-form');
// Form validation
function validateVenueForm() {
let isValid = true;
const errors = [];
// Clear previous errors
$('.hvac-form-error').removeClass('hvac-form-error');
$('.hvac-error-message').remove();
// Required fields
const requiredFields = [
{ id: 'venue_name', label: 'Venue Name' },
{ id: 'venue_address', label: 'Street Address' },
{ id: 'venue_city', label: 'City' },
{ id: 'venue_state', label: 'State/Province' },
{ id: 'venue_zip', label: 'Zip/Postal Code' },
{ id: 'venue_country', label: 'Country' }
];
requiredFields.forEach(field => {
const $field = $('#' + field.id);
const value = $field.val();
if (!value || value.trim() === '') {
isValid = false;
errors.push(field.label + ' is required');
$field.addClass('hvac-form-error');
$field.after('<span class="hvac-error-message">' + field.label + ' is required</span>');
}
});
// Validate phone format if provided
const phone = $('#venue_phone').val();
if (phone && !isValidPhone(phone)) {
isValid = false;
errors.push('Please enter a valid phone number');
$('#venue_phone').addClass('hvac-form-error');
$('#venue_phone').after('<span class="hvac-error-message">Please enter a valid phone number</span>');
}
// Validate website URL if provided
const website = $('#venue_website').val();
if (website && !isValidURL(website)) {
isValid = false;
errors.push('Please enter a valid website URL');
$('#venue_website').addClass('hvac-form-error');
$('#venue_website').after('<span class="hvac-error-message">Please enter a valid website URL</span>');
}
return isValid;
}
// Phone validation
function isValidPhone(phone) {
// Remove all non-digits
const digits = phone.replace(/\D/g, '');
// Check if it's 10 or 11 digits (US/Canada)
return digits.length >= 10 && digits.length <= 15;
}
// URL validation
function isValidURL(url) {
try {
new URL(url);
return true;
} catch (_) {
return false;
}
}
// Show success message
function showMessage(message, type = 'success') {
const messageClass = type === 'success' ? 'hvac-message-success' : 'hvac-message-error';
const $message = $('<div class="hvac-message ' + messageClass + '">' + message + '</div>');
// Remove any existing messages
$('.hvac-message').remove();
// Add new message
$('.hvac-page-header').after($message);
// Auto-hide success messages after 5 seconds
if (type === 'success') {
setTimeout(function() {
$message.fadeOut(function() {
$(this).remove();
});
}, 5000);
}
// Scroll to top
$('html, body').animate({
scrollTop: $('.hvac-page-header').offset().top - 100
}, 300);
}
// Handle venue form submission
if ($venueForm.length) {
$venueForm.on('submit', function(e) {
e.preventDefault();
// Validate form
if (!validateVenueForm()) {
return false;
}
// Disable submit button
const $submitButton = $venueForm.find('button[type="submit"]');
const originalText = $submitButton.text();
$submitButton.prop('disabled', true).text('Saving...');
// Gather form data
const formData = {
action: 'hvac_save_venue',
nonce: hvacVenues.nonce,
venue_id: $('input[name="venue_id"]').val(),
venue_name: $('#venue_name').val(),
venue_description: $('#venue_description').val(),
venue_address: $('#venue_address').val(),
venue_city: $('#venue_city').val(),
venue_state: $('#venue_state').val(),
venue_zip: $('#venue_zip').val(),
venue_country: $('#venue_country').val(),
venue_phone: $('#venue_phone').val(),
venue_website: $('#venue_website').val()
};
// Send AJAX request
$.ajax({
url: hvacVenues.ajax_url,
type: 'POST',
data: formData,
success: function(response) {
if (response.success) {
showMessage(response.data.message, 'success');
// If creating new venue, update form to edit mode
if (!formData.venue_id && response.data.venue_id) {
$('input[name="venue_id"]').val(response.data.venue_id);
$('.hvac-page-header h1').text('Edit Venue');
$('.hvac-breadcrumb').html('<a href="/trainer/dashboard/">Trainer</a> &gt; <a href="/trainer/venue/list/">Venues</a> &gt; Edit');
// Add delete button if not present
if (!$('#hvac-delete-venue').length) {
const deleteButton = '<button type="button" id="hvac-delete-venue" class="hvac-button hvac-button-danger" data-venue-id="' + response.data.venue_id + '">Delete Venue</button>';
$('.hvac-form-actions').append(deleteButton);
}
}
// Update button text
$submitButton.text('Update Venue');
} else {
showMessage(response.data || 'An error occurred while saving the venue.', 'error');
}
},
error: function() {
showMessage('An error occurred. Please try again.', 'error');
},
complete: function() {
// Re-enable submit button
$submitButton.prop('disabled', false).text(originalText);
}
});
});
}
// Handle venue deletion
$(document).on('click', '#hvac-delete-venue', function(e) {
e.preventDefault();
const $deleteButton = $(this);
const venueId = $deleteButton.data('venue-id');
if (!venueId) {
showMessage('Invalid venue ID', 'error');
return;
}
// Confirm deletion
if (!confirm('Are you sure you want to delete this venue? This action cannot be undone.')) {
return;
}
// Disable button
$deleteButton.prop('disabled', true).text('Deleting...');
// Send delete request
$.ajax({
url: hvacVenues.ajax_url,
type: 'POST',
data: {
action: 'hvac_delete_venue',
nonce: hvacVenues.nonce,
venue_id: venueId
},
success: function(response) {
if (response.success) {
showMessage(response.data || 'Venue deleted successfully.', 'success');
// Redirect to venues list after 2 seconds
setTimeout(function() {
window.location.href = '/trainer/venue/list/';
}, 2000);
} else {
showMessage(response.data || 'Failed to delete venue.', 'error');
// Re-enable button
$deleteButton.prop('disabled', false).text('Delete Venue');
}
},
error: function() {
showMessage('An error occurred. Please try again.', 'error');
// Re-enable button
$deleteButton.prop('disabled', false).text('Delete Venue');
}
});
});
// Handle filter form submission
if ($filterForm.length) {
// Prevent form submission on enter in search field
$filterForm.find('input[name="search"]').on('keypress', function(e) {
if (e.which === 13) {
e.preventDefault();
$filterForm.find('button[type="submit"]').click();
}
});
}
// Real-time validation
$('#venue_phone').on('blur', function() {
const phone = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (phone && !isValidPhone(phone)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid phone number</span>');
}
});
$('#venue_website').on('blur', function() {
const website = $(this).val();
$('.hvac-error-message', $(this).parent()).remove();
$(this).removeClass('hvac-form-error');
if (website && !isValidURL(website)) {
$(this).addClass('hvac-form-error');
$(this).after('<span class="hvac-error-message">Please enter a valid website URL</span>');
}
});
// Auto-format phone number
$('#venue_phone').on('input', function() {
let value = $(this).val().replace(/\D/g, '');
if (value.length > 0) {
if (value.length <= 3) {
value = value;
} else if (value.length <= 6) {
value = value.slice(0, 3) + '-' + value.slice(3);
} else if (value.length <= 10) {
value = value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6);
} else {
value = value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6, 10);
}
}
$(this).val(value);
});
});

View file

@ -0,0 +1,41 @@
We need to make a few major updates to the plugin.
We will be refactoring the Trainer Registration Page.
1. First, we want to edit the "Personal Information" section at the top of the form to be better aligned with our desired Trainer Profile page (which is associated with the User post type):[
- move the "Application Details" question to this section
- Ensure that the custom fields in this section are actually created as custom fields in the User post type.
2. The Existing "Business Information" section should be renamed to "Training Organization Information". This will align with the TEC post type called "Organizer".
- It will list the following pre-existing fields from the TEC plugin ("*" means required): Org Name *, Org Phone *, Org Email *, Org Website, Org Description *, Org Logo * (featured image of the Organizer).
- We want to add new custom fields called "Org Headquarters City", "Org Headquarters State", and "Org Headquarters Country",
- We also want all custom fields moved from the "Training Information" into this new "Training Organization Information" section. These fields likely do not currently exist as field types, so we need to create them.
3. We must modify how "Venue" records are created. A Venue is a special post type from The Events Calendar Plugin, and all users with the HVAC Trainer role should be associated with at least 1 Venue (created during registration). Here's the changes we want in this section:
- The "Address Information" extion should be renamed to "Training Venue Information", and it should be moved to the end of the form.
- This section already has a question which asks "Create Training Venue Profile?", which is meant to auto-populate the training venue profile. It should default to "Yes".
- The following custom fields fields will be conditionally visible when "Yes" is selected, and though most are auto-populated from the Org section above, the user should be able to edit them: Venue Name (defaults to [Org Name] + " of " [Org City]), Street Address, City, State/Province, Country, Postal Code, Phone, Website
Next, we want to update/add pages which are accessible to logged in trainers.
1. "Training Venues" Page (/trainer/venue/list): This page will allow the users to view a list of all training venues, even ones they didn't create. This should be in table format with similar filters/pagination as the dashboard page. If they are the author of a venue, then they there should be an "Edit" button beside that venue in the table.
2. "Manage Venue" Page (/trainer/venue/manage): This should have similar functionality to the "Manage Event" page, but will allow the user to create new Venuws or edit Venues which they are the author of, including all default and custom fields related to the Venue post type.
3. "Trainer Profile" Page (/trainer/profile and /trainer/profile/edit): This will be where trainers can view all default and custom fields related to the User post type.
4. "Training Organizers" Page (/trainer/organizer/list): This should have similar functionality to the "Manage Event" page, but will allow the user to create new Venuws or edit Organizers which they are the author of, including all default and custom fields related to the Organizer post type.
5. "Manage Venue" Page (/trainer/organizer/manage): This should have similar functionality to the "Manage Event" page, but will allow the user to create new Venuws or edit Organizers which they are the author of, including all default and custom fields related to the Organizer post type.
Finally, We should refactor the navigation, URL structure, and add Breadcrumbs.
- URL structure should generally align with the menu structure and we should also add breadcrumbs at the top of each page which also match the same patterns (trainer/dashboard/ = Trainer > Events > Dashboard; trainer/certificates/reports = Trainer > Certificates > Reports;
- Instead of having a series of custom buttons, we should create a native wordpress "Menu". It should be a Secondary Menu only available to Logged in users with the HVAC Trainer Role, and will only be shown on the custom pages from our custom plugin. As follows:
Events
├─ Dashboard
├─ New Event
Certificates
├─ Reports
├─ New Certificate
Customize
├─ Personal Profile
├─ Training Organizers
│ ├─ Add New Organizer (links to the Manage Organizer Page)
├─ Training Venues
│ ├─ Add New Venue (links to the Manage Venue Page)
Logout
Help
Your first task is to perform a detailed review of the existing page structure and ultrathink a plan to acheive the desired changes. Pay special attention to all of the requisite custom fields and ensure that you follow best practices in creating the field and associating them with the appropriate post types. If there are inherent wordpress limitations which prevent you from achieving your directives, inform the user before making your plan.

View file

@ -0,0 +1,575 @@
<?php
/**
* HVAC Organizers Management
*
* @package HVAC_Community_Events
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* HVAC_Organizers class
*/
class HVAC_Organizers {
/**
* Constructor
*/
public function __construct() {
// Register shortcodes
add_shortcode('hvac_trainer_organizers_list', array($this, 'render_organizers_list'));
add_shortcode('hvac_trainer_organizer_manage', array($this, 'render_organizer_manage'));
// Enqueue scripts
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
// Handle AJAX requests
add_action('wp_ajax_hvac_save_organizer', array($this, 'ajax_save_organizer'));
add_action('wp_ajax_hvac_delete_organizer', array($this, 'ajax_delete_organizer'));
add_action('wp_ajax_hvac_upload_org_logo', array($this, 'ajax_upload_org_logo'));
}
/**
* Enqueue scripts and styles
*/
public function enqueue_scripts() {
if (is_page('trainer/organizer/list') || is_page('trainer/organizer/manage')) {
wp_enqueue_style(
'hvac-organizers-style',
HVAC_PLUGIN_URL . 'assets/css/hvac-organizers.css',
array(),
HVAC_PLUGIN_VERSION
);
wp_enqueue_script(
'hvac-organizers-js',
HVAC_PLUGIN_URL . 'assets/js/hvac-organizers.js',
array('jquery'),
HVAC_PLUGIN_VERSION,
true
);
wp_localize_script('hvac-organizers-js', 'hvacOrganizers', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('hvac_organizers_nonce')
));
// Enqueue media uploader for logo upload
if (is_page('trainer/organizer/manage')) {
wp_enqueue_media();
}
}
}
/**
* Render organizers list
*/
public function render_organizers_list() {
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
return '<p>You must be logged in as a trainer to view this page.</p>';
}
ob_start();
?>
<div class="hvac-organizers-list">
<div class="hvac-page-header">
<h1>Training Organizers</h1>
<a href="/trainer/organizer/manage/" class="hvac-button hvac-button-primary">Add New Organizer</a>
</div>
<div class="hvac-breadcrumb">
<a href="/trainer/dashboard/">Trainer</a> &gt; <a href="/trainer/organizer/list/">Organizers</a> &gt; List
</div>
<?php $this->render_organizers_table(); ?>
</div>
<?php
return ob_get_clean();
}
/**
* Render organizers table
*/
private function render_organizers_table() {
$current_user_id = get_current_user_id();
// Get pagination parameters
$page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$per_page = 20;
$offset = ($page - 1) * $per_page;
// Build query
$query_args = array(
'post_type' => class_exists('Tribe__Events__Main') ? Tribe__Events__Main::ORGANIZER_POST_TYPE : 'tribe_organizer',
'posts_per_page' => $per_page,
'offset' => $offset,
'orderby' => 'title',
'order' => 'ASC',
'post_status' => 'publish',
'author' => $current_user_id // Only show organizers created by this user
);
// Filter handling
if (!empty($_GET['search'])) {
$query_args['s'] = sanitize_text_field($_GET['search']);
}
// Get organizers
$organizers_query = new WP_Query($query_args);
// Get total count for pagination
$total_organizers = $organizers_query->found_posts;
$total_pages = ceil($total_organizers / $per_page);
?>
<div class="hvac-organizers-filters">
<form method="get" class="hvac-filter-form">
<div class="hvac-filter-row">
<div class="hvac-filter-group">
<input type="text" name="search" placeholder="Search organizers..."
value="<?php echo esc_attr($_GET['search'] ?? ''); ?>" />
</div>
<div class="hvac-filter-group">
<button type="submit" class="hvac-button">Search</button>
<a href="/trainer/organizer/list/" class="hvac-button hvac-button-secondary">Clear</a>
</div>
</div>
</form>
</div>
<div class="hvac-organizers-table-wrapper">
<table class="hvac-organizers-table">
<thead>
<tr>
<th>Logo</th>
<th>Organization Name</th>
<th>Headquarters</th>
<th>Contact</th>
<th>Website</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
if ($organizers_query->have_posts()) {
while ($organizers_query->have_posts()) {
$organizers_query->the_post();
$organizer_id = get_the_ID();
// Get organizer meta
$phone = get_post_meta($organizer_id, '_OrganizerPhone', true);
$email = get_post_meta($organizer_id, '_OrganizerEmail', true);
$website = get_post_meta($organizer_id, '_OrganizerWebsite', true);
// Get headquarters location
$hq_city = get_post_meta($organizer_id, '_hvac_headquarters_city', true);
$hq_state = get_post_meta($organizer_id, '_hvac_headquarters_state', true);
$hq_country = get_post_meta($organizer_id, '_hvac_headquarters_country', true);
$hq_parts = array_filter(array($hq_city, $hq_state, $hq_country));
$headquarters = implode(', ', $hq_parts);
?>
<tr>
<td class="hvac-org-logo">
<?php if (has_post_thumbnail()): ?>
<?php the_post_thumbnail('thumbnail'); ?>
<?php else: ?>
<div class="hvac-logo-placeholder">
<span><?php echo esc_html(substr(get_the_title(), 0, 1)); ?></span>
</div>
<?php endif; ?>
</td>
<td>
<strong><?php the_title(); ?></strong>
</td>
<td><?php echo esc_html($headquarters ?: 'Not specified'); ?></td>
<td>
<?php if ($email): ?>
<a href="mailto:<?php echo esc_attr($email); ?>"><?php echo esc_html($email); ?></a>
<?php endif; ?>
<?php if ($phone): ?>
<br /><?php echo esc_html($phone); ?>
<?php endif; ?>
</td>
<td>
<?php if ($website): ?>
<a href="<?php echo esc_url($website); ?>" target="_blank">Visit Website</a>
<?php else: ?>
<span class="hvac-text-muted">Not specified</span>
<?php endif; ?>
</td>
<td>
<a href="/trainer/organizer/manage/?organizer_id=<?php echo $organizer_id; ?>"
class="hvac-button hvac-button-small">Edit</a>
</td>
</tr>
<?php
}
} else {
?>
<tr>
<td colspan="6" class="hvac-no-results">No organizers found.</td>
</tr>
<?php
}
wp_reset_postdata();
?>
</tbody>
</table>
</div>
<?php if ($total_pages > 1): ?>
<div class="hvac-pagination">
<?php
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'current' => $page,
'total' => $total_pages,
'prev_text' => '&laquo; Previous',
'next_text' => 'Next &raquo;'
));
?>
</div>
<?php endif; ?>
<?php
}
/**
* Render organizer manage form
*/
public function render_organizer_manage() {
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
return '<p>You must be logged in as a trainer to view this page.</p>';
}
$organizer_id = isset($_GET['organizer_id']) ? intval($_GET['organizer_id']) : 0;
$organizer = null;
if ($organizer_id) {
$organizer = get_post($organizer_id);
// Check if user can edit this organizer
if (!$organizer || $organizer->post_author != get_current_user_id()) {
return '<p>You do not have permission to edit this organizer.</p>';
}
}
ob_start();
?>
<div class="hvac-organizer-manage">
<div class="hvac-page-header">
<h1><?php echo $organizer ? 'Edit Organizer' : 'Create New Organizer'; ?></h1>
</div>
<div class="hvac-breadcrumb">
<a href="/trainer/dashboard/">Trainer</a> &gt;
<a href="/trainer/organizer/list/">Organizers</a> &gt;
<?php echo $organizer ? 'Edit' : 'New'; ?>
</div>
<form id="hvac-organizer-form" class="hvac-form">
<?php wp_nonce_field('hvac_organizer_manage', 'hvac_organizer_nonce'); ?>
<input type="hidden" name="organizer_id" value="<?php echo $organizer_id; ?>" />
<div class="hvac-form-section">
<h3>Organization Logo</h3>
<div class="hvac-org-logo-upload">
<div class="hvac-current-logo">
<?php if ($organizer && has_post_thumbnail($organizer_id)): ?>
<?php echo get_the_post_thumbnail($organizer_id, 'medium'); ?>
<?php else: ?>
<div class="hvac-logo-placeholder-large">
<span>No logo uploaded</span>
</div>
<?php endif; ?>
</div>
<div class="hvac-logo-actions">
<button type="button" id="hvac-upload-logo" class="hvac-button hvac-button-secondary">
<?php echo ($organizer && has_post_thumbnail($organizer_id)) ? 'Change Logo' : 'Upload Logo'; ?>
</button>
<?php if ($organizer && has_post_thumbnail($organizer_id)): ?>
<button type="button" id="hvac-remove-logo" class="hvac-button hvac-button-danger-outline">
Remove Logo
</button>
<?php endif; ?>
<input type="hidden" id="org_logo_id" name="org_logo_id"
value="<?php echo $organizer ? get_post_thumbnail_id($organizer_id) : ''; ?>" />
</div>
<p class="hvac-help-text">Recommended size: 300x300px. Maximum file size: 2MB.</p>
</div>
</div>
<div class="hvac-form-section">
<h3>Organization Information</h3>
<div class="hvac-form-row">
<label for="org_name">Organization Name *</label>
<input type="text" id="org_name" name="org_name" required
value="<?php echo $organizer ? esc_attr($organizer->post_title) : ''; ?>" />
</div>
<div class="hvac-form-row">
<label for="org_description">Description</label>
<textarea id="org_description" name="org_description" rows="4"><?php
echo $organizer ? esc_textarea($organizer->post_content) : '';
?></textarea>
</div>
</div>
<div class="hvac-form-section">
<h3>Headquarters Location</h3>
<div class="hvac-form-row">
<label for="hq_city">City *</label>
<input type="text" id="hq_city" name="hq_city" required
value="<?php echo $organizer ? esc_attr(get_post_meta($organizer_id, '_hvac_headquarters_city', true)) : ''; ?>" />
</div>
<div class="hvac-form-row hvac-form-row-half">
<div>
<label for="hq_state">State/Province *</label>
<input type="text" id="hq_state" name="hq_state" required
value="<?php echo $organizer ? esc_attr(get_post_meta($organizer_id, '_hvac_headquarters_state', true)) : ''; ?>" />
</div>
<div>
<label for="hq_country">Country *</label>
<select id="hq_country" name="hq_country" required>
<?php
$current_country = $organizer ? get_post_meta($organizer_id, '_hvac_headquarters_country', true) : 'United States';
$countries = array(
'United States' => 'United States',
'Canada' => 'Canada',
'United Kingdom' => 'United Kingdom',
'Australia' => 'Australia'
);
foreach ($countries as $code => $name) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr($code),
selected($current_country, $code, false),
esc_html($name)
);
}
?>
</select>
</div>
</div>
</div>
<div class="hvac-form-section">
<h3>Contact Information</h3>
<div class="hvac-form-row">
<label for="org_phone">Phone</label>
<input type="tel" id="org_phone" name="org_phone"
value="<?php echo $organizer ? esc_attr(get_post_meta($organizer_id, '_OrganizerPhone', true)) : ''; ?>" />
</div>
<div class="hvac-form-row">
<label for="org_email">Email</label>
<input type="email" id="org_email" name="org_email"
value="<?php echo $organizer ? esc_attr(get_post_meta($organizer_id, '_OrganizerEmail', true)) : ''; ?>" />
</div>
<div class="hvac-form-row">
<label for="org_website">Website</label>
<input type="url" id="org_website" name="org_website"
value="<?php echo $organizer ? esc_attr(get_post_meta($organizer_id, '_OrganizerWebsite', true)) : ''; ?>" />
</div>
</div>
<div class="hvac-form-actions">
<button type="submit" class="hvac-button hvac-button-primary">
<?php echo $organizer ? 'Update Organizer' : 'Create Organizer'; ?>
</button>
<a href="/trainer/organizer/list/" class="hvac-button hvac-button-secondary">Cancel</a>
<?php if ($organizer): ?>
<button type="button" id="hvac-delete-organizer" class="hvac-button hvac-button-danger"
data-organizer-id="<?php echo $organizer_id; ?>">Delete Organizer</button>
<?php endif; ?>
</div>
</form>
</div>
<?php
return ob_get_clean();
}
/**
* AJAX handler for saving organizer
*/
public function ajax_save_organizer() {
check_ajax_referer('hvac_organizers_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
$organizer_id = isset($_POST['organizer_id']) ? intval($_POST['organizer_id']) : 0;
// If editing, check ownership
if ($organizer_id) {
$organizer = get_post($organizer_id);
if (!$organizer || $organizer->post_author != get_current_user_id()) {
wp_send_json_error('You do not have permission to edit this organizer.');
}
}
// Prepare organizer data
$organizer_data = array(
'Organizer' => sanitize_text_field($_POST['org_name']),
'Description' => wp_kses_post($_POST['org_description']),
'Phone' => sanitize_text_field($_POST['org_phone']),
'Email' => sanitize_email($_POST['org_email']),
'Website' => esc_url_raw($_POST['org_website'])
);
if ($organizer_id) {
$organizer_data['ID'] = $organizer_id;
$result = function_exists('tribe_update_organizer') ?
tribe_update_organizer($organizer_id, $organizer_data) :
wp_update_post(array(
'ID' => $organizer_id,
'post_title' => $organizer_data['Organizer'],
'post_content' => $organizer_data['Description']
));
} else {
$organizer_data['post_status'] = 'publish';
$organizer_data['post_author'] = get_current_user_id();
$result = function_exists('tribe_create_organizer') ?
tribe_create_organizer($organizer_data) :
wp_insert_post(array(
'post_type' => 'tribe_organizer',
'post_title' => $organizer_data['Organizer'],
'post_content' => $organizer_data['Description'],
'post_status' => 'publish',
'post_author' => get_current_user_id()
));
}
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
// Update custom meta fields
$organizer_id = $organizer_id ?: $result;
update_post_meta($organizer_id, '_hvac_headquarters_city', sanitize_text_field($_POST['hq_city']));
update_post_meta($organizer_id, '_hvac_headquarters_state', sanitize_text_field($_POST['hq_state']));
update_post_meta($organizer_id, '_hvac_headquarters_country', sanitize_text_field($_POST['hq_country']));
// Update phone, email, website meta
update_post_meta($organizer_id, '_OrganizerPhone', sanitize_text_field($_POST['org_phone']));
update_post_meta($organizer_id, '_OrganizerEmail', sanitize_email($_POST['org_email']));
update_post_meta($organizer_id, '_OrganizerWebsite', esc_url_raw($_POST['org_website']));
// Handle logo
if (isset($_POST['org_logo_id'])) {
$logo_id = intval($_POST['org_logo_id']);
if ($logo_id) {
set_post_thumbnail($organizer_id, $logo_id);
} else {
delete_post_thumbnail($organizer_id);
}
}
// Update user's organizer_id if this is their first organizer
$user_organizer_id = get_user_meta(get_current_user_id(), 'organizer_id', true);
if (!$user_organizer_id) {
update_user_meta(get_current_user_id(), 'organizer_id', $organizer_id);
}
wp_send_json_success(array(
'message' => $organizer_id ? 'Organizer updated successfully.' : 'Organizer created successfully.',
'organizer_id' => $organizer_id
));
}
/**
* AJAX handler for deleting organizer
*/
public function ajax_delete_organizer() {
check_ajax_referer('hvac_organizers_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
$organizer_id = isset($_POST['organizer_id']) ? intval($_POST['organizer_id']) : 0;
if (!$organizer_id) {
wp_send_json_error('Invalid organizer ID');
}
$organizer = get_post($organizer_id);
if (!$organizer || $organizer->post_author != get_current_user_id()) {
wp_send_json_error('You do not have permission to delete this organizer.');
}
// Check if organizer is being used by any events
$events_using_organizer = get_posts(array(
'post_type' => class_exists('Tribe__Events__Main') ? Tribe__Events__Main::POSTTYPE : 'tribe_events',
'meta_query' => array(
array(
'key' => '_EventOrganizerID',
'value' => $organizer_id,
'compare' => '='
)
),
'posts_per_page' => 1
));
if (!empty($events_using_organizer)) {
wp_send_json_error('Cannot delete organizer. It is being used by one or more events.');
}
$result = wp_trash_post($organizer_id);
if ($result) {
// If this was the user's primary organizer, clear it
$user_organizer_id = get_user_meta(get_current_user_id(), 'organizer_id', true);
if ($user_organizer_id == $organizer_id) {
delete_user_meta(get_current_user_id(), 'organizer_id');
}
wp_send_json_success('Organizer deleted successfully.');
} else {
wp_send_json_error('Failed to delete organizer.');
}
}
/**
* AJAX handler for uploading organization logo
*/
public function ajax_upload_org_logo() {
check_ajax_referer('hvac_organizers_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
if (!isset($_FILES['org_logo'])) {
wp_send_json_error('No file uploaded');
}
require_once(ABSPATH . 'wp-admin/includes/image.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
$attachment_id = media_handle_upload('org_logo', 0);
if (is_wp_error($attachment_id)) {
wp_send_json_error($attachment_id->get_error_message());
}
wp_send_json_success(array(
'attachment_id' => $attachment_id,
'url' => wp_get_attachment_image_url($attachment_id, 'medium')
));
}
}

View file

@ -97,6 +97,9 @@ class HVAC_Plugin {
'class-hvac-trainer-status.php',
'class-hvac-access-control.php',
'class-hvac-registration.php',
'class-hvac-venues.php',
'class-hvac-trainer-profile-manager.php',
'class-hvac-organizers.php',
'class-hvac-manage-event.php',
'class-hvac-event-summary.php',
'class-hvac-trainer-profile.php',
@ -301,6 +304,21 @@ class HVAC_Plugin {
new HVAC_Registration();
}
// Initialize venues management
if (class_exists('HVAC_Venues')) {
new HVAC_Venues();
}
// Initialize trainer profile manager
if (class_exists('HVAC_Trainer_Profile_Manager')) {
new HVAC_Trainer_Profile_Manager();
}
// Initialize organizers management
if (class_exists('HVAC_Organizers')) {
new HVAC_Organizers();
}
// Initialize event management
if (class_exists('HVAC_Manage_Event')) {
new HVAC_Manage_Event();

File diff suppressed because it is too large Load diff

View file

@ -136,6 +136,29 @@ class HVAC_Registration {
// error_log('[HVAC REG DEBUG] process_registration_submission: Checking if errors is empty. Result: ' . (empty($errors) ? 'Yes' : 'No'));
}
}
// Handle Organization Logo Upload
$org_logo_data = null;
if (isset($_FILES['org_logo']) && $_FILES['org_logo']['error'] !== UPLOAD_ERR_NO_FILE) {
if ($_FILES['org_logo']['error'] === UPLOAD_ERR_OK) {
if (!is_uploaded_file($_FILES['org_logo']['tmp_name'])) {
$errors['org_logo'] = 'File upload error (invalid temp file).';
} else {
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $_FILES['org_logo']['tmp_name']);
finfo_close($finfo);
if (!in_array($mime_type, $allowed_types)) {
$errors['org_logo'] = 'Invalid file type detected (' . esc_html($mime_type) . '). Please upload a JPG, PNG, or GIF.';
} else {
$org_logo_data = $_FILES['org_logo'];
}
}
} else {
$errors['org_logo'] = 'There was an error uploading the organization logo. Code: ' . $_FILES['org_logo']['error'];
}
}
// --- End File Upload Handling ---
// Validate the rest of the form data
@ -145,7 +168,7 @@ class HVAC_Registration {
// --- Process if No Errors ---
if (empty($errors)) {
$user_id = $this->create_trainer_account($submitted_data, $profile_image_data);
$user_id = $this->create_trainer_account($submitted_data, $profile_image_data, $org_logo_data);
if (is_wp_error($user_id)) {
$errors['account'] = $user_id->get_error_message();
@ -311,6 +334,12 @@ class HVAC_Registration {
<small id="description_hint">A short bio about yourself. This will be displayed on your profile page.</small>
<?php if (isset($errors['description'])) echo '<p class="error-message" id="description_error">' . esc_html($errors['description']) . '</p>'; ?>
</div>
<div class="form-row">
<label for="application_details"><strong>Application Details *</strong></label>
<small>Please explain why you want to create a training account on Upskill HVAC.</small>
<textarea name="application_details" id="application_details" rows="5" required aria-describedby="application_details_error"><?php echo esc_textarea($data['application_details'] ?? ''); ?></textarea>
<?php if (isset($errors['application_details'])) echo '<p class="error-message" id="application_details_error">' . esc_html($errors['application_details']) . '</p>'; ?>
</div>
<div class="form-row">
<label for="profile_image">Profile Image (optional)</label>
<input type="file" name="profile_image" id="profile_image" accept="image/jpeg,image/png,image/gif" aria-describedby="profile_image_hint profile_image_error">
@ -319,119 +348,71 @@ class HVAC_Registration {
</div>
</div>
<!-- Business Information Section -->
<!-- Training Organization Information Section -->
<div class="form-section">
<h3>Business Information</h3>
<h3>Training Organization Information</h3>
<div class="form-row">
<label for="business_name"><strong>Business Name *</strong></label>
<label for="business_name"><strong>Organization Name *</strong></label>
<input type="text" name="business_name" id="business_name" value="<?php echo esc_attr($data['business_name'] ?? ''); ?>" required aria-describedby="business_name_error">
<?php if (isset($errors['business_name'])) echo '<p class="error-message" id="business_name_error">' . esc_html($errors['business_name']) . '</p>'; ?>
</div>
<div class="form-row form-row-half">
<div>
<label for="business_phone"><strong>Business Phone *</strong></label>
<label for="business_phone"><strong>Organization Phone *</strong></label>
<input type="tel" name="business_phone" id="business_phone" value="<?php echo esc_attr($data['business_phone'] ?? ''); ?>" required aria-describedby="business_phone_error">
<?php if (isset($errors['business_phone'])) echo '<p class="error-message" id="business_phone_error">' . esc_html($errors['business_phone']) . '</p>'; ?>
</div>
<div>
<label for="business_email"><strong>Business Email *</strong></label>
<label for="business_email"><strong>Organization Email *</strong></label>
<input type="email" name="business_email" id="business_email" value="<?php echo esc_attr($data['business_email'] ?? ''); ?>" required aria-describedby="business_email_error">
<?php if (isset($errors['business_email'])) echo '<p class="error-message" id="business_email_error">' . esc_html($errors['business_email']) . '</p>'; ?>
</div>
</div>
<div class="form-row">
<label for="business_website">Business Website (optional)</label>
<label for="business_website">Organization Website (optional)</label>
<input type="url" name="business_website" id="business_website" value="<?php echo esc_attr($data['business_website'] ?? ''); ?>" aria-describedby="business_website_error">
<?php if (isset($errors['business_website'])) echo '<p class="error-message" id="business_website_error">' . esc_html($errors['business_website']) . '</p>'; ?>
</div>
<div class="form-row">
<label for="business_description"><strong>Business Description *</strong></label>
<label for="business_description"><strong>Organization Description *</strong></label>
<textarea name="business_description" id="business_description" rows="4" required aria-describedby="business_description_error"><?php echo esc_textarea($data['business_description'] ?? ''); ?></textarea>
<?php if (isset($errors['business_description'])) echo '<p class="error-message" id="business_description_error">' . esc_html($errors['business_description']) . '</p>'; ?>
</div>
</div>
<!-- Address Information Section -->
<div class="form-section">
<h3>Address Information</h3>
<div class="form-row">
<label for="user_country"><strong>Country *</strong></label>
<select name="user_country" id="user_country" required aria-describedby="user_country_error">
<option value="">Select Country</option>
<option value="United States" <?php selected($data['user_country'] ?? '', 'United States'); ?>>United States</option>
<option value="Canada" <?php selected($data['user_country'] ?? '', 'Canada'); ?>>Canada</option>
<option value="" disabled>---</option>
<?php
$countries = $this->get_country_list();
foreach ($countries as $code => $name) {
if ($code !== 'US' && $code !== 'CA') {
echo '<option value="' . esc_attr($name) . '" ' . selected($data['user_country'] ?? '', $name, false) . '>' . esc_html($name) . '</option>';
}
}
?>
</select>
<?php if (isset($errors['user_country'])) echo '<p class="error-message" id="user_country_error">' . esc_html($errors['user_country']) . '</p>'; ?>
</div>
<div class="form-row form-row-half">
<div>
<label for="user_state"><strong>State/Province *</strong></label>
<select name="user_state" id="user_state" required aria-describedby="user_state_error">
<!-- Options loaded by JS -->
<option value="">Select State/Province</option>
<option value="Other" <?php selected($data['user_state'] ?? '', 'Other'); ?>>Other</option>
<?php
// Pre-populate selected state if available from transient
$selected_state = $data['user_state'] ?? '';
if (!empty($selected_state) && $selected_state !== 'Other') {
// Determine if it's a US state or CA province based on country or value itself
$is_us_state = array_key_exists($selected_state, $this->get_us_states());
$is_ca_province = array_key_exists($selected_state, $this->get_canadian_provinces());
if ($is_us_state || $is_ca_province) {
echo '<option value="' . esc_attr($selected_state) . '" selected>' . esc_html($selected_state) . '</option>';
}
}
?>
</select>
<input type="text" name="user_state_other" id="user_state_other"
value="<?php echo esc_attr($data['user_state_other'] ?? ''); ?>"
style="<?php echo (($data['user_state'] ?? '') === 'Other' && ($data['user_country'] ?? '') !== 'United States' && ($data['user_country'] ?? '') !== 'Canada') ? '' : 'display:none;'; ?> margin-top: 0.5rem;"
placeholder="Enter your state/province"
aria-describedby="user_state_other_error">
<?php if (isset($errors['user_state'])) echo '<p class="error-message" id="user_state_error">' . esc_html($errors['user_state']) . '</p>'; ?>
<?php if (isset($errors['user_state_other'])) echo '<p class="error-message" id="user_state_other_error">' . esc_html($errors['user_state_other']) . '</p>'; ?>
</div>
<div>
<label for="user_city"><strong>City *</strong></label>
<input type="text" name="user_city" id="user_city" value="<?php echo esc_attr($data['user_city'] ?? ''); ?>" required aria-describedby="user_city_error">
<?php if (isset($errors['user_city'])) echo '<p class="error-message" id="user_city_error">' . esc_html($errors['user_city']) . '</p>'; ?>
</div>
</div>
<div class="form-row">
<label for="user_zip"><strong>Zip/Postal Code *</strong></label>
<input type="text" name="user_zip" id="user_zip" value="<?php echo esc_attr($data['user_zip'] ?? ''); ?>" required aria-describedby="user_zip_error">
<?php if (isset($errors['user_zip'])) echo '<p class="error-message" id="user_zip_error">' . esc_html($errors['user_zip']) . '</p>'; ?>
</div>
</div>
<!-- Training Venue Section -->
<div class="form-section">
<h3>Training Venue</h3>
<!-- Organization Logo -->
<div class="form-row">
<label id="create_venue_label"><strong>Create Training Venue Profile? *</strong></label>
<small>Do you want to create a Training Venue Profile for your business to use when listing your training events? If yes, we will use the address provided above.</small>
<div class="radio-group" role="radiogroup" aria-labelledby="create_venue_label">
<label><input type="radio" name="create_venue" value="Yes" <?php checked($data['create_venue'] ?? 'No', 'Yes'); ?> required> Yes</label>
<label><input type="radio" name="create_venue" value="No" <?php checked($data['create_venue'] ?? 'No', 'No'); ?>> No</label>
</div>
<?php if (isset($errors['create_venue'])) echo '<p class="error-message">' . esc_html($errors['create_venue']) . '</p>'; ?>
<label for="org_logo"><strong>Organization Logo * </strong></label>
<input type="file" name="org_logo" id="org_logo" accept="image/jpeg,image/png,image/gif" required aria-describedby="org_logo_hint org_logo_error">
<small id="org_logo_hint">Please attach a .jpg, .png, or .gif image. This will be used as your organization's logo.</small>
<?php if (isset($errors['org_logo'])) echo '<p class="error-message" id="org_logo_error">' . esc_html($errors['org_logo']) . '</p>'; ?>
</div>
</div>
<!-- Training Information Section -->
<div class="form-section">
<h3>Training Information</h3>
<!-- Headquarters Location -->
<div class="form-row">
<h4>Organization Headquarters</h4>
</div>
<div class="form-row form-row-thirds">
<div>
<label for="org_headquarters_city">Headquarters City</label>
<input type="text" name="org_headquarters_city" id="org_headquarters_city" value="<?php echo esc_attr($data['org_headquarters_city'] ?? ''); ?>" aria-describedby="org_headquarters_city_error">
<?php if (isset($errors['org_headquarters_city'])) echo '<p class="error-message" id="org_headquarters_city_error">' . esc_html($errors['org_headquarters_city']) . '</p>'; ?>
</div>
<div>
<label for="org_headquarters_state">Headquarters State</label>
<input type="text" name="org_headquarters_state" id="org_headquarters_state" value="<?php echo esc_attr($data['org_headquarters_state'] ?? ''); ?>" aria-describedby="org_headquarters_state_error">
<?php if (isset($errors['org_headquarters_state'])) echo '<p class="error-message" id="org_headquarters_state_error">' . esc_html($errors['org_headquarters_state']) . '</p>'; ?>
</div>
<div>
<label for="org_headquarters_country">Headquarters Country</label>
<input type="text" name="org_headquarters_country" id="org_headquarters_country" value="<?php echo esc_attr($data['org_headquarters_country'] ?? ''); ?>" aria-describedby="org_headquarters_country_error">
<?php if (isset($errors['org_headquarters_country'])) echo '<p class="error-message" id="org_headquarters_country_error">' . esc_html($errors['org_headquarters_country']) . '</p>'; ?>
</div>
</div>
<!-- Training Information (moved from previous section) -->
<div class="form-row">
<h4>Training Capabilities</h4>
</div>
<div class="form-row">
<label id="business_type_label"><strong>Business Type *</strong></label>
<small>What type of business are you?</small>
@ -514,23 +495,113 @@ class HVAC_Registration {
</div>
<?php if (isset($errors['training_resources'])) echo '<p class="error-message">' . esc_html($errors['training_resources']) . '</p>'; ?>
</div>
</div>
<!-- Application Details Section -->
<div class="form-section">
<h3>Application Details</h3>
<div class="form-row">
<label for="application_details"><strong>Application Details *</strong></label>
<small>Please explain why you want to create a training account on Upskill HVAC.</small>
<textarea name="application_details" id="application_details" rows="5" required aria-describedby="application_details_error"><?php echo esc_textarea($data['application_details'] ?? ''); ?></textarea>
<?php if (isset($errors['application_details'])) echo '<p class="error-message" id="application_details_error">' . esc_html($errors['application_details']) . '</p>'; ?>
</div>
<div class="form-row">
<label for="annual_revenue_target">Annual Revenue Target (optional)</label>
<small>It's our goal to help you generate revenue through your training. How much revenue are you looking to generate annually though your training on Upskill HVAC?</small>
<input type="number" name="annual_revenue_target" id="annual_revenue_target" min="0" step="1" value="<?php echo esc_attr($data['annual_revenue_target'] ?? ''); ?>">
</div>
</div>
</div>
<!-- Training Venue Information Section -->
<div class="form-section">
<h3>Training Venue Information</h3>
<div class="form-row">
<label id="create_venue_label"><strong>Create Training Venue Profile? *</strong></label>
<small>Do you want to create a Training Venue Profile for your organization? This will be used when listing your training events.</small>
<div class="radio-group" role="radiogroup" aria-labelledby="create_venue_label">
<label><input type="radio" name="create_venue" value="Yes" <?php checked($data['create_venue'] ?? 'Yes', 'Yes'); ?> required> Yes</label>
<label><input type="radio" name="create_venue" value="No" <?php checked($data['create_venue'] ?? 'Yes', 'No'); ?>> No</label>
</div>
<?php if (isset($errors['create_venue'])) echo '<p class="error-message">' . esc_html($errors['create_venue']) . '</p>'; ?>
</div>
<div id="venue-details" style="<?php echo ($data['create_venue'] ?? 'Yes') === 'No' ? 'display:none;' : ''; ?>">
<div class="form-row">
<label for="venue_name"><strong>Venue Name *</strong></label>
<input type="text" name="venue_name" id="venue_name" value="<?php echo esc_attr($data['venue_name'] ?? ''); ?>" aria-describedby="venue_name_hint venue_name_error">
<small id="venue_name_hint">Defaults to "[Organization Name] of [City]"</small>
<?php if (isset($errors['venue_name'])) echo '<p class="error-message" id="venue_name_error">' . esc_html($errors['venue_name']) . '</p>'; ?>
</div>
<div class="form-row">
<label for="venue_address"><strong>Street Address *</strong></label>
<input type="text" name="venue_address" id="venue_address" value="<?php echo esc_attr($data['venue_address'] ?? ''); ?>" aria-describedby="venue_address_error">
<?php if (isset($errors['venue_address'])) echo '<p class="error-message" id="venue_address_error">' . esc_html($errors['venue_address']) . '</p>'; ?>
</div>
<div class="form-row">
<label for="user_country"><strong>Country *</strong></label>
<select name="user_country" id="user_country" required aria-describedby="user_country_error">
<option value="">Select Country</option>
<option value="United States" <?php selected($data['user_country'] ?? '', 'United States'); ?>>United States</option>
<option value="Canada" <?php selected($data['user_country'] ?? '', 'Canada'); ?>>Canada</option>
<option value="" disabled>---</option>
<?php
$countries = $this->get_country_list();
foreach ($countries as $code => $name) {
if ($code !== 'US' && $code !== 'CA') {
echo '<option value="' . esc_attr($name) . '" ' . selected($data['user_country'] ?? '', $name, false) . '>' . esc_html($name) . '</option>';
}
}
?>
</select>
<?php if (isset($errors['user_country'])) echo '<p class="error-message" id="user_country_error">' . esc_html($errors['user_country']) . '</p>'; ?>
</div>
<div class="form-row form-row-half">
<div>
<label for="user_state"><strong>State/Province *</strong></label>
<select name="user_state" id="user_state" required aria-describedby="user_state_error">
<!-- Options loaded by JS -->
<option value="">Select State/Province</option>
<option value="Other" <?php selected($data['user_state'] ?? '', 'Other'); ?>>Other</option>
<?php
// Pre-populate selected state if available from transient
$selected_state = $data['user_state'] ?? '';
if (!empty($selected_state) && $selected_state !== 'Other') {
// Determine if it's a US state or CA province based on country or value itself
$is_us_state = array_key_exists($selected_state, $this->get_us_states());
$is_ca_province = array_key_exists($selected_state, $this->get_canadian_provinces());
if ($is_us_state || $is_ca_province) {
echo '<option value="' . esc_attr($selected_state) . '" selected>' . esc_html($selected_state) . '</option>';
}
}
?>
</select>
<input type="text" name="user_state_other" id="user_state_other"
value="<?php echo esc_attr($data['user_state_other'] ?? ''); ?>"
style="<?php echo (($data['user_state'] ?? '') === 'Other' && ($data['user_country'] ?? '') !== 'United States' && ($data['user_country'] ?? '') !== 'Canada') ? '' : 'display:none;'; ?> margin-top: 0.5rem;"
placeholder="Enter your state/province"
aria-describedby="user_state_other_error">
<?php if (isset($errors['user_state'])) echo '<p class="error-message" id="user_state_error">' . esc_html($errors['user_state']) . '</p>'; ?>
<?php if (isset($errors['user_state_other'])) echo '<p class="error-message" id="user_state_other_error">' . esc_html($errors['user_state_other']) . '</p>'; ?>
</div>
<div>
<label for="user_city"><strong>City *</strong></label>
<input type="text" name="user_city" id="user_city" value="<?php echo esc_attr($data['user_city'] ?? ''); ?>" required aria-describedby="user_city_error">
<?php if (isset($errors['user_city'])) echo '<p class="error-message" id="user_city_error">' . esc_html($errors['user_city']) . '</p>'; ?>
</div>
</div>
<div class="form-row">
<label for="user_zip"><strong>Zip/Postal Code *</strong></label>
<input type="text" name="user_zip" id="user_zip" value="<?php echo esc_attr($data['user_zip'] ?? ''); ?>" required aria-describedby="user_zip_error">
<?php if (isset($errors['user_zip'])) echo '<p class="error-message" id="user_zip_error">' . esc_html($errors['user_zip']) . '</p>'; ?>
</div>
<div class="form-row form-row-half">
<div>
<label for="venue_phone">Venue Phone</label>
<input type="tel" name="venue_phone" id="venue_phone" value="<?php echo esc_attr($data['venue_phone'] ?? $data['business_phone'] ?? ''); ?>" aria-describedby="venue_phone_error">
<?php if (isset($errors['venue_phone'])) echo '<p class="error-message" id="venue_phone_error">' . esc_html($errors['venue_phone']) . '</p>'; ?>
</div>
<div>
<label for="venue_website">Venue Website</label>
<input type="url" name="venue_website" id="venue_website" value="<?php echo esc_attr($data['venue_website'] ?? $data['business_website'] ?? ''); ?>" aria-describedby="venue_website_error">
<?php if (isset($errors['venue_website'])) echo '<p class="error-message" id="venue_website_error">' . esc_html($errors['venue_website']) . '</p>'; ?>
</div>
</div>
</div><!-- end venue-details -->
</div>
<div class="form-submit">
<input type="submit" name="hvac_register" value="Register">
@ -617,6 +688,36 @@ class HVAC_Registration {
}
}
/**
* Handle organization logo upload
*
* @param int $organizer_id The ID of the organizer post to attach the image to.
* @param array $file_data The $_FILES array entry for the uploaded image.
* @return int|false Attachment ID on success, false on failure.
*/
private function handle_org_logo_upload($organizer_id, $file_data) {
if (!$organizer_id || empty($file_data) || !isset($file_data['tmp_name']) || $file_data['error'] !== UPLOAD_ERR_OK) {
return false;
}
require_once(ABSPATH . 'wp-admin/includes/image.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
// Set up the file for upload - need to temporarily set $_FILES for media_handle_upload
$_FILES['org_logo_temp'] = $file_data;
$attachment_id = media_handle_upload('org_logo_temp', $organizer_id);
unset($_FILES['org_logo_temp']);
if (is_wp_error($attachment_id)) {
return false;
} else {
// Set as featured image for the organizer
set_post_thumbnail($organizer_id, $attachment_id);
return $attachment_id;
}
}
/**
* Validate registration form data
*
@ -636,26 +737,49 @@ class HVAC_Registration {
'last_name' => 'Last Name',
'display_name' => 'Display Name',
'description' => 'Biographical Info',
'business_name' => 'Business Name',
'business_phone' => 'Business Phone',
'business_email' => 'Business Email',
'business_description' => 'Business Description',
'user_country' => 'Country',
'user_state' => 'State/Province',
'user_city' => 'City',
'user_zip' => 'Zip/Postal Code',
'business_name' => 'Organization Name',
'business_phone' => 'Organization Phone',
'business_email' => 'Organization Email',
'business_description' => 'Organization Description',
'org_logo' => 'Organization Logo',
'create_venue' => 'Create Training Venue Profile selection',
'business_type' => 'Business Type',
'application_details' => 'Application Details',
];
foreach ($required_fields as $field => $label) {
// Special handling for file upload
if ($field === 'org_logo') {
if (!isset($_FILES['org_logo']) || $_FILES['org_logo']['error'] === UPLOAD_ERR_NO_FILE) {
$errors[$field] = $label . ' is required.';
}
continue;
}
// Use trim to catch spaces-only input
if (empty($data[$field]) || trim($data[$field]) === '') {
$errors[$field] = $label . ' is required.';
}
}
// Conditional venue fields validation
if (isset($data['create_venue']) && $data['create_venue'] === 'Yes') {
$venue_required_fields = [
'venue_name' => 'Venue Name',
'venue_address' => 'Street Address',
'user_country' => 'Country',
'user_state' => 'State/Province',
'user_city' => 'City',
'user_zip' => 'Zip/Postal Code',
];
foreach ($venue_required_fields as $field => $label) {
if (empty($data[$field]) || trim($data[$field]) === '') {
$errors[$field] = $label . ' is required for venue creation.';
}
}
}
// Required checkbox groups
$required_checkboxes = [
'training_audience' => 'Training Audience',
@ -718,7 +842,7 @@ class HVAC_Registration {
}
// State/Province 'Other' validation
if (!empty($data['user_country'])) {
if (!empty($data['user_country']) && isset($data['create_venue']) && $data['create_venue'] === 'Yes') {
if ($data['user_country'] !== 'United States' && $data['user_country'] !== 'Canada') {
// If country is not US/CA, state *must* be 'Other'
if (empty($data['user_state']) || $data['user_state'] !== 'Other') {
@ -752,9 +876,10 @@ class HVAC_Registration {
*
* @param array $data Sanitized form data.
* @param array|null $profile_image_data The $_FILES entry for the profile image, if provided.
* @param array|null $org_logo_data The $_FILES entry for the organization logo, if provided.
* @return int|WP_Error User ID on success, WP_Error on failure.
*/
private function create_trainer_account($data, $profile_image_data = null) {
private function create_trainer_account($data, $profile_image_data = null, $org_logo_data = null) {
// Assume data is already somewhat validated by validate_registration
// Perform final sanitization here before insertion
$user_email = sanitize_email($data['user_email']);
@ -812,11 +937,9 @@ class HVAC_Registration {
'business_email' => sanitize_email($data['business_email']),
'business_website' => !empty($data['business_website']) ? esc_url_raw($data['business_website']) : '',
'business_description' => wp_kses_post($data['business_description']),
'user_country' => sanitize_text_field($data['user_country']),
// Use the 'Other' field value if state was 'Other', otherwise use the selected state
'user_state' => ($data['user_state'] === 'Other' && isset($data['user_state_other'])) ? sanitize_text_field($data['user_state_other']) : sanitize_text_field($data['user_state']),
'user_city' => sanitize_text_field($data['user_city']),
'user_zip' => sanitize_text_field($data['user_zip']),
'org_headquarters_city' => !empty($data['org_headquarters_city']) ? sanitize_text_field($data['org_headquarters_city']) : '',
'org_headquarters_state' => !empty($data['org_headquarters_state']) ? sanitize_text_field($data['org_headquarters_state']) : '',
'org_headquarters_country' => !empty($data['org_headquarters_country']) ? sanitize_text_field($data['org_headquarters_country']) : '',
'create_venue' => sanitize_text_field($data['create_venue']), // Should be 'Yes' or 'No'
'business_type' => sanitize_text_field($data['business_type']),
'training_audience' => (!empty($data['training_audience']) && is_array($data['training_audience'])) ? array_map('sanitize_text_field', $data['training_audience']) : [],
@ -828,6 +951,19 @@ class HVAC_Registration {
'account_status' => 'pending' // Set initial status
];
// If venue creation is requested, store venue-specific data
if (isset($data['create_venue']) && $data['create_venue'] === 'Yes') {
$meta_fields['venue_name'] = !empty($data['venue_name']) ? sanitize_text_field($data['venue_name']) : '';
$meta_fields['venue_address'] = !empty($data['venue_address']) ? sanitize_text_field($data['venue_address']) : '';
$meta_fields['user_country'] = sanitize_text_field($data['user_country']);
// Use the 'Other' field value if state was 'Other', otherwise use the selected state
$meta_fields['user_state'] = ($data['user_state'] === 'Other' && isset($data['user_state_other'])) ? sanitize_text_field($data['user_state_other']) : sanitize_text_field($data['user_state']);
$meta_fields['user_city'] = sanitize_text_field($data['user_city']);
$meta_fields['user_zip'] = sanitize_text_field($data['user_zip']);
$meta_fields['venue_phone'] = !empty($data['venue_phone']) ? sanitize_text_field($data['venue_phone']) : '';
$meta_fields['venue_website'] = !empty($data['venue_website']) ? esc_url_raw($data['venue_website']) : '';
}
foreach ($meta_fields as $key => $value) {
update_user_meta($user_id, $key, $value);
}
@ -842,7 +978,7 @@ class HVAC_Registration {
// --- Create Organizer Profile ---
$organizer_id = $this->create_organizer_profile($user_id, $meta_fields); // Pass sanitized meta fields
$organizer_id = $this->create_organizer_profile($user_id, $meta_fields, $org_logo_data); // Pass sanitized meta fields
if ($organizer_id) {
update_user_meta($user_id, 'hvac_organizer_id', $organizer_id);
@ -878,9 +1014,10 @@ class HVAC_Registration {
*
* @param int $user_id The user ID.
* @param array $meta_data Array of sanitized user meta data.
* @param array|null $org_logo_data The $_FILES entry for the organization logo, if provided.
* @return int|false Organizer Post ID on success, false on failure.
*/
private function create_organizer_profile($user_id, $meta_data) {
private function create_organizer_profile($user_id, $meta_data, $org_logo_data = null) {
if (!class_exists('Tribe__Events__Main') || !function_exists('tribe_create_organizer')) {
return false;
@ -918,6 +1055,33 @@ class HVAC_Registration {
return false;
}
// Store custom fields as post meta
if ($organizer_id) {
// Store headquarters location
if (!empty($meta_data['org_headquarters_city'])) {
update_post_meta($organizer_id, '_hvac_org_headquarters_city', $meta_data['org_headquarters_city']);
}
if (!empty($meta_data['org_headquarters_state'])) {
update_post_meta($organizer_id, '_hvac_org_headquarters_state', $meta_data['org_headquarters_state']);
}
if (!empty($meta_data['org_headquarters_country'])) {
update_post_meta($organizer_id, '_hvac_org_headquarters_country', $meta_data['org_headquarters_country']);
}
// Store training capabilities
update_post_meta($organizer_id, '_hvac_business_type', $meta_data['business_type']);
update_post_meta($organizer_id, '_hvac_training_audience', $meta_data['training_audience']);
update_post_meta($organizer_id, '_hvac_training_formats', $meta_data['training_formats']);
update_post_meta($organizer_id, '_hvac_training_locations', $meta_data['training_locations']);
update_post_meta($organizer_id, '_hvac_training_resources', $meta_data['training_resources']);
update_post_meta($organizer_id, '_hvac_annual_revenue_target', $meta_data['annual_revenue_target']);
// Handle logo upload
if ($org_logo_data) {
$this->handle_org_logo_upload($organizer_id, $org_logo_data);
}
}
return (int) $organizer_id;
}
@ -937,17 +1101,20 @@ class HVAC_Registration {
// Use the already processed state/province from meta
$state_province = $meta_data['user_state'];
// Determine venue name
$venue_name = !empty($meta_data['venue_name']) ? $meta_data['venue_name'] : $meta_data['business_name'] . ' of ' . $meta_data['user_city'];
$venue_data = array(
'Venue' => $meta_data['business_name'] . ' Training Venue', // Venue name from sanitized meta
'Venue' => $venue_name,
'Country' => $meta_data['user_country'],
'Address' => '', // TEC doesn't have a single address line, use City/State/Zip
'Address' => $meta_data['venue_address'], // Now we have a specific address field
'City' => $meta_data['user_city'],
'StateProvince' => $state_province,
'State' => $state_province, // Also set State field
'Province' => $state_province, // Also set Province field
'Zip' => $meta_data['user_zip'],
'Phone' => $meta_data['business_phone'],
'Website' => $meta_data['business_website'],
'Phone' => !empty($meta_data['venue_phone']) ? $meta_data['venue_phone'] : $meta_data['business_phone'],
'Website' => !empty($meta_data['venue_website']) ? $meta_data['venue_website'] : $meta_data['business_website'],
'post_status' => 'publish', // Publish venue immediately
'post_author' => $user_id // Associate with the new user
);
@ -1007,7 +1174,7 @@ class HVAC_Registration {
$first_name = $user_info->first_name ?: sanitize_text_field($data['first_name']);
$last_name = $user_info->last_name ?: sanitize_text_field($data['last_name']);
$message .= "Name: " . $first_name . " " . $last_name . "\n";
$message .= "Business Name: " . sanitize_text_field($data['business_name']) . "\n"; // Use raw data as meta might not be fully updated yet? Safer to use raw.
$message .= "Organization Name: " . sanitize_text_field($data['business_name']) . "\n"; // Use raw data as meta might not be fully updated yet? Safer to use raw.
$message .= "Application Details:\n" . wp_kses_post($data['application_details']) . "\n\n"; // Use wp_kses_post for safety
// Add link to user profile in admin

View file

@ -0,0 +1,537 @@
<?php
/**
* HVAC Trainer Profile Manager
*
* @package HVAC_Community_Events
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* HVAC_Trainer_Profile_Manager class
*/
class HVAC_Trainer_Profile_Manager {
/**
* Constructor
*/
public function __construct() {
// Register shortcodes
add_shortcode('hvac_trainer_profile_view', array($this, 'render_profile_view'));
add_shortcode('hvac_trainer_profile_edit', array($this, 'render_profile_edit'));
// Enqueue scripts
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
// Handle AJAX requests
add_action('wp_ajax_hvac_update_profile', array($this, 'ajax_update_profile'));
add_action('wp_ajax_hvac_upload_profile_photo', array($this, 'ajax_upload_profile_photo'));
}
/**
* Enqueue scripts and styles
*/
public function enqueue_scripts() {
if (is_page('trainer/profile') || is_page('trainer/profile/edit')) {
wp_enqueue_style(
'hvac-trainer-profile-style',
HVAC_PLUGIN_URL . 'assets/css/hvac-trainer-profile.css',
array(),
HVAC_PLUGIN_VERSION
);
wp_enqueue_script(
'hvac-trainer-profile-js',
HVAC_PLUGIN_URL . 'assets/js/hvac-trainer-profile.js',
array('jquery'),
HVAC_PLUGIN_VERSION,
true
);
wp_localize_script('hvac-trainer-profile-js', 'hvacProfile', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('hvac_profile_nonce')
));
// Enqueue media uploader for profile photo
if (is_page('trainer/profile/edit')) {
wp_enqueue_media();
}
}
}
/**
* Render profile view
*/
public function render_profile_view() {
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
return '<p>You must be logged in as a trainer to view this page.</p>';
}
$user_id = get_current_user_id();
$user = get_userdata($user_id);
// Get user meta
$phone = get_user_meta($user_id, 'user_phone', true);
$city = get_user_meta($user_id, 'user_city', true);
$state = get_user_meta($user_id, 'user_state', true);
$country = get_user_meta($user_id, 'user_country', true);
$linkedin = get_user_meta($user_id, 'user_linkedin', true);
$certifications = get_user_meta($user_id, 'trainer_certifications', true);
$years_experience = get_user_meta($user_id, 'years_experience', true);
$profile_photo_id = get_user_meta($user_id, 'profile_photo_id', true);
// Get organization info
$organizer_id = get_user_meta($user_id, 'organizer_id', true);
$organization = null;
if ($organizer_id) {
$organization = get_post($organizer_id);
}
ob_start();
?>
<div class="hvac-trainer-profile-view">
<div class="hvac-page-header">
<h1>Trainer Profile</h1>
<a href="/trainer/profile/edit/" class="hvac-button hvac-button-primary">Edit Profile</a>
</div>
<div class="hvac-breadcrumb">
<a href="/trainer/dashboard/">Trainer</a> &gt; <a href="/trainer/profile/">Profile</a> &gt; View
</div>
<div class="hvac-profile-content">
<div class="hvac-profile-sidebar">
<div class="hvac-profile-photo">
<?php if ($profile_photo_id): ?>
<?php echo wp_get_attachment_image($profile_photo_id, 'medium', false, array('alt' => $user->display_name)); ?>
<?php else: ?>
<div class="hvac-profile-photo-placeholder">
<span><?php echo esc_html(substr($user->first_name, 0, 1) . substr($user->last_name, 0, 1)); ?></span>
</div>
<?php endif; ?>
</div>
<div class="hvac-profile-stats">
<div class="hvac-stat-item">
<span class="hvac-stat-value"><?php echo $this->get_trainer_event_count($user_id); ?></span>
<span class="hvac-stat-label">Events Created</span>
</div>
<div class="hvac-stat-item">
<span class="hvac-stat-value"><?php echo $this->get_trainer_student_count($user_id); ?></span>
<span class="hvac-stat-label">Students Trained</span>
</div>
<?php if ($years_experience): ?>
<div class="hvac-stat-item">
<span class="hvac-stat-value"><?php echo esc_html($years_experience); ?></span>
<span class="hvac-stat-label">Years Experience</span>
</div>
<?php endif; ?>
</div>
</div>
<div class="hvac-profile-main">
<div class="hvac-profile-section">
<h2>Personal Information</h2>
<div class="hvac-profile-details">
<div class="hvac-detail-row">
<span class="hvac-detail-label">Name:</span>
<span class="hvac-detail-value"><?php echo esc_html($user->first_name . ' ' . $user->last_name); ?></span>
</div>
<div class="hvac-detail-row">
<span class="hvac-detail-label">Email:</span>
<span class="hvac-detail-value"><?php echo esc_html($user->user_email); ?></span>
</div>
<?php if ($phone): ?>
<div class="hvac-detail-row">
<span class="hvac-detail-label">Phone:</span>
<span class="hvac-detail-value"><?php echo esc_html($phone); ?></span>
</div>
<?php endif; ?>
<div class="hvac-detail-row">
<span class="hvac-detail-label">Location:</span>
<span class="hvac-detail-value">
<?php
$location_parts = array_filter(array($city, $state, $country));
echo esc_html(implode(', ', $location_parts));
?>
</span>
</div>
<?php if ($linkedin): ?>
<div class="hvac-detail-row">
<span class="hvac-detail-label">LinkedIn:</span>
<span class="hvac-detail-value">
<a href="<?php echo esc_url($linkedin); ?>" target="_blank">View Profile</a>
</span>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!empty($user->description)): ?>
<div class="hvac-profile-section">
<h2>About</h2>
<div class="hvac-profile-bio">
<?php echo wp_kses_post(wpautop($user->description)); ?>
</div>
</div>
<?php endif; ?>
<?php if ($organization): ?>
<div class="hvac-profile-section">
<h2>Training Organization</h2>
<div class="hvac-profile-details">
<div class="hvac-detail-row">
<span class="hvac-detail-label">Organization:</span>
<span class="hvac-detail-value"><?php echo esc_html($organization->post_title); ?></span>
</div>
<?php
$org_city = get_post_meta($organizer_id, '_hvac_headquarters_city', true);
$org_state = get_post_meta($organizer_id, '_hvac_headquarters_state', true);
$org_country = get_post_meta($organizer_id, '_hvac_headquarters_country', true);
if ($org_city || $org_state || $org_country):
?>
<div class="hvac-detail-row">
<span class="hvac-detail-label">Headquarters:</span>
<span class="hvac-detail-value">
<?php
$hq_parts = array_filter(array($org_city, $org_state, $org_country));
echo esc_html(implode(', ', $hq_parts));
?>
</span>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($certifications): ?>
<div class="hvac-profile-section">
<h2>Certifications</h2>
<div class="hvac-certifications-list">
<?php
$cert_array = is_array($certifications) ? $certifications : explode("\n", $certifications);
foreach ($cert_array as $cert):
if (trim($cert)):
?>
<div class="hvac-certification-item">
<i class="dashicons dashicons-awards"></i>
<?php echo esc_html(trim($cert)); ?>
</div>
<?php
endif;
endforeach;
?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* Render profile edit form
*/
public function render_profile_edit() {
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
return '<p>You must be logged in as a trainer to view this page.</p>';
}
$user_id = get_current_user_id();
$user = get_userdata($user_id);
// Get user meta
$phone = get_user_meta($user_id, 'user_phone', true);
$city = get_user_meta($user_id, 'user_city', true);
$state = get_user_meta($user_id, 'user_state', true);
$country = get_user_meta($user_id, 'user_country', true);
$linkedin = get_user_meta($user_id, 'user_linkedin', true);
$website = $user->user_url;
$certifications = get_user_meta($user_id, 'trainer_certifications', true);
$years_experience = get_user_meta($user_id, 'years_experience', true);
$profile_photo_id = get_user_meta($user_id, 'profile_photo_id', true);
ob_start();
?>
<div class="hvac-trainer-profile-edit">
<div class="hvac-page-header">
<h1>Edit Profile</h1>
</div>
<div class="hvac-breadcrumb">
<a href="/trainer/dashboard/">Trainer</a> &gt; <a href="/trainer/profile/">Profile</a> &gt; Edit
</div>
<form id="hvac-profile-form" class="hvac-form">
<?php wp_nonce_field('hvac_profile_edit', 'hvac_profile_nonce'); ?>
<div class="hvac-form-section">
<h3>Profile Photo</h3>
<div class="hvac-profile-photo-upload">
<div class="hvac-current-photo">
<?php if ($profile_photo_id): ?>
<?php echo wp_get_attachment_image($profile_photo_id, 'thumbnail'); ?>
<?php else: ?>
<div class="hvac-photo-placeholder">No photo uploaded</div>
<?php endif; ?>
</div>
<div class="hvac-photo-actions">
<button type="button" id="hvac-upload-photo" class="hvac-button hvac-button-secondary">
<?php echo $profile_photo_id ? 'Change Photo' : 'Upload Photo'; ?>
</button>
<?php if ($profile_photo_id): ?>
<button type="button" id="hvac-remove-photo" class="hvac-button hvac-button-danger-outline">
Remove Photo
</button>
<?php endif; ?>
<input type="hidden" id="profile_photo_id" name="profile_photo_id" value="<?php echo $profile_photo_id; ?>" />
</div>
</div>
</div>
<div class="hvac-form-section">
<h3>Personal Information</h3>
<div class="hvac-form-row hvac-form-row-half">
<div>
<label for="first_name">First Name *</label>
<input type="text" id="first_name" name="first_name" required
value="<?php echo esc_attr($user->first_name); ?>" />
</div>
<div>
<label for="last_name">Last Name *</label>
<input type="text" id="last_name" name="last_name" required
value="<?php echo esc_attr($user->last_name); ?>" />
</div>
</div>
<div class="hvac-form-row">
<label for="display_name">Display Name *</label>
<input type="text" id="display_name" name="display_name" required
value="<?php echo esc_attr($user->display_name); ?>" />
</div>
<div class="hvac-form-row">
<label for="email">Email Address *</label>
<input type="email" id="email" name="email" required
value="<?php echo esc_attr($user->user_email); ?>" />
</div>
<div class="hvac-form-row">
<label for="phone">Phone Number</label>
<input type="tel" id="phone" name="phone"
value="<?php echo esc_attr($phone); ?>" />
</div>
<div class="hvac-form-row">
<label for="description">Bio / About</label>
<textarea id="description" name="description" rows="5"><?php echo esc_textarea($user->description); ?></textarea>
</div>
</div>
<div class="hvac-form-section">
<h3>Location</h3>
<div class="hvac-form-row">
<label for="city">City</label>
<input type="text" id="city" name="city"
value="<?php echo esc_attr($city); ?>" />
</div>
<div class="hvac-form-row hvac-form-row-half">
<div>
<label for="state">State/Province</label>
<input type="text" id="state" name="state"
value="<?php echo esc_attr($state); ?>" />
</div>
<div>
<label for="country">Country</label>
<select id="country" name="country">
<option value="">Select Country</option>
<?php
$countries = array(
'United States' => 'United States',
'Canada' => 'Canada',
'United Kingdom' => 'United Kingdom',
'Australia' => 'Australia'
);
foreach ($countries as $code => $name) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr($code),
selected($country, $code, false),
esc_html($name)
);
}
?>
</select>
</div>
</div>
</div>
<div class="hvac-form-section">
<h3>Professional Information</h3>
<div class="hvac-form-row">
<label for="years_experience">Years of Experience</label>
<input type="number" id="years_experience" name="years_experience" min="0" max="50"
value="<?php echo esc_attr($years_experience); ?>" />
</div>
<div class="hvac-form-row">
<label for="certifications">Certifications (one per line)</label>
<textarea id="certifications" name="certifications" rows="5"><?php echo esc_textarea($certifications); ?></textarea>
</div>
<div class="hvac-form-row">
<label for="website">Website</label>
<input type="url" id="website" name="website"
value="<?php echo esc_attr($website); ?>" />
</div>
<div class="hvac-form-row">
<label for="linkedin">LinkedIn Profile</label>
<input type="url" id="linkedin" name="linkedin"
value="<?php echo esc_attr($linkedin); ?>" />
</div>
</div>
<div class="hvac-form-actions">
<button type="submit" class="hvac-button hvac-button-primary">Save Changes</button>
<a href="/trainer/profile/" class="hvac-button hvac-button-secondary">Cancel</a>
</div>
</form>
</div>
<?php
return ob_get_clean();
}
/**
* AJAX handler for updating profile
*/
public function ajax_update_profile() {
check_ajax_referer('hvac_profile_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
$user_id = get_current_user_id();
// Update user data
$user_data = array(
'ID' => $user_id,
'first_name' => sanitize_text_field($_POST['first_name']),
'last_name' => sanitize_text_field($_POST['last_name']),
'display_name' => sanitize_text_field($_POST['display_name']),
'user_email' => sanitize_email($_POST['email']),
'user_url' => esc_url_raw($_POST['website']),
'description' => wp_kses_post($_POST['description'])
);
$result = wp_update_user($user_data);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
// Update user meta
update_user_meta($user_id, 'user_phone', sanitize_text_field($_POST['phone']));
update_user_meta($user_id, 'user_city', sanitize_text_field($_POST['city']));
update_user_meta($user_id, 'user_state', sanitize_text_field($_POST['state']));
update_user_meta($user_id, 'user_country', sanitize_text_field($_POST['country']));
update_user_meta($user_id, 'user_linkedin', esc_url_raw($_POST['linkedin']));
update_user_meta($user_id, 'years_experience', intval($_POST['years_experience']));
update_user_meta($user_id, 'trainer_certifications', sanitize_textarea_field($_POST['certifications']));
// Update profile photo if changed
if (isset($_POST['profile_photo_id'])) {
update_user_meta($user_id, 'profile_photo_id', intval($_POST['profile_photo_id']));
}
wp_send_json_success('Profile updated successfully.');
}
/**
* AJAX handler for uploading profile photo
*/
public function ajax_upload_profile_photo() {
check_ajax_referer('hvac_profile_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
if (!isset($_FILES['profile_photo'])) {
wp_send_json_error('No file uploaded');
}
require_once(ABSPATH . 'wp-admin/includes/image.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
$attachment_id = media_handle_upload('profile_photo', 0);
if (is_wp_error($attachment_id)) {
wp_send_json_error($attachment_id->get_error_message());
}
// Update user meta
update_user_meta(get_current_user_id(), 'profile_photo_id', $attachment_id);
wp_send_json_success(array(
'attachment_id' => $attachment_id,
'url' => wp_get_attachment_image_url($attachment_id, 'thumbnail')
));
}
/**
* Get trainer event count
*/
private function get_trainer_event_count($user_id) {
$post_type = class_exists('Tribe__Events__Main') ? Tribe__Events__Main::POSTTYPE : 'tribe_events';
$count = count_user_posts($user_id, $post_type);
return $count;
}
/**
* Get trainer student count
*/
private function get_trainer_student_count($user_id) {
global $wpdb;
// Get all events by this trainer
$post_type = class_exists('Tribe__Events__Main') ? Tribe__Events__Main::POSTTYPE : 'tribe_events';
$events = get_posts(array(
'post_type' => $post_type,
'author' => $user_id,
'posts_per_page' => -1,
'fields' => 'ids'
));
if (empty($events)) {
return 0;
}
// Count attendees across all events
$attendee_count = 0;
foreach ($events as $event_id) {
$attendees = get_post_meta($event_id, '_tribe_tickets_attendees', true);
if (is_array($attendees)) {
$attendee_count += count($attendees);
}
}
return $attendee_count;
}
}

View file

@ -0,0 +1,536 @@
<?php
/**
* HVAC Venues Management
*
* @package HVAC_Community_Events
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* HVAC_Venues class
*/
class HVAC_Venues {
/**
* Constructor
*/
public function __construct() {
// Register shortcodes
add_shortcode('hvac_trainer_venues_list', array($this, 'render_venues_list'));
add_shortcode('hvac_trainer_venue_manage', array($this, 'render_venue_manage'));
// Enqueue scripts
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
// Handle AJAX requests
add_action('wp_ajax_hvac_save_venue', array($this, 'ajax_save_venue'));
add_action('wp_ajax_hvac_delete_venue', array($this, 'ajax_delete_venue'));
add_action('wp_ajax_hvac_load_venue', array($this, 'ajax_load_venue'));
}
/**
* Enqueue scripts and styles
*/
public function enqueue_scripts() {
if (is_page('trainer/venue/list') || is_page('trainer/venue/manage')) {
wp_enqueue_style(
'hvac-venues-style',
HVAC_PLUGIN_URL . 'assets/css/hvac-venues.css',
array(),
HVAC_PLUGIN_VERSION
);
wp_enqueue_script(
'hvac-venues-js',
HVAC_PLUGIN_URL . 'assets/js/hvac-venues.js',
array('jquery'),
HVAC_PLUGIN_VERSION,
true
);
wp_localize_script('hvac-venues-js', 'hvacVenues', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('hvac_venues_nonce')
));
}
}
/**
* Render venues list
*/
public function render_venues_list() {
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
return '<p>You must be logged in as a trainer to view this page.</p>';
}
ob_start();
?>
<div class="hvac-venues-list">
<div class="hvac-page-header">
<h1>Training Venues</h1>
<a href="/trainer/venue/manage/" class="hvac-button hvac-button-primary">Add New Venue</a>
</div>
<div class="hvac-breadcrumb">
<a href="/trainer/dashboard/">Trainer</a> &gt; <a href="/trainer/venue/list/">Venues</a> &gt; List
</div>
<?php $this->render_venues_table(); ?>
</div>
<?php
return ob_get_clean();
}
/**
* Render venues table
*/
private function render_venues_table() {
global $wpdb;
$current_user_id = get_current_user_id();
// Get pagination parameters
$page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$per_page = 20;
$offset = ($page - 1) * $per_page;
// Build query
$query_args = array(
'post_type' => class_exists('Tribe__Events__Main') ? Tribe__Events__Main::VENUE_POST_TYPE : 'tribe_venue',
'posts_per_page' => $per_page,
'offset' => $offset,
'orderby' => 'title',
'order' => 'ASC',
'post_status' => 'publish'
);
// Filter handling
if (!empty($_GET['search'])) {
$query_args['s'] = sanitize_text_field($_GET['search']);
}
if (!empty($_GET['state'])) {
$query_args['meta_query'] = array(
array(
'key' => '_VenueStateProvince',
'value' => sanitize_text_field($_GET['state']),
'compare' => '='
)
);
}
// Get venues
$venues_query = new WP_Query($query_args);
// Get total count for pagination
$total_venues = $venues_query->found_posts;
$total_pages = ceil($total_venues / $per_page);
?>
<div class="hvac-venues-filters">
<form method="get" class="hvac-filter-form">
<div class="hvac-filter-row">
<div class="hvac-filter-group">
<input type="text" name="search" placeholder="Search venues..."
value="<?php echo esc_attr($_GET['search'] ?? ''); ?>" />
</div>
<div class="hvac-filter-group">
<select name="state">
<option value="">All States</option>
<?php
// Get unique states
$states = $wpdb->get_col("
SELECT DISTINCT meta_value
FROM {$wpdb->postmeta}
WHERE meta_key = '_VenueStateProvince'
AND meta_value != ''
ORDER BY meta_value
");
foreach ($states as $state) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr($state),
selected($_GET['state'] ?? '', $state, false),
esc_html($state)
);
}
?>
</select>
</div>
<div class="hvac-filter-group">
<button type="submit" class="hvac-button">Filter</button>
<a href="/trainer/venue/list/" class="hvac-button hvac-button-secondary">Clear</a>
</div>
</div>
</form>
</div>
<div class="hvac-venues-table-wrapper">
<table class="hvac-venues-table">
<thead>
<tr>
<th>Venue Name</th>
<th>Address</th>
<th>City</th>
<th>State</th>
<th>Phone</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
if ($venues_query->have_posts()) {
while ($venues_query->have_posts()) {
$venues_query->the_post();
$venue_id = get_the_ID();
$is_author = (get_post_field('post_author', $venue_id) == $current_user_id);
// Get venue meta
$address = get_post_meta($venue_id, '_VenueAddress', true);
$city = get_post_meta($venue_id, '_VenueCity', true);
$state = get_post_meta($venue_id, '_VenueStateProvince', true);
$phone = get_post_meta($venue_id, '_VenuePhone', true);
?>
<tr>
<td>
<strong><?php the_title(); ?></strong>
<?php if ($is_author): ?>
<span class="hvac-badge hvac-badge-owner">Your Venue</span>
<?php endif; ?>
</td>
<td><?php echo esc_html($address); ?></td>
<td><?php echo esc_html($city); ?></td>
<td><?php echo esc_html($state); ?></td>
<td><?php echo esc_html($phone); ?></td>
<td>
<?php if ($is_author): ?>
<a href="/trainer/venue/manage/?venue_id=<?php echo $venue_id; ?>"
class="hvac-button hvac-button-small">Edit</a>
<?php else: ?>
<span class="hvac-text-muted">View Only</span>
<?php endif; ?>
</td>
</tr>
<?php
}
} else {
?>
<tr>
<td colspan="6" class="hvac-no-results">No venues found.</td>
</tr>
<?php
}
wp_reset_postdata();
?>
</tbody>
</table>
</div>
<?php if ($total_pages > 1): ?>
<div class="hvac-pagination">
<?php
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'current' => $page,
'total' => $total_pages,
'prev_text' => '&laquo; Previous',
'next_text' => 'Next &raquo;'
));
?>
</div>
<?php endif; ?>
<?php
}
/**
* Render venue manage form
*/
public function render_venue_manage() {
if (!is_user_logged_in() || !current_user_can('hvac_trainer')) {
return '<p>You must be logged in as a trainer to view this page.</p>';
}
$venue_id = isset($_GET['venue_id']) ? intval($_GET['venue_id']) : 0;
$venue = null;
if ($venue_id) {
$venue = get_post($venue_id);
// Check if user can edit this venue
if (!$venue || $venue->post_author != get_current_user_id()) {
return '<p>You do not have permission to edit this venue.</p>';
}
}
ob_start();
?>
<div class="hvac-venue-manage">
<div class="hvac-page-header">
<h1><?php echo $venue ? 'Edit Venue' : 'Create New Venue'; ?></h1>
</div>
<div class="hvac-breadcrumb">
<a href="/trainer/dashboard/">Trainer</a> &gt;
<a href="/trainer/venue/list/">Venues</a> &gt;
<?php echo $venue ? 'Edit' : 'New'; ?>
</div>
<form id="hvac-venue-form" class="hvac-form">
<?php wp_nonce_field('hvac_venue_manage', 'hvac_venue_nonce'); ?>
<input type="hidden" name="venue_id" value="<?php echo $venue_id; ?>" />
<div class="hvac-form-section">
<h3>Venue Information</h3>
<div class="hvac-form-row">
<label for="venue_name">Venue Name *</label>
<input type="text" id="venue_name" name="venue_name" required
value="<?php echo $venue ? esc_attr($venue->post_title) : ''; ?>" />
</div>
<div class="hvac-form-row">
<label for="venue_description">Description</label>
<textarea id="venue_description" name="venue_description" rows="4"><?php
echo $venue ? esc_textarea($venue->post_content) : '';
?></textarea>
</div>
</div>
<div class="hvac-form-section">
<h3>Location Details</h3>
<div class="hvac-form-row">
<label for="venue_address">Street Address *</label>
<input type="text" id="venue_address" name="venue_address" required
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueAddress', true)) : ''; ?>" />
</div>
<div class="hvac-form-row hvac-form-row-half">
<div>
<label for="venue_city">City *</label>
<input type="text" id="venue_city" name="venue_city" required
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueCity', true)) : ''; ?>" />
</div>
<div>
<label for="venue_state">State/Province *</label>
<input type="text" id="venue_state" name="venue_state" required
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueStateProvince', true)) : ''; ?>" />
</div>
</div>
<div class="hvac-form-row hvac-form-row-half">
<div>
<label for="venue_zip">Zip/Postal Code *</label>
<input type="text" id="venue_zip" name="venue_zip" required
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueZip', true)) : ''; ?>" />
</div>
<div>
<label for="venue_country">Country *</label>
<select id="venue_country" name="venue_country" required>
<?php
$current_country = $venue ? get_post_meta($venue_id, '_VenueCountry', true) : 'United States';
$countries = array(
'United States' => 'United States',
'Canada' => 'Canada',
'United Kingdom' => 'United Kingdom',
'Australia' => 'Australia'
);
foreach ($countries as $code => $name) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr($code),
selected($current_country, $code, false),
esc_html($name)
);
}
?>
</select>
</div>
</div>
</div>
<div class="hvac-form-section">
<h3>Contact Information</h3>
<div class="hvac-form-row hvac-form-row-half">
<div>
<label for="venue_phone">Phone</label>
<input type="tel" id="venue_phone" name="venue_phone"
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenuePhone', true)) : ''; ?>" />
</div>
<div>
<label for="venue_website">Website</label>
<input type="url" id="venue_website" name="venue_website"
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueURL', true)) : ''; ?>" />
</div>
</div>
</div>
<div class="hvac-form-actions">
<button type="submit" class="hvac-button hvac-button-primary">
<?php echo $venue ? 'Update Venue' : 'Create Venue'; ?>
</button>
<a href="/trainer/venue/list/" class="hvac-button hvac-button-secondary">Cancel</a>
<?php if ($venue): ?>
<button type="button" id="hvac-delete-venue" class="hvac-button hvac-button-danger"
data-venue-id="<?php echo $venue_id; ?>">Delete Venue</button>
<?php endif; ?>
</div>
</form>
</div>
<?php
return ob_get_clean();
}
/**
* AJAX handler for saving venue
*/
public function ajax_save_venue() {
check_ajax_referer('hvac_venues_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
$venue_id = isset($_POST['venue_id']) ? intval($_POST['venue_id']) : 0;
// If editing, check ownership
if ($venue_id) {
$venue = get_post($venue_id);
if (!$venue || $venue->post_author != get_current_user_id()) {
wp_send_json_error('You do not have permission to edit this venue.');
}
}
// Prepare venue data
$venue_data = array(
'Venue' => sanitize_text_field($_POST['venue_name']),
'Description' => wp_kses_post($_POST['venue_description']),
'Address' => sanitize_text_field($_POST['venue_address']),
'City' => sanitize_text_field($_POST['venue_city']),
'StateProvince' => sanitize_text_field($_POST['venue_state']),
'State' => sanitize_text_field($_POST['venue_state']),
'Province' => sanitize_text_field($_POST['venue_state']),
'Zip' => sanitize_text_field($_POST['venue_zip']),
'Country' => sanitize_text_field($_POST['venue_country']),
'Phone' => sanitize_text_field($_POST['venue_phone']),
'URL' => esc_url_raw($_POST['venue_website']),
'ShowMap' => true,
'ShowMapLink' => true
);
if ($venue_id) {
$venue_data['ID'] = $venue_id;
$result = function_exists('tribe_update_venue') ?
tribe_update_venue($venue_id, $venue_data) :
wp_update_post($venue_data);
} else {
$venue_data['post_status'] = 'publish';
$venue_data['post_author'] = get_current_user_id();
$result = function_exists('tribe_create_venue') ?
tribe_create_venue($venue_data) :
wp_insert_post($venue_data);
}
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
} else {
wp_send_json_success(array(
'message' => $venue_id ? 'Venue updated successfully.' : 'Venue created successfully.',
'venue_id' => $result
));
}
}
/**
* AJAX handler for deleting venue
*/
public function ajax_delete_venue() {
check_ajax_referer('hvac_venues_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
$venue_id = isset($_POST['venue_id']) ? intval($_POST['venue_id']) : 0;
if (!$venue_id) {
wp_send_json_error('Invalid venue ID');
}
$venue = get_post($venue_id);
if (!$venue || $venue->post_author != get_current_user_id()) {
wp_send_json_error('You do not have permission to delete this venue.');
}
// Check if venue is being used by any events
$events_using_venue = get_posts(array(
'post_type' => class_exists('Tribe__Events__Main') ? Tribe__Events__Main::POSTTYPE : 'tribe_events',
'meta_query' => array(
array(
'key' => '_EventVenueID',
'value' => $venue_id,
'compare' => '='
)
),
'posts_per_page' => 1
));
if (!empty($events_using_venue)) {
wp_send_json_error('Cannot delete venue. It is being used by one or more events.');
}
$result = wp_trash_post($venue_id);
if ($result) {
wp_send_json_success('Venue deleted successfully.');
} else {
wp_send_json_error('Failed to delete venue.');
}
}
/**
* AJAX handler for loading venue data
*/
public function ajax_load_venue() {
check_ajax_referer('hvac_venues_nonce', 'nonce');
if (!current_user_can('hvac_trainer')) {
wp_send_json_error('Unauthorized');
}
$venue_id = isset($_GET['venue_id']) ? intval($_GET['venue_id']) : 0;
if (!$venue_id) {
wp_send_json_error('Invalid venue ID');
}
$venue = get_post($venue_id);
if (!$venue) {
wp_send_json_error('Venue not found');
}
$venue_data = array(
'id' => $venue_id,
'name' => $venue->post_title,
'description' => $venue->post_content,
'address' => get_post_meta($venue_id, '_VenueAddress', true),
'city' => get_post_meta($venue_id, '_VenueCity', true),
'state' => get_post_meta($venue_id, '_VenueStateProvince', true),
'zip' => get_post_meta($venue_id, '_VenueZip', true),
'country' => get_post_meta($venue_id, '_VenueCountry', true),
'phone' => get_post_meta($venue_id, '_VenuePhone', true),
'website' => get_post_meta($venue_id, '_VenueURL', true)
);
wp_send_json_success($venue_data);
}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* Template Name: Trainer Organizer Manage
* Description: Template for creating and editing training organizers
*/
get_header();
?>
<div class="hvac-page-wrapper hvac-trainer-organizer-manage-page">
<div class="container">
<?php
// Render the organizer manage shortcode
echo do_shortcode('[hvac_trainer_organizer_manage]');
?>
</div>
</div>
<?php
get_footer();

View file

@ -0,0 +1,20 @@
<?php
/**
* Template Name: Trainer Organizers List
* Description: Template for listing all training organizers
*/
get_header();
?>
<div class="hvac-page-wrapper hvac-trainer-organizers-list-page">
<div class="container">
<?php
// Render the organizers list shortcode
echo do_shortcode('[hvac_trainer_organizers_list]');
?>
</div>
</div>
<?php
get_footer();

View file

@ -0,0 +1,20 @@
<?php
/**
* Template Name: Trainer Profile Edit
* Description: Template for editing trainer profile
*/
get_header();
?>
<div class="hvac-page-wrapper hvac-trainer-profile-edit-page">
<div class="container">
<?php
// Render the profile edit shortcode
echo do_shortcode('[hvac_trainer_profile_edit]');
?>
</div>
</div>
<?php
get_footer();

View file

@ -0,0 +1,20 @@
<?php
/**
* Template Name: Trainer Profile View
* Description: Template for viewing trainer profile
*/
get_header();
?>
<div class="hvac-page-wrapper hvac-trainer-profile-page">
<div class="container">
<?php
// Render the profile view shortcode
echo do_shortcode('[hvac_trainer_profile_view]');
?>
</div>
</div>
<?php
get_footer();

View file

@ -0,0 +1,20 @@
<?php
/**
* Template Name: Trainer Venue Manage
* Description: Template for creating and editing training venues
*/
get_header();
?>
<div class="hvac-page-wrapper hvac-trainer-venue-manage-page">
<div class="container">
<?php
// Render the venue manage shortcode
echo do_shortcode('[hvac_trainer_venue_manage]');
?>
</div>
</div>
<?php
get_footer();

View file

@ -0,0 +1,20 @@
<?php
/**
* Template Name: Trainer Venues List
* Description: Template for listing all training venues
*/
get_header();
?>
<div class="hvac-page-wrapper hvac-trainer-venues-list-page">
<div class="container">
<?php
// Render the venues list shortcode
echo do_shortcode('[hvac_trainer_venues_list]');
?>
</div>
</div>
<?php
get_footer();