Initial commit for Cloudways deployment source (plugin only)
This commit is contained in:
commit
5cae9128b6
18 changed files with 4396 additions and 0 deletions
96
hvac-community-events/assets/css/community-login.css
Normal file
96
hvac-community-events/assets/css/community-login.css
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* HVAC Community Events: Community Login Styles
|
||||
*
|
||||
* Styles for the custom login form page.
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
.hvac-community-login-wrapper {
|
||||
/* Add styles to center the card vertically/horizontally if needed */
|
||||
padding: 40px 0; /* Example padding */
|
||||
}
|
||||
|
||||
.hvac-login-form-card {
|
||||
max-width: 400px; /* Adjust as needed based on design */
|
||||
margin: 0 auto;
|
||||
padding: 30px;
|
||||
background-color: #ffffff; /* White card background */
|
||||
border: 1px solid #e0e0e0; /* Light border */
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1); /* Subtle shadow */
|
||||
border-radius: 4px; /* Rounded corners */
|
||||
}
|
||||
|
||||
/* Style the form generated by wp_login_form */
|
||||
#hvac_community_loginform p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#hvac_community_loginform label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#hvac_community_loginform input[type="text"],
|
||||
#hvac_community_loginform input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box; /* Include padding and border in the element's total width and height */
|
||||
}
|
||||
|
||||
#hvac_community_loginform .login-remember label {
|
||||
font-weight: normal;
|
||||
display: inline-block; /* Align checkbox and label */
|
||||
}
|
||||
#hvac_community_loginform .login-remember input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
#hvac_community_loginform .login-submit #wp-submit {
|
||||
/* Use Astra button styles if possible, or define custom */
|
||||
/* Example using Astra's class structure (might need adjustment) */
|
||||
/* @extend .ast-button; */
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #0073aa; /* Example blue */
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
width: 100%; /* Make button full width */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#hvac_community_loginform .login-submit #wp-submit:hover {
|
||||
background-color: #005a87; /* Darker blue on hover */
|
||||
}
|
||||
|
||||
.hvac-login-links {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hvac-login-links a {
|
||||
color: #0073aa; /* Link color */
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.hvac-login-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Add responsive adjustments if needed */
|
||||
@media (max-width: 544px) { /* Example using Astra mobile breakpoint */
|
||||
.hvac-login-form-card {
|
||||
max-width: 90%;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
196
hvac-community-events/assets/css/hvac-dashboard.css
Normal file
196
hvac-community-events/assets/css/hvac-dashboard.css
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* HVAC Trainer Dashboard Styles
|
||||
*
|
||||
* Styles specific to the template-hvac-dashboard.php template.
|
||||
*/
|
||||
/* General Page Styles */
|
||||
body.page-template-template-hvac-dashboard {
|
||||
background-color: #f0f4f8; /* Light blue background - Adjust color as needed */
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5em; /* Example size */
|
||||
margin-bottom: 1em;
|
||||
color: #333; /* Adjust color as needed */
|
||||
}
|
||||
|
||||
|
||||
/* Header */
|
||||
.hvac-dashboard-header {
|
||||
margin-bottom: 2em;
|
||||
padding-bottom: 1em;
|
||||
border-bottom: 1px solid #eee; /* Consider using theme variable for border color */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap; /* Allow wrapping on smaller screens */
|
||||
}
|
||||
|
||||
.hvac-dashboard-nav a {
|
||||
margin-left: 0.5em; /* Add some space between nav buttons */
|
||||
}
|
||||
|
||||
/* Stats Section */
|
||||
.hvac-dashboard-stats {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
/* Use flexbox for 5 columns */
|
||||
.hvac-stats-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px; /* Adjust gap as needed */
|
||||
margin-left: -10px; /* Counteract column padding */
|
||||
margin-right: -10px; /* Counteract column padding */
|
||||
}
|
||||
|
||||
.hvac-stats-grid > .ast-col {
|
||||
flex: 1 1 calc(20% - 20px); /* 5 columns minus gap */
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 150px; /* Prevent cards from becoming too small */
|
||||
}
|
||||
|
||||
/* Responsive adjustments for stats grid */
|
||||
@media (max-width: 921px) { /* Astra Tablet Breakpoint */
|
||||
.hvac-stats-grid > .ast-col {
|
||||
flex: 1 1 calc(33.333% - 20px); /* 3 columns */
|
||||
}
|
||||
}
|
||||
@media (max-width: 544px) { /* Astra Mobile Breakpoint */
|
||||
.hvac-stats-grid > .ast-col {
|
||||
flex: 1 1 calc(50% - 20px); /* 2 columns */
|
||||
}
|
||||
}
|
||||
|
||||
.hvac-stat-card {
|
||||
border: 1px solid #e0e0e0; /* Lighter border */
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
border-radius: 4px; /* Slight rounding */
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05); /* Subtle shadow */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* TODO: Add styles for icons using ::before or background images */
|
||||
/* Example:
|
||||
.hvac-stat-card::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #ccc; // Placeholder
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
*/
|
||||
|
||||
.hvac-stat-card .stat-value {
|
||||
font-size: 2.2em;
|
||||
font-weight: 600; /* Slightly bolder */
|
||||
color: #0073aa; /* Example blue color - use theme variable if possible */
|
||||
line-height: 1.1;
|
||||
margin-bottom: 0.1em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hvac-stat-card .stat-label {
|
||||
font-size: 0.9em;
|
||||
color: #555; /* Darker grey */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Remove old h3/p/small styles if no longer used */
|
||||
.hvac-stat-card h3,
|
||||
.hvac-stat-card p,
|
||||
.hvac-stat-card small {
|
||||
display: none; /* Hide old elements if PHP was updated */
|
||||
}
|
||||
|
||||
|
||||
/* Event Tabs */
|
||||
.hvac-event-tabs {
|
||||
margin-bottom: 1.5em;
|
||||
border-bottom: 1px solid #ccc; /* Separator line */
|
||||
}
|
||||
|
||||
.hvac-tabs-nav {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hvac-tabs-nav li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hvac-tab-link {
|
||||
display: block;
|
||||
padding: 0.8em 1.2em;
|
||||
text-decoration: none;
|
||||
color: #555;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
margin-bottom: -1px; /* Overlap border-bottom */
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.hvac-tab-link:hover {
|
||||
color: #0073aa; /* Theme primary color */
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.hvac-tab-link.active {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
background-color: #fff; /* Match page background if needed */
|
||||
border-color: #ccc #ccc #fff; /* Border to create tab effect */
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
/* Hide old button filters if PHP was updated */
|
||||
.hvac-event-filters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Events Table */
|
||||
.hvac-events-table-wrapper {
|
||||
overflow-x: auto; /* Add horizontal scroll for smaller screens */
|
||||
}
|
||||
|
||||
/* Ensure table uses standard WP/Theme styling and add custom class */
|
||||
.hvac-events-table {
|
||||
margin-top: 1em;
|
||||
background-color: #fff; /* White background for the table card */
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
padding: 15px; /* Add padding inside the card */
|
||||
}
|
||||
|
||||
/* Ensure striped rows are visible */
|
||||
.hvac-events-table.striped tbody tr:nth-child(odd) {
|
||||
background-color: #f9f9f9; /* Adjust stripe color if needed */
|
||||
}
|
||||
|
||||
.hvac-events-table .column-actions .ast-button {
|
||||
margin-right: 0.5em;
|
||||
padding: 0.3em 0.8em; /* Make buttons smaller */
|
||||
font-size: 0.85em;
|
||||
/* TODO: Add icon styles using ::before */
|
||||
}
|
||||
|
||||
.hvac-events-table .column-actions .ast-button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
71
hvac-community-events/assets/css/hvac-event-summary.css
Normal file
71
hvac-community-events/assets/css/hvac-event-summary.css
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Styles for the HVAC Community Events Single Event Summary Template
|
||||
*/
|
||||
|
||||
.hvac-event-summary-details,
|
||||
.hvac-event-summary-transactions {
|
||||
margin-bottom: 2em; /* Add spacing between sections */
|
||||
padding: 1.5em;
|
||||
border: 1px solid #e2e2e2; /* Basic border like theme cards */
|
||||
border-radius: 4px; /* Slight rounding */
|
||||
background-color: #fff; /* White background */
|
||||
}
|
||||
|
||||
.hvac-event-summary-details h2,
|
||||
.hvac-event-summary-transactions h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.5em; /* Adjust as needed */
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.hvac-event-summary-details h3 {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.hvac-event-summary-details p,
|
||||
.hvac-event-summary-transactions p {
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
|
||||
.hvac-event-summary-details .event-description {
|
||||
margin-top: 1em;
|
||||
padding-top: 1em;
|
||||
border-top: 1px dashed #eee;
|
||||
}
|
||||
|
||||
/* Basic Table Styling - Inherit Astra's base styles where possible */
|
||||
.hvac-transactions-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.hvac-transactions-table th,
|
||||
.hvac-transactions-table td {
|
||||
text-align: left;
|
||||
padding: 0.8em 1em;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.hvac-transactions-table th {
|
||||
background-color: #f8f8f8; /* Light background for header */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hvac-transactions-table tbody tr:nth-child(odd) {
|
||||
background-color: #fdfdfd; /* Subtle striping */
|
||||
}
|
||||
|
||||
.hvac-transactions-table tbody tr:hover {
|
||||
background-color: #f1f1f1; /* Hover effect */
|
||||
}
|
||||
|
||||
/* Ensure edit button has some margin */
|
||||
.entry-header .button.astra-button {
|
||||
margin-left: 1em;
|
||||
vertical-align: middle; /* Align with title */
|
||||
}
|
||||
106
hvac-community-events/assets/css/hvac-registration.css
Normal file
106
hvac-community-events/assets/css/hvac-registration.css
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/* HVAC Trainer Registration Form Styles */
|
||||
.hvac-registration-form {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.hvac-registration-form h2 {
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hvac-registration-form .form-section {
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.hvac-registration-form .form-section h3 {
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hvac-registration-form .form-row {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hvac-registration-form label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.hvac-registration-form input[type="text"],
|
||||
.hvac-registration-form input[type="email"],
|
||||
.hvac-registration-form input[type="password"],
|
||||
.hvac-registration-form input[type="url"],
|
||||
.hvac-registration-form textarea,
|
||||
.hvac-registration-form select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hvac-registration-form .form-submit {
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hvac-registration-form input[type="submit"] {
|
||||
background-color: #0274be;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.hvac-registration-form input[type="submit"]:hover {
|
||||
background-color: #0261a0;
|
||||
}
|
||||
|
||||
/* Checkbox/Radio Group Styles */
|
||||
.hvac-registration-form .checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.hvac-registration-form .checkbox-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hvac-registration-form .checkbox-group input[type="checkbox"],
|
||||
.hvac-registration-form .checkbox-group input[type="radio"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Error message styling */
|
||||
.hvac-registration-form .hvac-errors {
|
||||
background-color: #fff0f0;
|
||||
border: 1px solid #ffcccc;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hvac-registration-form .hvac-errors .error {
|
||||
color: #d63638;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
78
hvac-community-events/assets/js/hvac-registration.js
Normal file
78
hvac-community-events/assets/js/hvac-registration.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
jQuery(document).ready(function($) {
|
||||
const $countrySelect = $('#user_country');
|
||||
const $stateSelect = $('#user_state');
|
||||
const $stateOtherInput = $('#user_state_other');
|
||||
|
||||
// Function to populate states/provinces
|
||||
function loadStates(country) {
|
||||
console.log(`[DEBUG] loadStates called for country: ${country}`); // More specific log
|
||||
$stateSelect.find('option').not('[value=""],[value="Other"]').remove(); // Clear existing options except defaults
|
||||
console.log(`[DEBUG] Cleared existing state options.`);
|
||||
|
||||
let options = {};
|
||||
let dataSource = 'none';
|
||||
// <<< CORRECTED: Use hvac_reg_vars instead of hvacRegistrationData
|
||||
if (country === 'United States' && typeof hvac_reg_vars !== 'undefined' && hvac_reg_vars.us_states) {
|
||||
options = hvac_reg_vars.us_states;
|
||||
dataSource = 'us_states';
|
||||
} else if (country === 'Canada' && typeof hvac_reg_vars !== 'undefined' && hvac_reg_vars.ca_provinces) {
|
||||
options = hvac_reg_vars.ca_provinces;
|
||||
dataSource = 'ca_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;
|
||||
}
|
||||
console.log(`[DEBUG] Data source: ${dataSource}, Options found: ${Object.keys(options).length}`);
|
||||
|
||||
// Append new options
|
||||
let optionsAppended = 0;
|
||||
$.each(options, function(value, label) {
|
||||
optionsAppended++;
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
console.log(`[DEBUG] Appended ${optionsAppended} state/province options.`);
|
||||
|
||||
// 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();
|
||||
console.log(`[DEBUG] Country changed to: ${country}`); // Log country change
|
||||
|
||||
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)
|
||||
|
||||
});
|
||||
204
hvac-community-events/hvac-community-events.php
Normal file
204
hvac-community-events/hvac-community-events.php
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: HVAC Community Events
|
||||
* Plugin URI: https://upskillhvac.com
|
||||
* Description: Custom plugin for HVAC trainer event management system
|
||||
* Version: 1.0.0
|
||||
* Author: Upskill HVAC
|
||||
* Author URI: https://upskillhvac.com
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
* Text Domain: hvac-community-events
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
// error_log('[HVAC DEBUG] Main plugin file hvac-community-events.php loaded.'); // REMOVED DEBUG LOG
|
||||
|
||||
// Define plugin constants
|
||||
define('HVAC_CE_VERSION', '1.0.0');
|
||||
define('HVAC_CE_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('HVAC_CE_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
|
||||
/**
|
||||
* Create required pages and roles upon plugin activation.
|
||||
*/
|
||||
function hvac_ce_create_required_pages() {
|
||||
|
||||
// Ensure the roles class is available
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-roles.php';
|
||||
error_log('HVAC CE: Activation hook fired.'); // Add logging start
|
||||
$required_pages = [
|
||||
'community-login' => [
|
||||
'title' => 'Community Login',
|
||||
'content' => '<!-- wp:shortcode -->[hvac_community_login]<!-- /wp:shortcode -->',
|
||||
],
|
||||
'trainer-registration' => [
|
||||
'title' => 'Trainer Registration',
|
||||
'content' => '<!-- wp:shortcode -->[hvac_trainer_registration]<!-- /wp:shortcode -->',
|
||||
],
|
||||
'hvac-dashboard' => [
|
||||
'title' => 'Trainer Dashboard',
|
||||
'content' => '', // Content handled by template or redirect
|
||||
],
|
||||
'manage-event' => [ // New page for TEC CE submission form shortcode
|
||||
'title' => 'Manage Event',
|
||||
'content' => '<!-- wp:shortcode -->[tribe_community_events view="submission_form"]<!-- /wp:shortcode -->',
|
||||
],
|
||||
'my-events' => [ // New page for TEC CE event list shortcode
|
||||
'title' => 'My Events',
|
||||
'content' => '<!-- wp:shortcode -->[tribe_community_events view="my_events"]<!-- /wp:shortcode -->',
|
||||
],
|
||||
'trainer-profile' => [ // Add Trainer Profile page
|
||||
'title' => 'Trainer Profile',
|
||||
'content' => '<!-- wp:shortcode -->[hvac_trainer_profile]<!-- /wp:shortcode -->',
|
||||
],
|
||||
// REMOVED: 'submit-event' page creation. Will link to default TEC CE page.
|
||||
// Add future required pages here
|
||||
];
|
||||
|
||||
$created_pages_option = 'hvac_community_pages';
|
||||
$created_pages = get_option($created_pages_option, []);
|
||||
|
||||
foreach ($required_pages as $slug => $page_data) {
|
||||
// Check if page already exists (by slug)
|
||||
$existing_page = get_page_by_path($slug, OBJECT, 'page');
|
||||
|
||||
if (!$existing_page) {
|
||||
error_log("HVAC CE: Page with slug '{$slug}' not found. Attempting to create."); // Add logging: page missing
|
||||
// Page does not exist, create it
|
||||
$post_data = [
|
||||
'post_title' => $page_data['title'],
|
||||
'post_name' => $slug,
|
||||
'post_content' => $page_data['content'],
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'page',
|
||||
'comment_status' => 'closed',
|
||||
'ping_status' => 'closed',
|
||||
];
|
||||
|
||||
$page_id = wp_insert_post($post_data);
|
||||
|
||||
// Log the result of wp_insert_post
|
||||
if (is_wp_error($page_id)) {
|
||||
error_log("HVAC CE: Error creating page '{$slug}': " . $page_id->get_error_message());
|
||||
} else {
|
||||
error_log("HVAC CE: Successfully created page '{$slug}' with ID: {$page_id}.");
|
||||
}
|
||||
|
||||
// Store the created page ID - Rewritten to avoid tool issue with &&
|
||||
if ($page_id) { // Check if page_id is truthy (non-zero, non-null, etc.)
|
||||
if (!is_wp_error($page_id)) { // Then check if it's not a WP_Error object
|
||||
// Use a key based on the slug or feature name for clarity
|
||||
$feature_key = str_replace('-', '_', $slug);
|
||||
$created_pages[$feature_key] = $page_id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure existing pages are also recorded in the option if not already
|
||||
$feature_key = str_replace('-', '_', $slug);
|
||||
if (!isset($created_pages[$feature_key])) {
|
||||
$created_pages[$feature_key] = $existing_page->ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the option with any newly created page IDs (and existing ones)
|
||||
update_option($created_pages_option, $created_pages);
|
||||
|
||||
// Create the custom role (Moved inside the activation function)
|
||||
$roles_manager = new HVAC_Roles();
|
||||
$roles_manager->create_trainer_role();
|
||||
error_log('HVAC CE: Attempted to create hvac_trainer role.'); // Add logging: role creation attempt
|
||||
|
||||
} // <<-- Brace moved here
|
||||
register_activation_hook(__FILE__, 'hvac_ce_create_required_pages');
|
||||
|
||||
|
||||
/**
|
||||
* Remove custom roles upon plugin deactivation.
|
||||
*/
|
||||
function hvac_ce_remove_roles() {
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-roles.php';
|
||||
$roles_manager = new HVAC_Roles();
|
||||
$roles_manager->remove_trainer_role();
|
||||
error_log('HVAC CE: Deactivation hook fired, attempted to remove hvac_trainer role.');
|
||||
}
|
||||
register_deactivation_hook(__FILE__, 'hvac_ce_remove_roles');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enqueue styles specifically for the HVAC Dashboard page.
|
||||
*/
|
||||
function hvac_ce_enqueue_dashboard_styles() {
|
||||
// Check if we are on the specific dashboard page
|
||||
// Assumes the page slug is 'hvac-dashboard' as created in the activation hook
|
||||
if ( is_page( 'hvac-dashboard' ) ) {
|
||||
wp_enqueue_style(
|
||||
'hvac-dashboard-style',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard.css',
|
||||
[], // No dependencies for now
|
||||
HVAC_CE_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'hvac_ce_enqueue_dashboard_styles' );
|
||||
|
||||
|
||||
/**
|
||||
* Enqueue styles specifically for the HVAC Event Summary page.
|
||||
*/
|
||||
function hvac_ce_enqueue_event_summary_styles() {
|
||||
// Check if we are on a single event page
|
||||
if ( is_singular( Tribe__Events__Main::POSTTYPE ) ) {
|
||||
wp_enqueue_style(
|
||||
'hvac-event-summary-style',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/css/hvac-event-summary.css',
|
||||
[], // No dependencies for now
|
||||
HVAC_CE_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'hvac_ce_enqueue_event_summary_styles' );
|
||||
|
||||
|
||||
|
||||
// Include the main plugin class
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-community-events.php'; // Main plugin class
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-hvac-profile.php'; // Include the new Profile class
|
||||
// Initialize the plugin via plugins_loaded hook
|
||||
function hvac_community_events_init() {
|
||||
// Use Singleton pattern for both classes
|
||||
HVAC_Community_Events::instance();
|
||||
HVAC_Profile::instance(); // Restore instantiation here
|
||||
}
|
||||
add_action('plugins_loaded', 'hvac_community_events_init');
|
||||
|
||||
// REMOVED: Instantiate directly AFTER function definition to ensure hooks are added for E2E tests
|
||||
// HVAC_Profile::instance();
|
||||
|
||||
|
||||
/**
|
||||
* Include custom template for single event summary page.
|
||||
*
|
||||
* @param string $template The path of the template to include.
|
||||
* @return string The path of the template file.
|
||||
*/
|
||||
function hvac_ce_include_event_summary_template( $template ) {
|
||||
// Check if it's a single event post type view
|
||||
if ( is_singular( Tribe__Events__Main::POSTTYPE ) ) {
|
||||
// Check if the custom template exists in the plugin's template directory
|
||||
$custom_template = HVAC_CE_PLUGIN_DIR . 'templates/single-hvac-event-summary.php';
|
||||
if ( file_exists( $custom_template ) ) {
|
||||
// Return the path to the custom template
|
||||
return $custom_template;
|
||||
}
|
||||
}
|
||||
// Return the original template if not a single event or custom template doesn't exist
|
||||
return $template;
|
||||
}
|
||||
add_filter( 'template_include', 'hvac_ce_include_event_summary_template', 99 );
|
||||
152
hvac-community-events/includes/class-hvac-community-events.php
Normal file
152
hvac-community-events/includes/class-hvac-community-events.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
/**
|
||||
* Main plugin class for HVAC Community Events
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class HVAC_Community_Events {
|
||||
/**
|
||||
* The single instance of the class
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Main instance
|
||||
*/
|
||||
public static function instance() {
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
error_log('[HVAC DEBUG] HVAC_Community_Events constructor running.'); // ADDED LOG
|
||||
$this->define_constants();
|
||||
$this->includes();
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define constants
|
||||
*/
|
||||
private function define_constants() {
|
||||
// Additional constants can be defined here
|
||||
}
|
||||
|
||||
/**
|
||||
* Include required files
|
||||
*/
|
||||
private function includes() {
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-roles.php';
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-registration.php';
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-settings.php';
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-login-handler.php'; // Add Login Handler
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-event-handler.php'; // Add Event Handler
|
||||
// Include dashboard data class if it's not autoloaded
|
||||
if ( ! class_exists('HVAC_Dashboard_Data') ) {
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks
|
||||
*/
|
||||
private function init_hooks() {
|
||||
// Register activation/deactivation hooks
|
||||
// Note: These hooks are typically registered outside the class instance context
|
||||
// register_activation_hook(__FILE__, array($this, 'activate')); // This won't work correctly here
|
||||
// register_deactivation_hook(__FILE__, array($this, 'deactivate')); // This won't work correctly here
|
||||
|
||||
// Initialize other hooks
|
||||
add_action('init', array($this, 'init'));
|
||||
|
||||
// Template loading for custom pages
|
||||
add_filter('template_include', array($this, 'load_custom_templates'));
|
||||
} // End init_hooks
|
||||
|
||||
/**
|
||||
* Plugin activation (Should be called statically or from the main plugin file context)
|
||||
*/
|
||||
public static function activate() {
|
||||
// Activation code here (e.g., page creation, role creation)
|
||||
// Note: This method might need to be moved or called differently
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin deactivation (Should be called statically or from the main plugin file context)
|
||||
*/
|
||||
public static function deactivate() {
|
||||
// Remove the hvac_trainer role
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-roles.php'; // Ensure class is available
|
||||
$roles = new HVAC_Roles();
|
||||
$roles->remove_trainer_role();
|
||||
|
||||
// Additional deactivation tasks
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize plugin actions attached to 'init' hook
|
||||
*/
|
||||
public function init() {
|
||||
static $initialized = false; // Flag to run only once
|
||||
if ($initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize handlers
|
||||
error_log('[HVAC DEBUG] HVAC_Community_Events::init() - Before new Login_Handler()'); // Updated Log
|
||||
new \HVAC_Community_Events\Community\Login_Handler();
|
||||
error_log('[HVAC DEBUG] HVAC_Community_Events::init() - Before new HVAC_Registration()'); // Updated Log
|
||||
new HVAC_Registration(); // Instantiate Registration class to register shortcode
|
||||
error_log('[HVAC DEBUG] HVAC_Community_Events::init() - After new HVAC_Registration()'); // Updated Log
|
||||
|
||||
// Prevent trainers from accessing wp-admin
|
||||
add_action('admin_init', array($this, 'redirect_trainers_from_admin'));
|
||||
|
||||
$initialized = true; // Mark as initialized
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect HVAC trainers from admin area to frontend dashboard
|
||||
*/
|
||||
public function redirect_trainers_from_admin() {
|
||||
if (defined('DOING_AJAX') && DOING_AJAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user is trying to access wp-admin and has trainer role but not admin caps
|
||||
if ( is_admin() && ! current_user_can('manage_options') && current_user_can('view_hvac_dashboard') ) {
|
||||
wp_redirect(home_url('/hvac-dashboard/')); // Corrected slug
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom templates for plugin pages.
|
||||
*
|
||||
* @param string $template The path of the template to include.
|
||||
* @return string The path of the template to include.
|
||||
*/
|
||||
public function load_custom_templates( $template ) {
|
||||
// Check if we are on the HVAC Dashboard page
|
||||
if ( is_page( 'hvac-dashboard' ) ) {
|
||||
$new_template = HVAC_CE_PLUGIN_DIR . 'templates/template-hvac-dashboard.php';
|
||||
if ( file_exists( $new_template ) ) {
|
||||
return $new_template;
|
||||
}
|
||||
}
|
||||
|
||||
// Add checks for other custom pages here if needed
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
} // End class HVAC_Community_Events
|
||||
312
hvac-community-events/includes/class-hvac-dashboard-data.php
Normal file
312
hvac-community-events/includes/class-hvac-dashboard-data.php
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Community Events Dashboard Data Handler
|
||||
*
|
||||
* Retrieves and calculates data needed for the Trainer Dashboard.
|
||||
*
|
||||
* @package HVAC Community Events
|
||||
* @subpackage Includes
|
||||
* @author Roo
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class HVAC_Dashboard_Data
|
||||
*
|
||||
* Handles fetching and processing data for the trainer dashboard.
|
||||
*/
|
||||
class HVAC_Dashboard_Data {
|
||||
|
||||
/**
|
||||
* The ID of the trainer user.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private int $user_id;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $user_id The ID of the trainer user.
|
||||
*/
|
||||
public function __construct( int $user_id ) {
|
||||
$this->user_id = $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of events created by the trainer.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_events_count() : int {
|
||||
$args = array(
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
// 'author' => $this->user_id, // Query by organizer instead
|
||||
'post_status' => array( 'publish', 'future', 'draft', 'pending', 'private' ),
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids', // Only need the count
|
||||
// Restore organizer query
|
||||
'meta_key' => '_EventOrganizerID',
|
||||
'meta_value' => $this->user_id,
|
||||
'meta_compare' => '=', // Explicitly set compare
|
||||
'meta_type' => 'NUMERIC', // Specify numeric comparison
|
||||
);
|
||||
$query = new WP_Query( $args );
|
||||
return (int) $query->found_posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of upcoming events for the trainer.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_upcoming_events_count() : int {
|
||||
$today = date( 'Y-m-d H:i:s' );
|
||||
$args = array(
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
// 'author' => $this->user_id, // Query by organizer instead
|
||||
'post_status' => array( 'publish', 'future' ), // Only published or scheduled future events
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids', // Only need the count
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND', // Combine organizer and date query
|
||||
array(
|
||||
'key' => '_EventOrganizerID',
|
||||
'value' => $this->user_id,
|
||||
'compare' => '=',
|
||||
'type' => 'NUMERIC', // Specify numeric comparison
|
||||
),
|
||||
array(
|
||||
'key' => '_EventStartDate',
|
||||
'value' => $today,
|
||||
'compare' => '>=',
|
||||
'type' => 'DATETIME',
|
||||
),
|
||||
),
|
||||
'orderby' => 'event_date',
|
||||
'order' => 'ASC',
|
||||
);
|
||||
$query = new WP_Query( $args );
|
||||
return (int) $query->found_posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of past events for the trainer.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_past_events_count() : int {
|
||||
$today = date( 'Y-m-d H:i:s' );
|
||||
$args = array(
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
// 'author' => $this->user_id, // Query by organizer instead
|
||||
'post_status' => array( 'publish', 'private' ), // Count published or private past events
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids', // Only need the count
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND', // Combine organizer and date query
|
||||
array(
|
||||
'key' => '_EventOrganizerID',
|
||||
'value' => $this->user_id,
|
||||
'compare' => '=',
|
||||
'type' => 'NUMERIC', // Specify numeric comparison
|
||||
),
|
||||
array(
|
||||
'key' => '_EventEndDate', // Use end date to determine if it's truly past
|
||||
'value' => $today,
|
||||
'compare' => '<',
|
||||
'type' => 'DATETIME',
|
||||
),
|
||||
),
|
||||
);
|
||||
$query = new WP_Query( $args );
|
||||
return (int) $query->found_posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of tickets sold across all the trainer's events.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_tickets_sold() : int {
|
||||
$total_tickets = 0;
|
||||
$args = array(
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
// 'author' => $this->user_id, // Query by organizer instead
|
||||
'post_status' => array( 'publish', 'future', 'draft', 'pending', 'private' ), // Include all statuses for historical data
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids', // Only need the IDs
|
||||
'meta_key' => '_EventOrganizerID',
|
||||
'meta_value' => $this->user_id,
|
||||
'meta_compare' => '=', // Explicitly set compare
|
||||
'meta_type' => 'NUMERIC', // Specify numeric comparison
|
||||
'meta_compare' => '=', // Explicitly set compare
|
||||
'meta_type' => 'NUMERIC', // Specify numeric comparison
|
||||
);
|
||||
$event_ids = get_posts( $args );
|
||||
|
||||
if ( ! empty( $event_ids ) ) {
|
||||
foreach ( $event_ids as $event_id ) {
|
||||
// Event Tickets Plus often stores sold count in '_tribe_tickets_sold' meta
|
||||
$sold = get_post_meta( $event_id, '_tribe_tickets_sold', true );
|
||||
if ( is_numeric( $sold ) ) {
|
||||
$total_tickets += (int) $sold;
|
||||
}
|
||||
// Fallback or alternative check if needed (e.g., querying attendee posts)
|
||||
// Depending on the exact ticket plugin setup, this might need adjustment.
|
||||
}
|
||||
}
|
||||
|
||||
return $total_tickets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total revenue generated across all the trainer's events.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function get_total_revenue() : float {
|
||||
$total_revenue = 0.0;
|
||||
$args = array(
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
// 'author' => $this->user_id, // Query by organizer instead
|
||||
'post_status' => array( 'publish', 'future', 'draft', 'pending', 'private' ), // Include all statuses for historical data
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids', // Only need the IDs
|
||||
'meta_key' => '_EventOrganizerID',
|
||||
'meta_value' => $this->user_id,
|
||||
);
|
||||
$event_ids = get_posts( $args );
|
||||
|
||||
if ( ! empty( $event_ids ) ) {
|
||||
foreach ( $event_ids as $event_id ) {
|
||||
// Event Tickets Plus often stores total revenue in '_tribe_revenue_total' meta
|
||||
$revenue = get_post_meta( $event_id, '_tribe_revenue_total', true );
|
||||
if ( is_numeric( $revenue ) ) {
|
||||
$total_revenue += (float) $revenue;
|
||||
}
|
||||
// Depending on the exact ticket plugin setup, this might need adjustment.
|
||||
}
|
||||
}
|
||||
|
||||
return $total_revenue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the annual revenue target set by the trainer.
|
||||
*
|
||||
* @return float|null Returns the target as a float, or null if not set.
|
||||
*/
|
||||
public function get_annual_revenue_target() : ?float {
|
||||
$target = get_user_meta( $this->user_id, 'annual_revenue_target', true );
|
||||
return ! empty( $target ) && is_numeric( $target ) ? (float) $target : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data needed for the events table on the dashboard, filtered by time.
|
||||
*
|
||||
* @param string $filter_time The time period to filter events by ('all', 'upcoming', 'past'). Defaults to 'all'.
|
||||
* @return array An array of event data arrays/objects, each containing keys like: id, status, name, link, date, organizer, capacity, sold, revenue.
|
||||
*/
|
||||
public function get_events_table_data( string $filter_time = 'all' ) : array {
|
||||
$events_data = [];
|
||||
$today = date( 'Y-m-d H:i:s' );
|
||||
$meta_query_args = array( // Base meta query for organizer
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_EventOrganizerID',
|
||||
'value' => $this->user_id,
|
||||
'compare' => '=',
|
||||
'type' => 'NUMERIC',
|
||||
),
|
||||
);
|
||||
$orderby = 'meta_value';
|
||||
$order = 'DESC'; // Default: show most recent first
|
||||
$post_status = array( 'publish', 'future', 'draft', 'pending', 'private' ); // Include all relevant statuses
|
||||
|
||||
// Modify meta query based on time filter
|
||||
if ( 'upcoming' === $filter_time ) {
|
||||
$meta_query_args[] = array(
|
||||
'key' => '_EventStartDate',
|
||||
'value' => $today,
|
||||
'compare' => '>=',
|
||||
'type' => 'DATETIME',
|
||||
);
|
||||
$order = 'ASC'; // Show upcoming events chronologically
|
||||
$post_status = array( 'publish', 'future' ); // Only show published/scheduled upcoming
|
||||
} elseif ( 'past' === $filter_time ) {
|
||||
$meta_query_args[] = array(
|
||||
'key' => '_EventEndDate', // Use end date for past events
|
||||
'value' => $today,
|
||||
'compare' => '<',
|
||||
'type' => 'DATETIME',
|
||||
);
|
||||
// Keep DESC order for past events
|
||||
$post_status = array( 'publish', 'private' ); // Only show completed published/private past events
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
// 'author' => $this->user_id, // Querying by organizer via meta_query
|
||||
'post_status' => $post_status, // Use dynamically set statuses
|
||||
'posts_per_page' => -1,
|
||||
'meta_query' => $meta_query_args, // Use the constructed meta query
|
||||
'orderby' => $orderby, // Use dynamic orderby key
|
||||
'meta_key' => '_EventStartDate', // Still use start date for ordering key
|
||||
'order' => $order, // Use dynamic order direction
|
||||
);
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
|
||||
if ( $query->have_posts() ) {
|
||||
while ( $query->have_posts() ) {
|
||||
$query->the_post();
|
||||
$event_id = get_the_ID();
|
||||
|
||||
// Get Capacity - Sum capacity of all tickets for this event
|
||||
$total_capacity = 0;
|
||||
if ( function_exists( 'tribe_get_tickets' ) ) {
|
||||
$tickets = tribe_get_tickets( $event_id );
|
||||
if ( $tickets ) {
|
||||
foreach ( $tickets as $ticket ) {
|
||||
$capacity = $ticket->capacity();
|
||||
// -1 often means unlimited capacity for Tribe Tickets
|
||||
if ( $capacity === -1 ) {
|
||||
$total_capacity = -1; // Mark as unlimited
|
||||
break; // No need to sum further if one is unlimited
|
||||
}
|
||||
if ( is_numeric( $capacity ) ) {
|
||||
$total_capacity += $capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sold = get_post_meta( $event_id, '_tribe_tickets_sold', true );
|
||||
$revenue = get_post_meta( $event_id, '_tribe_revenue_total', true );
|
||||
|
||||
$events_data[] = array(
|
||||
'id' => $event_id,
|
||||
'status' => get_post_status( $event_id ),
|
||||
'name' => get_the_title(),
|
||||
// Return raw data instead of calling TEC functions here
|
||||
'link' => get_permalink( $event_id ), // Use standard WP permalink
|
||||
'start_date_ts' => strtotime( get_post_meta( $event_id, '_EventStartDate', true ) ), // Return timestamp
|
||||
'organizer_id' => (int) get_post_meta( $event_id, '_EventOrganizerID', true ), // Return organizer ID
|
||||
'capacity' => ( $total_capacity === -1 ) ? 'Unlimited' : (int) $total_capacity,
|
||||
'sold' => is_numeric( $sold ) ? (int) $sold : 0,
|
||||
'revenue' => is_numeric( $revenue ) ? (float) $revenue : 0.0,
|
||||
);
|
||||
}
|
||||
wp_reset_postdata(); // Restore original Post Data
|
||||
}
|
||||
|
||||
return $events_data;
|
||||
}
|
||||
|
||||
} // End class HVAC_Dashboard_Data
|
||||
1066
hvac-community-events/includes/class-hvac-registration.php
Normal file
1066
hvac-community-events/includes/class-hvac-registration.php
Normal file
File diff suppressed because it is too large
Load diff
93
hvac-community-events/includes/class-hvac-roles.php
Normal file
93
hvac-community-events/includes/class-hvac-roles.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles custom roles and capabilities for the HVAC Community Events plugin
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class HVAC_Roles {
|
||||
/**
|
||||
* Create the hvac_trainer role with all required capabilities
|
||||
*/
|
||||
public function create_trainer_role() {
|
||||
// Check if role already exists
|
||||
if (get_role('hvac_trainer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the role with capabilities
|
||||
add_role(
|
||||
'hvac_trainer',
|
||||
__('HVAC Trainer', 'hvac-community-events'),
|
||||
$this->get_trainer_capabilities()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the hvac_trainer role
|
||||
*/
|
||||
public function remove_trainer_role() {
|
||||
remove_role('hvac_trainer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all capabilities for the trainer role
|
||||
*/
|
||||
public function get_trainer_capabilities() {
|
||||
$caps = array(
|
||||
// Basic WordPress capabilities
|
||||
'read' => true,
|
||||
'upload_files' => true,
|
||||
|
||||
// Custom HVAC capabilities
|
||||
'manage_hvac_events' => true,
|
||||
'edit_hvac_profile' => true,
|
||||
'view_hvac_dashboard' => true,
|
||||
'manage_attendees' => true,
|
||||
'email_attendees' => true,
|
||||
|
||||
// The Events Calendar capabilities
|
||||
'publish_tribe_events' => true,
|
||||
'edit_tribe_events' => true,
|
||||
'delete_tribe_events' => true,
|
||||
'edit_published_tribe_events' => true,
|
||||
'delete_published_tribe_events' => true,
|
||||
'read_private_tribe_events' => true,
|
||||
);
|
||||
|
||||
// Explicitly deny admin capabilities
|
||||
$denied_caps = array(
|
||||
'manage_options',
|
||||
'moderate_comments',
|
||||
'manage_categories',
|
||||
'manage_links',
|
||||
'edit_others_posts',
|
||||
'edit_pages',
|
||||
'edit_others_pages',
|
||||
'edit_published_pages',
|
||||
'publish_pages',
|
||||
'delete_pages',
|
||||
'delete_others_pages',
|
||||
'delete_published_pages',
|
||||
'delete_others_posts',
|
||||
'import',
|
||||
'export',
|
||||
'edit_theme_options',
|
||||
);
|
||||
|
||||
foreach ($denied_caps as $cap) {
|
||||
$caps[$cap] = false;
|
||||
}
|
||||
|
||||
return $caps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current user has a specific HVAC trainer capability
|
||||
*/
|
||||
public static function check_trainer_capability($capability) {
|
||||
return current_user_can($capability);
|
||||
}
|
||||
}
|
||||
71
hvac-community-events/includes/class-hvac-settings.php
Normal file
71
hvac-community-events/includes/class-hvac-settings.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles plugin settings and options
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class HVAC_Settings {
|
||||
public function __construct() {
|
||||
add_action('admin_menu', array($this, 'add_admin_menu'));
|
||||
add_action('admin_init', array($this, 'register_settings'));
|
||||
}
|
||||
|
||||
public function add_admin_menu() {
|
||||
add_options_page(
|
||||
__('HVAC Community Events', 'hvac-ce'),
|
||||
__('HVAC Community', 'hvac-ce'),
|
||||
'manage_options',
|
||||
'hvac-ce',
|
||||
array($this, 'options_page')
|
||||
);
|
||||
}
|
||||
|
||||
public function register_settings() {
|
||||
register_setting('hvac_ce_options', 'hvac_ce_options');
|
||||
|
||||
add_settings_section(
|
||||
'hvac_ce_main',
|
||||
__('HVAC Community Events Settings', 'hvac-ce'),
|
||||
array($this, 'settings_section_callback'),
|
||||
'hvac-ce'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'notification_emails',
|
||||
__('Trainer Notification Emails', 'hvac-ce'),
|
||||
array($this, 'notification_emails_callback'),
|
||||
'hvac-ce',
|
||||
'hvac_ce_main'
|
||||
);
|
||||
}
|
||||
|
||||
public function settings_section_callback() {
|
||||
echo '<p>' . __('Configure settings for HVAC Community Events', 'hvac-ce') . '</p>';
|
||||
}
|
||||
|
||||
public function notification_emails_callback() {
|
||||
$options = get_option('hvac_ce_options');
|
||||
echo '<input type="text" name="hvac_ce_options[notification_emails]" value="' .
|
||||
esc_attr($options['notification_emails'] ?? '') . '" class="regular-text">';
|
||||
echo '<p class="description">' .
|
||||
__('Comma-separated list of emails to notify when new trainers register', 'hvac-ce') . '</p>';
|
||||
}
|
||||
|
||||
public function options_page() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e('HVAC Community Events Settings', 'hvac-ce'); ?></h1>
|
||||
<form method="post" action="options.php">
|
||||
<?php
|
||||
settings_fields('hvac_ce_options');
|
||||
do_settings_sections('hvac-ce');
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles the display and processing of the event creation/modification form
|
||||
* for HVAC Trainers. Leverages TEC Community Events functionality where possible.
|
||||
*
|
||||
* NOTE: This class is currently largely unused as functionality has been moved
|
||||
* to using TEC Community Events shortcodes on dedicated pages. Kept for potential future use
|
||||
* or if specific hooks are needed later.
|
||||
*
|
||||
* @package Hvac_Community_Events
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Class HVAC_Event_Handler
|
||||
*/
|
||||
class HVAC_Event_Handler {
|
||||
|
||||
/**
|
||||
* Instance of this class.
|
||||
* @var object
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Return an instance of this class.
|
||||
* @return object A single instance of this class.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
// If the single instance hasn't been set, set it now.
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
self::$instance->init();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks.
|
||||
*/
|
||||
public function init() {
|
||||
// REMOVED: Hooks for processing form submissions (admin_post_hvac_save_event)
|
||||
// add_action( 'admin_post_hvac_save_event', [ $this, 'process_event_submission' ] );
|
||||
// add_action( 'admin_post_nopriv_hvac_save_event', [ $this, 'process_event_submission' ] ); // Handle non-logged-in attempts if necessary
|
||||
|
||||
// REMOVED: Shortcode registration for [hvac_event_form]
|
||||
// add_shortcode( 'hvac_event_form', [ $this, 'display_event_form_shortcode' ] );
|
||||
}
|
||||
|
||||
// REMOVED: display_event_form_shortcode method as we will link to the default TEC CE form page.
|
||||
|
||||
// REMOVED: process_event_submission method as TEC CE shortcode handles its own submission.
|
||||
|
||||
// REMOVED: can_user_edit_event helper method as it's no longer used.
|
||||
|
||||
}
|
||||
|
||||
// Instantiate the class
|
||||
HVAC_Event_Handler::get_instance();
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles data retrieval for the Event Summary page.
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class HVAC_Event_Summary_Data {
|
||||
|
||||
/**
|
||||
* The ID of the event post.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $event_id = null;
|
||||
|
||||
/**
|
||||
* The event post object.
|
||||
*
|
||||
* @var WP_Post|null
|
||||
*/
|
||||
private $event_post = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $event_id The ID of the event to retrieve data for.
|
||||
*/
|
||||
public function __construct( $event_id ) {
|
||||
$this->event_id = absint( $event_id );
|
||||
if ( $this->event_id > 0 ) {
|
||||
$this->event_post = get_post( $this->event_id );
|
||||
// Ensure it's an event post type (adjust post type if needed)
|
||||
if ( ! $this->event_post || get_post_type( $this->event_post ) !== Tribe__Events__Main::POSTTYPE ) {
|
||||
$this->event_id = null;
|
||||
$this->event_post = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event is valid.
|
||||
*
|
||||
* @return bool True if the event ID is valid and the post exists, false otherwise.
|
||||
*/
|
||||
public function is_valid_event() {
|
||||
return ! is_null( $this->event_post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get basic event details.
|
||||
*
|
||||
* @return array|null An array of event details or null if the event is invalid.
|
||||
*/
|
||||
public function get_event_details() {
|
||||
if ( ! $this->is_valid_event() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$details = [
|
||||
'id' => $this->event_id,
|
||||
'title' => get_the_title( $this->event_id ),
|
||||
'description' => apply_filters( 'the_content', get_post_field( 'post_content', $this->event_id ) ),
|
||||
'excerpt' => get_the_excerpt( $this->event_id ),
|
||||
'permalink' => get_permalink( $this->event_id ),
|
||||
'start_date' => null,
|
||||
'end_date' => null,
|
||||
'cost' => null,
|
||||
'is_all_day' => false,
|
||||
'is_recurring'=> false,
|
||||
'timezone' => null,
|
||||
];
|
||||
|
||||
// Use TEC functions if available
|
||||
if ( function_exists( 'tribe_get_start_date' ) ) {
|
||||
$details['start_date'] = tribe_get_start_date( $this->event_id, true, 'Y-m-d H:i:s' ); // Get raw date/time
|
||||
}
|
||||
if ( function_exists( 'tribe_get_end_date' ) ) {
|
||||
$details['end_date'] = tribe_get_end_date( $this->event_id, true, 'Y-m-d H:i:s' ); // Get raw date/time
|
||||
}
|
||||
if ( function_exists( 'tribe_get_cost' ) ) {
|
||||
$details['cost'] = tribe_get_cost( $this->event_id, true );
|
||||
}
|
||||
if ( function_exists( 'tribe_event_is_all_day' ) ) {
|
||||
$details['is_all_day'] = tribe_event_is_all_day( $this->event_id );
|
||||
}
|
||||
if ( function_exists( 'tribe_is_recurring_event' ) ) {
|
||||
$details['is_recurring'] = tribe_is_recurring_event( $this->event_id );
|
||||
}
|
||||
if ( function_exists( 'tribe_get_timezone' ) ) {
|
||||
$details['timezone'] = tribe_get_timezone( $this->event_id );
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event venue details.
|
||||
*
|
||||
* @return array|null An array of venue details or null if the event is invalid or has no venue.
|
||||
*/
|
||||
public function get_event_venue_details() {
|
||||
if ( ! $this->is_valid_event() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$venue_details = null;
|
||||
$venue_id = null;
|
||||
|
||||
if ( function_exists( 'tribe_get_venue_id' ) ) {
|
||||
$venue_id = tribe_get_venue_id( $this->event_id );
|
||||
}
|
||||
|
||||
if ( $venue_id && function_exists( 'tribe_get_venue_details' ) ) {
|
||||
// tribe_get_venue_details is deprecated, use individual functions
|
||||
$venue_details = [
|
||||
'id' => $venue_id,
|
||||
'name' => function_exists('tribe_get_venue') ? tribe_get_venue( $venue_id ) : get_the_title( $venue_id ),
|
||||
'address' => function_exists('tribe_get_full_address') ? tribe_get_full_address( $venue_id ) : null,
|
||||
'street' => function_exists('tribe_get_address') ? tribe_get_address( $venue_id ) : null,
|
||||
'city' => function_exists('tribe_get_city') ? tribe_get_city( $venue_id ) : null,
|
||||
'stateprovince' => function_exists('tribe_get_stateprovince') ? tribe_get_stateprovince( $venue_id ) : null, // Use stateprovince for consistency
|
||||
'state' => function_exists('tribe_get_state') ? tribe_get_state( $venue_id ) : null,
|
||||
'province' => function_exists('tribe_get_province') ? tribe_get_province( $venue_id ) : null,
|
||||
'zip' => function_exists('tribe_get_zip') ? tribe_get_zip( $venue_id ) : null,
|
||||
'country' => function_exists('tribe_get_country') ? tribe_get_country( $venue_id ) : null,
|
||||
'phone' => function_exists('tribe_get_phone') ? tribe_get_phone( $venue_id ) : null,
|
||||
'website' => function_exists('tribe_get_venue_website_link') ? tribe_get_venue_website_link( $venue_id, false ) : null, // Get URL only
|
||||
'map_link' => function_exists('tribe_get_map_link') ? tribe_get_map_link( $venue_id ) : null,
|
||||
'directions_link' => function_exists('tribe_get_directions_link') ? tribe_get_directions_link( $venue_id ) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $venue_details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event organizer details.
|
||||
*
|
||||
* @return array|null An array of organizer details or null if the event is invalid or has no organizer.
|
||||
*/
|
||||
public function get_event_organizer_details() {
|
||||
if ( ! $this->is_valid_event() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$organizer_details = null;
|
||||
$organizer_ids = [];
|
||||
|
||||
if ( function_exists( 'tribe_get_organizer_ids' ) ) {
|
||||
$organizer_ids = tribe_get_organizer_ids( $this->event_id );
|
||||
}
|
||||
|
||||
// Get details for the first organizer found
|
||||
if ( ! empty( $organizer_ids ) && is_array( $organizer_ids ) ) {
|
||||
$organizer_id = $organizer_ids[0];
|
||||
|
||||
if ( $organizer_id > 0 ) {
|
||||
$organizer_details = [
|
||||
'id' => $organizer_id,
|
||||
'name' => function_exists('tribe_get_organizer') ? tribe_get_organizer( $organizer_id ) : get_the_title( $organizer_id ),
|
||||
'phone' => function_exists('tribe_get_organizer_phone') ? tribe_get_organizer_phone( $organizer_id ) : null,
|
||||
'website' => function_exists('tribe_get_organizer_website_link') ? tribe_get_organizer_website_link( $organizer_id, false ) : null, // Get URL only
|
||||
'email' => function_exists('tribe_get_organizer_email') ? tribe_get_organizer_email( $organizer_id ) : null,
|
||||
'permalink' => function_exists('tribe_get_event_link') ? tribe_get_event_link( $organizer_id, false, false ) : get_permalink( $organizer_id ), // Link to organizer post
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $organizer_details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data associated with the event.
|
||||
* Requires Event Tickets / Event Tickets Plus.
|
||||
*
|
||||
* @return array An array of transaction data (e.g., orders, attendees). Empty array if none or invalid event.
|
||||
*/
|
||||
public function get_event_transactions() {
|
||||
if ( ! $this->is_valid_event() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$transactions = [];
|
||||
|
||||
// Check if Event Tickets is active and the necessary class/method exists
|
||||
if ( class_exists( 'Tribe__Tickets__Tickets_Handler' ) && method_exists( Tribe__Tickets__Tickets_Handler::instance(), 'get_attendees_by_id' ) ) {
|
||||
$attendees = Tribe__Tickets__Tickets_Handler::instance()->get_attendees_by_id( $this->event_id );
|
||||
|
||||
if ( is_array( $attendees ) ) {
|
||||
foreach ( $attendees as $attendee ) {
|
||||
// Extract relevant data - structure might vary based on ticket provider (Woo, EDD, RSVP, Tribe)
|
||||
$order_id = isset( $attendee['order_id'] ) ? $attendee['order_id'] : null;
|
||||
$ticket_type_id = isset( $attendee['product_id'] ) ? $attendee['product_id'] : null; // product_id often holds ticket type ID
|
||||
$attendee_id = isset( $attendee['attendee_id'] ) ? $attendee['attendee_id'] : null; // Unique ID for the attendee record
|
||||
|
||||
// Get purchaser info (might be stored differently depending on provider)
|
||||
$purchaser_name = isset( $attendee['holder_name'] ) ? $attendee['holder_name'] : null;
|
||||
$purchaser_email = isset( $attendee['holder_email'] ) ? $attendee['holder_email'] : null;
|
||||
if ( empty( $purchaser_name ) && isset( $attendee['purchaser_name'] ) ) {
|
||||
$purchaser_name = $attendee['purchaser_name'];
|
||||
}
|
||||
if ( empty( $purchaser_email ) && isset( $attendee['purchaser_email'] ) ) {
|
||||
$purchaser_email = $attendee['purchaser_email'];
|
||||
}
|
||||
|
||||
|
||||
$transactions[] = [
|
||||
'attendee_id' => $attendee_id,
|
||||
'order_id' => $order_id,
|
||||
'ticket_type_id' => $ticket_type_id,
|
||||
'ticket_type_name'=> $ticket_type_id ? get_the_title( $ticket_type_id ) : 'N/A',
|
||||
'purchaser_name' => $purchaser_name,
|
||||
'purchaser_email' => $purchaser_email,
|
||||
'security_code' => isset( $attendee['security_code'] ) ? $attendee['security_code'] : null,
|
||||
'checked_in' => isset( $attendee['check_in'] ) ? (bool) $attendee['check_in'] : false,
|
||||
// Add other relevant fields if needed, e.g., price, order date
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
}
|
||||
979
hvac-community-events/includes/community/class-hvac-profile.php
Normal file
979
hvac-community-events/includes/community/class-hvac-profile.php
Normal file
|
|
@ -0,0 +1,979 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles the HVAC trainer profile editing functionality.
|
||||
*
|
||||
* @package HVAC Community Events
|
||||
* @subpackage Includes/Community
|
||||
* @author Roo
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class HVAC_Profile {
|
||||
|
||||
const PROFILE_ACTION = 'hvac_update_profile'; // Action name for admin-post
|
||||
const TRANSIENT_PREFIX = 'hvac_profile_'; // Prefix for transients
|
||||
|
||||
private static $instance = null; // Singleton instance
|
||||
|
||||
/**
|
||||
* Get Singleton instance.
|
||||
*
|
||||
* @return HVAC_Profile
|
||||
*/
|
||||
public static function instance() {
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent direct instantiation.
|
||||
*/
|
||||
private function __construct() { // Make constructor private
|
||||
// Register shortcode for profile form
|
||||
add_shortcode('hvac_trainer_profile', array($this, 'render_profile_form'));
|
||||
|
||||
// Enqueue styles and scripts (reuse registration styles for now)
|
||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
||||
|
||||
// Hook to a later action and check for our specific POST action
|
||||
add_action('wp_loaded', array($this, 'maybe_process_profile_submission'));
|
||||
|
||||
// <<< ADDED: Prevent canonical redirect from stripping our success param
|
||||
add_filter('redirect_canonical', array($this, 'prevent_canonical_redirect_on_success'), 10, 2); // Restore filter
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents canonical redirects on the profile page if our success query var is present.
|
||||
*
|
||||
* @param string $redirect_url The redirect URL.
|
||||
* @param string $requested_url The requested URL.
|
||||
* @return string|false The redirect URL or false to prevent redirect.
|
||||
*/
|
||||
public function prevent_canonical_redirect_on_success($redirect_url, $requested_url) {
|
||||
// Only interfere if it's the profile page and our success flag is set
|
||||
// Use get_queried_object_id() to be more robust than is_page('slug')
|
||||
$profile_page_id = get_page_by_path('trainer-profile', OBJECT, 'page') ? get_page_by_path('trainer-profile', OBJECT, 'page')->ID : null;
|
||||
|
||||
if ($profile_page_id && is_page($profile_page_id) && isset($_GET['profile_updated'])) {
|
||||
// Check if the redirect is simply adding/removing a trailing slash to our success URL
|
||||
// Allow this specific type of canonical redirect to proceed.
|
||||
$success_url_base = home_url('/trainer-profile/');
|
||||
$success_url_with_flag = add_query_arg('profile_updated', '1', $success_url_base);
|
||||
|
||||
// Compare URLs ignoring the trailing slash
|
||||
if (untrailingslashit($redirect_url) === untrailingslashit($success_url_with_flag)) {
|
||||
return $redirect_url; // Allow this specific redirect
|
||||
}
|
||||
// Otherwise, prevent any other canonical redirect on this specific request
|
||||
// error_log("[DEBUG CANONICAL] Preventing redirect from {$requested_url} to {$redirect_url}"); // Optional debug
|
||||
return false;
|
||||
}
|
||||
return $redirect_url; // Allow all other canonical redirects
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the profile form was submitted and processes it.
|
||||
* Hooked to wp_loaded.
|
||||
*/
|
||||
public function maybe_process_profile_submission() {
|
||||
if (isset($_POST['action']) && $_POST['action'] === self::PROFILE_ACTION) {
|
||||
$this->process_profile_submission();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enqueues styles and scripts. Reuses registration form styles.
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] Method called.'); // Log method entry
|
||||
// Only enqueue on pages where the shortcode might be present
|
||||
// A more robust check might involve checking post content or using a flag
|
||||
$profile_page_object = get_page_by_path('trainer-profile', OBJECT, 'page');
|
||||
$profile_page_id = $profile_page_object ? $profile_page_object->ID : null;
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] Profile Page ID found: ' . print_r($profile_page_id, true)); // Log page ID result
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] is_page check result: ' . (is_page($profile_page_id) ? 'true' : 'false')); // Log is_page result
|
||||
|
||||
// More robust check: See if the current post content contains our shortcode
|
||||
global $post;
|
||||
$should_enqueue = false;
|
||||
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'hvac_trainer_profile')) {
|
||||
$should_enqueue = true;
|
||||
}
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] Shortcode check result: ' . ($should_enqueue ? 'true' : 'false')); // Log shortcode check
|
||||
|
||||
// if ($profile_page_id && is_page($profile_page_id)) { // Original check
|
||||
if ($should_enqueue) { // Use shortcode check instead
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] Condition met, enqueuing scripts/styles.'); // Log condition met
|
||||
wp_enqueue_style(
|
||||
'hvac-registration-style',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/css/hvac-registration.css',
|
||||
array(), // Dependencies
|
||||
HVAC_CE_VERSION
|
||||
);
|
||||
wp_enqueue_script(
|
||||
'hvac-registration-script',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/js/hvac-registration.js',
|
||||
array('jquery'), // Dependencies
|
||||
HVAC_CE_VERSION,
|
||||
true // Load in footer
|
||||
);
|
||||
// Pass country/state data to JS (same as registration)
|
||||
$countries = $this->get_country_list();
|
||||
$us_states = $this->get_us_states();
|
||||
$ca_provinces = $this->get_canadian_provinces();
|
||||
// Add debug logging
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] Countries: ' . print_r($countries, true));
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] US States: ' . print_r($us_states, true));
|
||||
error_log('[DEBUG HVAC_Profile::enqueue_scripts] CA Provinces: ' . print_r($ca_provinces, true));
|
||||
|
||||
wp_localize_script('hvac-registration-script', 'hvac_reg_vars', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'), // If needed for future AJAX
|
||||
'countries' => $countries,
|
||||
'us_states' => $us_states,
|
||||
'ca_provinces' => $ca_provinces,
|
||||
'selected_country' => '', // Will be populated dynamically if needed
|
||||
'selected_state' => '' // Will be populated dynamically if needed
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders the profile form shortcode.
|
||||
* Handles security checks and retrieves messages from transients.
|
||||
*/
|
||||
public function render_profile_form() {
|
||||
error_log('[DEBUG PROFILE RENDER] Entering render_profile_form'); // Log entry
|
||||
// Ensure instance exists (runs constructor and adds hooks) when shortcode is rendered
|
||||
self::instance(); // Restore instantiation via shortcode render
|
||||
|
||||
// --- Security Check ---
|
||||
if (!is_user_logged_in() || !current_user_can('edit_hvac_profile')) { // Use appropriate capability
|
||||
return '<p>You must be logged in as a trainer to view this page.</p>';
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$errors = [];
|
||||
$success_message = '';
|
||||
$submitted_data = [];
|
||||
|
||||
$output = ''; // Build output string
|
||||
|
||||
// Check for messages/errors from redirect
|
||||
if (isset($_GET['profile_updated']) && $_GET['profile_updated'] === '1') {
|
||||
$success_message = 'Profile updated successfully.';
|
||||
$output .= '<!-- DEBUG_SUCCESS_FLAG_DETECTED -->'; // Keep debug marker
|
||||
$output .= '<div class="hvac-notice hvac-success"><p>' . esc_html($success_message) . '</p></div>'; // Append to output
|
||||
} elseif (isset($_GET['profile_error']) && $_GET['profile_error'] === '1' && isset($_GET['tid'])) {
|
||||
$transient_id_from_url = sanitize_key($_GET['tid']);
|
||||
$transient_key = self::TRANSIENT_PREFIX . $transient_id_from_url;
|
||||
|
||||
$transient_data = get_transient($transient_key);
|
||||
|
||||
if ($transient_data && is_array($transient_data)) {
|
||||
$errors = $transient_data['errors'] ?? [];
|
||||
$submitted_data = $transient_data['data'] ?? [];
|
||||
delete_transient($transient_key);
|
||||
} else {
|
||||
$errors['transient'] = 'Could not retrieve submission details. Please try again.';
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
$output .= '<div class="hvac-errors">'; // Append to output
|
||||
foreach ($errors as $error_key => $error_message) {
|
||||
$output .= '<p class="error-message"><strong>Error:</strong> ' . esc_html($error_message) . '</p>'; // Append to output
|
||||
}
|
||||
$output .= '</div>'; // Append to output
|
||||
}
|
||||
}
|
||||
|
||||
// Display the form HTML (needs to return instead of echo now)
|
||||
$output .= $this->display_profile_form_html($user_id, $errors, $submitted_data); // <<< Pass submitted_data, append output
|
||||
|
||||
error_log('[DEBUG PROFILE RENDER] Exiting render_profile_form'); // Log exit
|
||||
return $output; // Return the built string
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the actual profile form HTML.
|
||||
* NOW RETURNS HTML STRING INSTEAD OF ECHOING.
|
||||
*
|
||||
* @param int $user_id The ID of the current user.
|
||||
* @param array $errors Array of validation errors.
|
||||
* @param array $submitted_data Array of submitted form data (used for repopulation on error).
|
||||
* @return string Form HTML.
|
||||
*/
|
||||
private function display_profile_form_html($user_id, $errors = [], $submitted_data = []) { // <<< Added $submitted_data param
|
||||
error_log('[DEBUG PROFILE RENDER] Entering display_profile_form_html for user ID: ' . $user_id); // Log entry
|
||||
$current_user = get_userdata($user_id);
|
||||
if (!$current_user) {
|
||||
return '<p>Error: Could not load user data.</p>'; // Return instead of echo
|
||||
}
|
||||
|
||||
// Fetch user meta data - adapt keys from registration logic
|
||||
$meta_keys = [
|
||||
'first_name', 'last_name', 'description', // Core WP fields
|
||||
'user_url', // Core WP field
|
||||
'user_linkedin', 'personal_accreditation', 'business_name',
|
||||
'business_phone', 'business_email', 'business_website', 'business_description',
|
||||
'user_country', 'user_state', 'user_city', 'user_zip',
|
||||
'business_type', 'training_audience', 'training_formats',
|
||||
'training_locations', 'training_resources',
|
||||
'profile_image_id', // Store attachment ID instead of URL
|
||||
'linked_venue_id' // Store linked venue post ID
|
||||
];
|
||||
$user_meta = [];
|
||||
foreach ($meta_keys as $key) {
|
||||
// Core WP fields are directly on the user object
|
||||
if (isset($current_user->$key)) {
|
||||
$user_meta[$key] = $current_user->$key;
|
||||
} else {
|
||||
$user_meta[$key] = get_user_meta($user_id, $key, true);
|
||||
}
|
||||
}
|
||||
// Special handling for user_email and display_name from user object
|
||||
$user_meta['user_email'] = $current_user->user_email;
|
||||
$user_meta['display_name'] = $current_user->display_name;
|
||||
|
||||
// Prioritize submitted data (if available, e.g., after error) over stored meta for repopulation
|
||||
$form_data = !empty($submitted_data) ? $submitted_data : $user_meta; // <<< Use submitted data if present
|
||||
|
||||
// Ensure array types for checkboxes/multiselects using the correct data source
|
||||
$array_keys = ['training_audience', 'training_formats', 'training_locations', 'training_resources'];
|
||||
foreach($array_keys as $key) {
|
||||
if (!isset($form_data[$key]) || !is_array($form_data[$key])) { // Check form_data
|
||||
$form_data[$key] = []; // Default to empty array if not set or not array
|
||||
}
|
||||
}
|
||||
|
||||
// Get profile image URL (still based on stored meta)
|
||||
$profile_image_url = '';
|
||||
if (!empty($user_meta['profile_image_id']) && is_numeric($user_meta['profile_image_id'])) {
|
||||
$profile_image_url = wp_get_attachment_image_url((int)$user_meta['profile_image_id'], 'thumbnail'); // Or another appropriate size
|
||||
}
|
||||
|
||||
// Get linked venue info (still based on stored meta)
|
||||
$linked_venue_id = $user_meta['linked_venue_id'] ?? null;
|
||||
$venue_info = '';
|
||||
if ($linked_venue_id && get_post_status($linked_venue_id) === 'publish') {
|
||||
$venue_title = get_the_title($linked_venue_id);
|
||||
$venue_url = get_permalink($linked_venue_id); // Or admin edit link if preferred
|
||||
$venue_info = 'Linked Training Venue: <a href="' . esc_url($venue_url) . '" target="_blank">' . esc_html($venue_title) . '</a>';
|
||||
// TODO: Add more venue details if needed (address etc.)
|
||||
} elseif ($linked_venue_id) {
|
||||
$venue_info = 'Linked Training Venue: (Venue not published or found)';
|
||||
} else {
|
||||
$venue_info = 'No Training Venue linked to this profile.';
|
||||
}
|
||||
|
||||
// Start building HTML string
|
||||
$html = '<div class="hvac-profile-form hvac-registration-form">'; // Reuse registration form class
|
||||
$html .= '<h2>Edit Trainer Profile</h2>';
|
||||
|
||||
$html .= '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" id="hvac-trainer-profile-form" enctype="multipart/form-data" novalidate>';
|
||||
$html .= '<input type="hidden" name="action" value="' . esc_attr(self::PROFILE_ACTION) . '">';
|
||||
$html .= wp_nonce_field(self::PROFILE_ACTION, 'hvac_profile_nonce', true, false); // Return nonce field instead of echoing
|
||||
|
||||
// Account Information
|
||||
$html .= '<div class="form-section">';
|
||||
$html .= '<h3>Account Information</h3>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="user_email"><strong>Email *</strong></label>';
|
||||
$html .= '<input type="email" name="user_email" id="user_email" value="' . esc_attr($form_data['user_email'] ?? '') . '" required aria-describedby="user_email_error">';
|
||||
if (isset($errors['user_email'])) $html .= '<p class="error-message" id="user_email_error">' . esc_html($errors['user_email']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row form-row-half">';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="user_pass">New Password</label>';
|
||||
$html .= '<input type="password" name="user_pass" id="user_pass" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must be at least 8 characters long, and include at least one uppercase letter, one lowercase letter, and one number." aria-describedby="user_pass_hint user_pass_error">';
|
||||
$html .= '<small id="user_pass_hint">Leave blank to keep current password. Must be 8+ chars, upper, lower, number.</small>';
|
||||
if (isset($errors['user_pass'])) $html .= '<p class="error-message" id="user_pass_error">' . esc_html($errors['user_pass']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="confirm_password">Confirm New Password</label>';
|
||||
$html .= '<input type="password" name="confirm_password" id="confirm_password" aria-describedby="confirm_password_error">';
|
||||
if (isset($errors['confirm_password'])) $html .= '<p class="error-message" id="confirm_password_error">' . esc_html($errors['confirm_password']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="current_password"><strong>Current Password *</strong></label>';
|
||||
$html .= '<input type="password" name="current_password" id="current_password" required aria-describedby="current_password_hint current_password_error">';
|
||||
$html .= '<small id="current_password_hint">Required to update email or password.</small>';
|
||||
if (isset($errors['current_password'])) $html .= '<p class="error-message" id="current_password_error">' . esc_html($errors['current_password']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End Account Info
|
||||
|
||||
// Personal Information Section
|
||||
$html .= '<div class="form-section">';
|
||||
$html .= '<h3>Personal Information</h3>';
|
||||
$html .= '<div class="form-row form-row-half">';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="first_name"><strong>First Name *</strong></label>';
|
||||
$html .= '<input type="text" name="first_name" id="first_name" value="' . esc_attr($form_data['first_name'] ?? '') . '" required aria-describedby="first_name_error">';
|
||||
if (isset($errors['first_name'])) $html .= '<p class="error-message" id="first_name_error">' . esc_html($errors['first_name']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="last_name"><strong>Last Name *</strong></label>';
|
||||
$html .= '<input type="text" name="last_name" id="last_name" value="' . esc_attr($form_data['last_name'] ?? '') . '" required aria-describedby="last_name_error">';
|
||||
if (isset($errors['last_name'])) $html .= '<p class="error-message" id="last_name_error">' . esc_html($errors['last_name']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="display_name"><strong>Display Name *</strong></label>';
|
||||
$html .= '<input type="text" name="display_name" id="display_name" value="' . esc_attr($form_data['display_name'] ?? '') . '" required aria-describedby="display_name_hint display_name_error">';
|
||||
$html .= '<small id="display_name_hint">This will be the name displayed to other users on the site.</small>';
|
||||
if (isset($errors['display_name'])) $html .= '<p class="error-message" id="display_name_error">' . esc_html($errors['display_name']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row form-row-half">';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="user_url">Personal Website (optional)</label>';
|
||||
$html .= '<input type="url" name="user_url" id="user_url" value="' . esc_attr($form_data['user_url'] ?? '') . '" aria-describedby="user_url_error">';
|
||||
if (isset($errors['user_url'])) $html .= '<p class="error-message" id="user_url_error">' . esc_html($errors['user_url']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="user_linkedin">LinkedIn Profile URL (optional)</label>';
|
||||
$html .= '<input type="url" name="user_linkedin" id="user_linkedin" value="' . esc_attr($form_data['user_linkedin'] ?? '') . '" aria-describedby="user_linkedin_error">';
|
||||
if (isset($errors['user_linkedin'])) $html .= '<p class="error-message" id="user_linkedin_error">' . esc_html($errors['user_linkedin']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="personal_accreditation">Personal Accreditation (optional)</label>';
|
||||
$html .= '<input type="text" name="personal_accreditation" id="personal_accreditation" value="' . esc_attr($form_data['personal_accreditation'] ?? '') . '" aria-describedby="personal_accreditation_hint">';
|
||||
$html .= '<small id="personal_accreditation_hint">Enter your abbreviated accreditations separated by commas.</small>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="description"><strong>Biographical Info *</strong></label>';
|
||||
$html .= '<textarea name="description" id="description" rows="4" required aria-describedby="description_hint description_error">' . esc_textarea($form_data['description'] ?? '') . '</textarea>';
|
||||
$html .= '<small id="description_hint">A short bio about yourself. This will be displayed on your profile page.</small>';
|
||||
if (isset($errors['description'])) $html .= '<p class="error-message" id="description_error">' . esc_html($errors['description']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="profile_image">Profile Image (optional)</label>';
|
||||
if ($profile_image_url) {
|
||||
$html .= '<div class="current-profile-image">';
|
||||
$html .= '<img src="' . esc_url($profile_image_url) . '" alt="Current Profile Image" style="max-width: 100px; height: auto; margin-bottom: 10px;">';
|
||||
$html .= '<label style="display: block; margin-bottom: 5px;">';
|
||||
$html .= '<input type="checkbox" name="delete_profile_image" value="1"> Delete current image';
|
||||
$html .= '</label>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '<input type="file" name="profile_image" id="profile_image" accept="image/jpeg,image/png,image/gif" aria-describedby="profile_image_hint profile_image_error">';
|
||||
$html .= '<small id="profile_image_hint">Upload a new image to replace the current one (.jpg, .png, .gif).</small>';
|
||||
if (isset($errors['profile_image'])) $html .= '<p class="error-message" id="profile_image_error">' . esc_html($errors['profile_image']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End Personal Info
|
||||
|
||||
// Business Information Section
|
||||
$html .= '<div class="form-section">';
|
||||
$html .= '<h3>Business Information</h3>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="business_name"><strong>Business Name *</strong></label>';
|
||||
$html .= '<input type="text" name="business_name" id="business_name" value="' . esc_attr($form_data['business_name'] ?? '') . '" required aria-describedby="business_name_error">';
|
||||
if (isset($errors['business_name'])) $html .= '<p class="error-message" id="business_name_error">' . esc_html($errors['business_name']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row form-row-half">';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="business_phone"><strong>Business Phone *</strong></label>';
|
||||
$html .= '<input type="tel" name="business_phone" id="business_phone" value="' . esc_attr($form_data['business_phone'] ?? '') . '" required aria-describedby="business_phone_error">';
|
||||
if (isset($errors['business_phone'])) $html .= '<p class="error-message" id="business_phone_error">' . esc_html($errors['business_phone']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="business_email"><strong>Business Email *</strong></label>';
|
||||
$html .= '<input type="email" name="business_email" id="business_email" value="' . esc_attr($form_data['business_email'] ?? '') . '" required aria-describedby="business_email_error">';
|
||||
if (isset($errors['business_email'])) $html .= '<p class="error-message" id="business_email_error">' . esc_html($errors['business_email']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="business_website">Business Website (optional)</label>';
|
||||
$html .= '<input type="url" name="business_website" id="business_website" value="' . esc_attr($form_data['business_website'] ?? '') . '" aria-describedby="business_website_error">';
|
||||
if (isset($errors['business_website'])) $html .= '<p class="error-message" id="business_website_error">' . esc_html($errors['business_website']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="business_description"><strong>Business Description *</strong></label>';
|
||||
$html .= '<textarea name="business_description" id="business_description" rows="4" required aria-describedby="business_description_error">' . esc_textarea($form_data['business_description'] ?? '') . '</textarea>';
|
||||
if (isset($errors['business_description'])) $html .= '<p class="error-message" id="business_description_error">' . esc_html($errors['business_description']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End Business Info
|
||||
|
||||
// Address Information Section
|
||||
$html .= '<div class="form-section">';
|
||||
$html .= '<h3>Address Information</h3>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="user_country"><strong>Country *</strong></label>';
|
||||
$html .= '<select name="user_country" id="user_country" required aria-describedby="user_country_error">';
|
||||
$html .= '<option value="">Select Country</option>';
|
||||
$html .= '<option value="United States" ' . selected($form_data['user_country'] ?? '', 'United States', false) . '>United States</option>';
|
||||
$html .= '<option value="Canada" ' . selected($form_data['user_country'] ?? '', 'Canada', false) . '>Canada</option>';
|
||||
$html .= '<option value="" disabled>---</option>';
|
||||
$countries = $this->get_country_list();
|
||||
foreach ($countries as $code => $name) {
|
||||
if ($code !== 'US' && $code !== 'CA') {
|
||||
$html .= '<option value="' . esc_attr($name) . '" ' . selected($form_data['user_country'] ?? '', $name, false) . '>' . esc_html($name) . '</option>';
|
||||
}
|
||||
}
|
||||
$html .= '</select>';
|
||||
if (isset($errors['user_country'])) $html .= '<p class="error-message" id="user_country_error">' . esc_html($errors['user_country']) . '</p>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="form-row form-row-half">';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="user_state"><strong>State/Province *</strong></label>';
|
||||
$html .= '<select name="user_state" id="user_state" required aria-describedby="user_state_error">';
|
||||
$html .= '<option value="">Select State/Province</option>';
|
||||
$html .= '<option value="Other" ' . selected($form_data['user_state'] ?? '', 'Other', false) . '>Other</option>';
|
||||
$selected_state = $form_data['user_state'] ?? ''; // Use form_data
|
||||
if (!empty($selected_state) && $selected_state !== 'Other') {
|
||||
$html .= '<option value="' . esc_attr($selected_state) . '" selected>' . esc_html($selected_state) . '</option>';
|
||||
}
|
||||
$html .= '</select>';
|
||||
$other_style = (($form_data['user_state'] ?? '') === 'Other' && ($form_data['user_country'] ?? '') !== 'United States' && ($form_data['user_country'] ?? '') !== 'Canada') ? '' : 'display:none;';
|
||||
$html .= '<input type="text" name="user_state_other" id="user_state_other" value="' . esc_attr($form_data['user_state_other'] ?? '') . '" style="' . $other_style . ' margin-top: 0.5rem;" placeholder="Enter your state/province" aria-describedby="user_state_other_error">';
|
||||
if (isset($errors['user_state'])) $html .= '<p class="error-message" id="user_state_error">' . esc_html($errors['user_state']) . '</p>';
|
||||
if (isset($errors['user_state_other'])) $html .= '<p class="error-message" id="user_state_other_error">' . esc_html($errors['user_state_other']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div>';
|
||||
$html .= '<label for="user_city"><strong>City *</strong></label>';
|
||||
$html .= '<input type="text" name="user_city" id="user_city" value="' . esc_attr($form_data['user_city'] ?? '') . '" required aria-describedby="user_city_error">';
|
||||
if (isset($errors['user_city'])) $html .= '<p class="error-message" id="user_city_error">' . esc_html($errors['user_city']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label for="user_zip"><strong>Zip/Postal Code *</strong></label>';
|
||||
$html .= '<input type="text" name="user_zip" id="user_zip" value="' . esc_attr($form_data['user_zip'] ?? '') . '" required aria-describedby="user_zip_error">';
|
||||
if (isset($errors['user_zip'])) $html .= '<p class="error-message" id="user_zip_error">' . esc_html($errors['user_zip']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End Address Info
|
||||
|
||||
// Training Venue Section
|
||||
$html .= '<div class="form-section">';
|
||||
$html .= '<h3>Training Venue</h3>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<p>' . wp_kses_post($venue_info) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End Training Venue
|
||||
|
||||
// Training Information Section
|
||||
$html .= '<div class="form-section">';
|
||||
$html .= '<h3>Training Information</h3>';
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label id="business_type_label"><strong>Business Type *</strong></label>';
|
||||
$html .= '<small>What type of business are you?</small>';
|
||||
$html .= '<div class="radio-group" role="radiogroup" aria-labelledby="business_type_label">';
|
||||
$business_types = ["Manufacturer", "Distributor", "Contractor", "Consultant", "Educator", "Government", "Other"];
|
||||
foreach ($business_types as $type) {
|
||||
$html .= '<label><input type="radio" name="business_type" value="' . esc_attr($type) . '" ' . checked($form_data['business_type'] ?? '', $type, false) . ' required> ' . esc_html($type) . '</label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if (isset($errors['business_type'])) $html .= '<p class="error-message">' . esc_html($errors['business_type']) . '</p>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label id="training_audience_label"><strong>Training Audience *</strong></label>';
|
||||
$html .= '<small>Who do you offer training to? (Select all that apply)</small>';
|
||||
$html .= '<div class="checkbox-group" role="group" aria-labelledby="training_audience_label">';
|
||||
$audience_options = [
|
||||
"Anyone" => "Anyone (open to the public)",
|
||||
"Industry professionals" => "Industry professionals",
|
||||
"Internal staff" => "Internal staff in my company",
|
||||
"Registered students" => "Registered students/members of my org/institution"
|
||||
];
|
||||
$selected_audience = $form_data['training_audience'] ?? []; // Use form_data
|
||||
foreach ($audience_options as $value => $label) {
|
||||
$html .= '<label><input type="checkbox" name="training_audience[]" value="' . esc_attr($value) . '" ' . checked(in_array($value, $selected_audience), true, false) . '> ' . esc_html($label) . '</label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if (isset($errors['training_audience'])) $html .= '<p class="error-message">' . esc_html($errors['training_audience']) . '</p>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label id="training_formats_label"><strong>Training Formats *</strong></label>';
|
||||
$html .= '<small>What formats of training do you offer?</small>';
|
||||
$html .= '<div class="checkbox-group" role="group" aria-labelledby="training_formats_label">';
|
||||
$format_options = ["In-person", "Virtual", "Hybrid", "On-demand"];
|
||||
$selected_formats = $form_data['training_formats'] ?? []; // Use form_data
|
||||
foreach ($format_options as $option) {
|
||||
$html .= '<label><input type="checkbox" name="training_formats[]" value="' . esc_attr($option) . '" ' . checked(in_array($option, $selected_formats), true, false) . '> ' . esc_html($option) . '</label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if (isset($errors['training_formats'])) $html .= '<p class="error-message">' . esc_html($errors['training_formats']) . '</p>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label id="training_locations_label"><strong>Training Locations *</strong></label>';
|
||||
$html .= '<small>Where are you willing to provide training? (Select all that apply)</small>';
|
||||
$html .= '<div class="checkbox-group" role="group" aria-labelledby="training_locations_label">';
|
||||
$location_options = ["Online", "Local", "Regional", "Travel National", "Travel International"];
|
||||
$selected_locations = $form_data['training_locations'] ?? []; // Use form_data
|
||||
foreach ($location_options as $option) {
|
||||
$html .= '<label><input type="checkbox" name="training_locations[]" value="' . esc_attr($option) . '" ' . checked(in_array($option, $selected_locations), true, false) . '> ' . esc_html($option) . '</label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if (isset($errors['training_locations'])) $html .= '<p class="error-message">' . esc_html($errors['training_locations']) . '</p>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="form-row">';
|
||||
$html .= '<label id="training_resources_label"><strong>Training Resources *</strong></label>';
|
||||
$html .= '<small>What training resources do you have access to? (Select all that apply)</small>';
|
||||
$html .= '<div class="checkbox-group" role="group" aria-labelledby="training_resources_label">';
|
||||
$resource_options = [
|
||||
"Classroom" => "Classroom",
|
||||
"Training Lab" => "Training Lab",
|
||||
"Ducted Furnace(s)" => "Ducted Furnace(s)",
|
||||
"Ducted Air Handler(s)" => "Ducted Air Handler(s)",
|
||||
"Ducted Air Conditioner(s)" => "Ducted Air Conditioner(s)",
|
||||
"Ducted Heat Pump(s)" => "Ducted Heat Pump(s)",
|
||||
"Ductless Heat Pump(s)" => "Ductless Heat Pump(s)",
|
||||
"Training Manuals" => "Training Manuals",
|
||||
"Presentation Slides" => "Presentation Slides",
|
||||
"LMS Platform / SCORM Files" => "LMS Platform / SCORM Files",
|
||||
"Custom Curriculum" => "Custom Curriculum",
|
||||
"Other" => "Other"
|
||||
];
|
||||
$selected_resources = $form_data['training_resources'] ?? []; // Use form_data
|
||||
foreach ($resource_options as $value => $label) {
|
||||
$html .= '<label><input type="checkbox" name="training_resources[]" value="' . esc_attr($value) . '" ' . checked(in_array($value, $selected_resources), true, false) . '> ' . esc_html($label) . '</label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if (isset($errors['training_resources'])) $html .= '<p class="error-message">' . esc_html($errors['training_resources']) . '</p>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End Training Info
|
||||
|
||||
$html .= '<div class="form-row submit-row">';
|
||||
$html .= '<input type="submit" value="Update Profile">';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '</form>';
|
||||
$html .= '</div>'; // End .hvac-profile-form
|
||||
|
||||
error_log('[DEBUG PROFILE RENDER] Exiting display_profile_form_html'); // Log exit
|
||||
return $html; // Return the built string
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes the profile form submission.
|
||||
* Handles validation, user update, and redirects.
|
||||
*/
|
||||
public function process_profile_submission() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['hvac_profile_nonce']) || !wp_verify_nonce($_POST['hvac_profile_nonce'], self::PROFILE_ACTION)) {
|
||||
wp_die('Security check failed.');
|
||||
}
|
||||
|
||||
// Check user logged in
|
||||
if (!is_user_logged_in()) {
|
||||
wp_die('You must be logged in to update your profile.');
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Check capability
|
||||
if (!current_user_can('edit_hvac_profile', $user_id)) { // Check capability for the specific user
|
||||
wp_die('You do not have permission to edit this profile.');
|
||||
}
|
||||
|
||||
// Sanitize POST data (basic sanitization, more specific in validation/update)
|
||||
$submitted_data = stripslashes_deep($_POST);
|
||||
|
||||
// Validate data
|
||||
$errors = $this->validate_profile_data($submitted_data, $user_id);
|
||||
|
||||
// Redirect back with errors if validation fails
|
||||
if (!empty($errors)) {
|
||||
$redirect_url = home_url('/trainer-profile/'); // Redirect back to profile page
|
||||
$this->redirect_with_errors($errors, $redirect_url, $submitted_data); // <<< Pass submitted data
|
||||
exit; // Stop execution after redirect
|
||||
}
|
||||
|
||||
// Handle image upload/deletion
|
||||
$image_data = $_FILES['profile_image'] ?? null;
|
||||
$delete_image = isset($submitted_data['delete_profile_image']) && $submitted_data['delete_profile_image'] === '1';
|
||||
|
||||
// Attempt to update the user account
|
||||
$update_result = $this->update_trainer_account($user_id, $submitted_data, $image_data, $delete_image);
|
||||
|
||||
// Handle update result
|
||||
if (is_wp_error($update_result)) {
|
||||
// Update failed, redirect back with error message(s)
|
||||
$errors = $update_result->get_error_messages(); // Get all error messages
|
||||
$error_codes = $update_result->get_error_codes();
|
||||
$error_map = [];
|
||||
foreach($error_codes as $index => $code) {
|
||||
$error_map[$code] = $errors[$index]; // Map code to message
|
||||
}
|
||||
$redirect_url = home_url('/trainer-profile/');
|
||||
$this->redirect_with_errors($error_map, $redirect_url, $submitted_data); // Pass error map and submitted data
|
||||
exit;
|
||||
} elseif ($update_result === true) {
|
||||
// Success! Redirect with success flag
|
||||
$redirect_url = add_query_arg('profile_updated', '1', home_url('/trainer-profile/'));
|
||||
wp_safe_redirect($redirect_url);
|
||||
exit;
|
||||
} else {
|
||||
// Should not happen if update_trainer_account always returns true or WP_Error
|
||||
$errors['general'] = 'An unexpected error occurred during profile update.';
|
||||
$redirect_url = home_url('/trainer-profile/');
|
||||
$this->redirect_with_errors($errors, $redirect_url, $submitted_data); // Pass submitted data
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects back to a URL with errors stored in a transient.
|
||||
* NOW ACCEPTS SUBMITTED DATA TO STORE IN TRANSIENT.
|
||||
*
|
||||
* @param array $errors Array of errors (key => message).
|
||||
* @param string $redirect_url URL to redirect back to.
|
||||
* @param array $submitted_data The submitted form data.
|
||||
*/
|
||||
private function redirect_with_errors($errors, $redirect_url, $submitted_data) { // <<< Added $submitted_data param
|
||||
$transient_id = wp_generate_password(12, false); // Generate a unique ID for the transient
|
||||
$transient_key = self::TRANSIENT_PREFIX . $transient_id;
|
||||
|
||||
// Store both errors and submitted data in the transient
|
||||
$transient_data = [
|
||||
'errors' => $errors,
|
||||
'data' => $submitted_data, // <<< Store submitted data
|
||||
];
|
||||
|
||||
set_transient($transient_key, $transient_data, MINUTE_IN_SECONDS * 5); // Store for 5 minutes
|
||||
|
||||
// Add error flag and transient ID to the redirect URL
|
||||
$redirect_url = add_query_arg(array(
|
||||
'profile_error' => '1',
|
||||
'tid' => $transient_id
|
||||
), $redirect_url);
|
||||
|
||||
wp_safe_redirect($redirect_url);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the submitted profile data.
|
||||
* Changed from private to public for unit testing.
|
||||
*
|
||||
* @param array $data Submitted form data.
|
||||
* @param int $user_id The ID of the user being updated.
|
||||
* @return array Array of errors (empty if valid).
|
||||
*/
|
||||
public function validate_profile_data($data, $user_id) { // Changed from private to public
|
||||
$errors = [];
|
||||
$current_user = get_userdata($user_id);
|
||||
|
||||
// --- Account Information ---
|
||||
// Email
|
||||
if (empty($data['user_email'])) {
|
||||
$errors['user_email'] = 'Email address is required.';
|
||||
} elseif (!is_email($data['user_email'])) {
|
||||
$errors['user_email'] = 'Invalid email address format.';
|
||||
} elseif ($data['user_email'] !== $current_user->user_email && email_exists($data['user_email'])) {
|
||||
$errors['user_email'] = 'This email address is already in use by another account.';
|
||||
}
|
||||
|
||||
// Password (only validate if a new password is provided)
|
||||
if (!empty($data['user_pass'])) {
|
||||
if (strlen($data['user_pass']) < 8) {
|
||||
$errors['user_pass'] = 'Password must be at least 8 characters long.';
|
||||
} elseif (!preg_match('/[A-Z]/', $data['user_pass'])) {
|
||||
$errors['user_pass'] = 'Password must contain at least one uppercase letter.';
|
||||
} elseif (!preg_match('/[a-z]/', $data['user_pass'])) {
|
||||
$errors['user_pass'] = 'Password must contain at least one lowercase letter.';
|
||||
} elseif (!preg_match('/[0-9]/', $data['user_pass'])) {
|
||||
$errors['user_pass'] = 'Password must contain at least one number.';
|
||||
} elseif (empty($data['confirm_password'])) {
|
||||
$errors['confirm_password'] = 'Please confirm your new password.';
|
||||
} elseif ($data['user_pass'] !== $data['confirm_password']) {
|
||||
$errors['confirm_password'] = 'Passwords do not match.';
|
||||
}
|
||||
}
|
||||
|
||||
// Current Password (required only if email or password changes)
|
||||
$email_changed = isset($data['user_email']) && $data['user_email'] !== $current_user->user_email;
|
||||
$password_changed = !empty($data['user_pass']);
|
||||
if (($email_changed || $password_changed) && empty($data['current_password'])) {
|
||||
$errors['current_password'] = 'Current password is required to update email or password.';
|
||||
}
|
||||
// Note: Actual check if current password is correct happens in update_trainer_account
|
||||
|
||||
// --- Personal Information ---
|
||||
if (empty($data['first_name'])) $errors['first_name'] = 'First Name is required.';
|
||||
if (empty($data['last_name'])) $errors['last_name'] = 'Last Name is required.';
|
||||
if (empty($data['display_name'])) $errors['display_name'] = 'Display Name is required.';
|
||||
if (!empty($data['user_url']) && !filter_var($data['user_url'], FILTER_VALIDATE_URL)) $errors['user_url'] = 'Invalid Personal Website URL format.';
|
||||
if (!empty($data['user_linkedin']) && !filter_var($data['user_linkedin'], FILTER_VALIDATE_URL)) $errors['user_linkedin'] = 'Invalid LinkedIn Profile URL format.';
|
||||
if (empty($data['description'])) $errors['description'] = 'Biographical Info is required.';
|
||||
|
||||
// Profile Image Validation (basic)
|
||||
if (isset($_FILES['profile_image']) && $_FILES['profile_image']['error'] === UPLOAD_ERR_OK) {
|
||||
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
|
||||
$file_info = wp_check_filetype_and_ext($_FILES['profile_image']['tmp_name'], $_FILES['profile_image']['name']);
|
||||
if (empty($file_info['type']) || !in_array($file_info['type'], $allowed_types)) {
|
||||
$errors['profile_image'] = 'Invalid file type. Please upload a JPG, PNG, or GIF image.';
|
||||
}
|
||||
// Add size check if needed:
|
||||
// if ($_FILES['profile_image']['size'] > MAX_UPLOAD_SIZE) {
|
||||
// $errors['profile_image'] = 'File size exceeds limit.';
|
||||
// }
|
||||
} elseif (isset($_FILES['profile_image']) && $_FILES['profile_image']['error'] !== UPLOAD_ERR_NO_FILE && $_FILES['profile_image']['error'] !== UPLOAD_ERR_OK) {
|
||||
$errors['profile_image'] = 'Error uploading profile image. Code: ' . $_FILES['profile_image']['error'];
|
||||
}
|
||||
|
||||
|
||||
// --- Business Information ---
|
||||
if (empty($data['business_name'])) $errors['business_name'] = 'Business Name is required.';
|
||||
if (empty($data['business_phone'])) $errors['business_phone'] = 'Business Phone is required.';
|
||||
if (empty($data['business_email'])) {
|
||||
$errors['business_email'] = 'Business Email is required.';
|
||||
} elseif (!is_email($data['business_email'])) {
|
||||
$errors['business_email'] = 'Invalid Business Email format.';
|
||||
}
|
||||
if (!empty($data['business_website']) && !filter_var($data['business_website'], FILTER_VALIDATE_URL)) $errors['business_website'] = 'Invalid Business Website URL format.';
|
||||
if (empty($data['business_description'])) $errors['business_description'] = 'Business Description is required.';
|
||||
|
||||
// --- Address Information ---
|
||||
if (empty($data['user_country'])) $errors['user_country'] = 'Country is required.';
|
||||
if (empty($data['user_state'])) {
|
||||
$errors['user_state'] = 'State/Province is required.';
|
||||
} elseif ($data['user_state'] === 'Other' && empty($data['user_state_other'])) {
|
||||
$errors['user_state_other'] = 'Please specify your state/province.';
|
||||
}
|
||||
if (empty($data['user_city'])) $errors['user_city'] = 'City is required.';
|
||||
if (empty($data['user_zip'])) $errors['user_zip'] = 'Zip/Postal Code is required.';
|
||||
|
||||
// --- Training Information ---
|
||||
if (empty($data['business_type'])) $errors['business_type'] = 'Business Type is required.';
|
||||
if (empty($data['training_audience'])) $errors['training_audience'] = 'Please select at least one option for Training Audience.';
|
||||
if (empty($data['training_formats'])) $errors['training_formats'] = 'Please select at least one option for Training Formats.';
|
||||
if (empty($data['training_locations'])) $errors['training_locations'] = 'Please select at least one option for Training Locations.';
|
||||
if (empty($data['training_resources'])) $errors['training_resources'] = 'Please select at least one option for Training Resources.';
|
||||
|
||||
// Ensure checkbox/multiselect data is arrays if submitted (even if empty)
|
||||
$array_keys = ['training_audience', 'training_formats', 'training_locations', 'training_resources'];
|
||||
foreach($array_keys as $key) {
|
||||
if (isset($data[$key]) && !is_array($data[$key])) {
|
||||
$errors[$key] = 'Invalid data format for ' . $key . '.';
|
||||
} elseif (!isset($data[$key])) {
|
||||
// If the field is required and not submitted at all (e.g., disabled JS)
|
||||
if (in_array($key, ['training_audience', 'training_formats', 'training_locations', 'training_resources'])) { // Add other required array fields if any
|
||||
$errors[$key] = 'Please select at least one option for ' . str_replace('_', ' ', ucfirst($key)) . '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the user account and meta data.
|
||||
*
|
||||
* @param int $user_id The ID of the user to update.
|
||||
* @param array $data Sanitized submitted form data.
|
||||
* @param array|null $image_data Uploaded file data from $_FILES['profile_image'].
|
||||
* @param bool $delete_image Whether to delete the current profile image.
|
||||
* @return bool|WP_Error True on success, WP_Error on failure.
|
||||
*/
|
||||
private function update_trainer_account($user_id, $data, $image_data, $delete_image) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Entering function for user ID: ' . $user_id);
|
||||
$update_args = array(
|
||||
'ID' => $user_id,
|
||||
);
|
||||
|
||||
// Sanitize and prepare data for wp_update_user
|
||||
if (isset($data['user_email'])) {
|
||||
$update_args['user_email'] = sanitize_email($data['user_email']);
|
||||
}
|
||||
if (!empty($data['user_pass'])) {
|
||||
$update_args['user_pass'] = $data['user_pass']; // wp_update_user handles hashing
|
||||
}
|
||||
if (isset($data['user_url'])) {
|
||||
$update_args['user_url'] = esc_url_raw($data['user_url']);
|
||||
}
|
||||
if (isset($data['display_name'])) {
|
||||
$update_args['display_name'] = sanitize_text_field($data['display_name']);
|
||||
}
|
||||
if (isset($data['first_name'])) {
|
||||
$update_args['first_name'] = sanitize_text_field($data['first_name']);
|
||||
}
|
||||
if (isset($data['last_name'])) {
|
||||
$update_args['last_name'] = sanitize_text_field($data['last_name']);
|
||||
}
|
||||
if (isset($data['description'])) {
|
||||
$update_args['description'] = sanitize_textarea_field($data['description']);
|
||||
}
|
||||
|
||||
// Check current password if email or password is being changed
|
||||
$current_user = get_userdata($user_id);
|
||||
$needs_current_password_check = false;
|
||||
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Checking if password or email update is needed.');
|
||||
// Check if password needs updating
|
||||
if (!empty($data['user_pass'])) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] New password provided.');
|
||||
$needs_current_password_check = true;
|
||||
}
|
||||
// Check if email needs updating
|
||||
if (isset($data['user_email']) && $data['user_email'] !== $current_user->user_email) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Email change detected: ' . $current_user->user_email . ' -> ' . $data['user_email']);
|
||||
$needs_current_password_check = true;
|
||||
}
|
||||
|
||||
// Check current password if needed
|
||||
if ($needs_current_password_check) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Current password check required.');
|
||||
if (empty($data['current_password'])) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Error: Current password missing for email/password update.');
|
||||
$error = new WP_Error('missing_current_password', 'Current password is required to update email or password.');
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Returning WP_Error: ' . $error->get_error_code());
|
||||
return $error;
|
||||
}
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Checking current password...');
|
||||
if (!wp_check_password($data['current_password'], $current_user->user_pass, $user_id)) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Error: Current password check failed for user ID: ' . $user_id);
|
||||
$error = new WP_Error('incorrect_current_password', 'The current password you entered is incorrect.');
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Returning WP_Error: ' . $error->get_error_code());
|
||||
return $error;
|
||||
}
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Current password check successful.');
|
||||
}
|
||||
|
||||
// Update the user core data
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Calling wp_update_user with args: ' . print_r($update_args, true));
|
||||
$result = wp_update_user($update_args);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] wp_update_user failed: ' . $result->get_error_message());
|
||||
$error = new WP_Error('user_update_failed', 'Could not update user information: ' . $result->get_error_message());
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Returning WP_Error: ' . $error->get_error_code());
|
||||
return $error; // Return the modified error object
|
||||
}
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] wp_update_user successful for user ID: ' . $user_id);
|
||||
|
||||
// Update user meta data
|
||||
$meta_to_update = [
|
||||
'user_linkedin' => sanitize_text_field($data['user_linkedin'] ?? ''),
|
||||
'personal_accreditation' => sanitize_text_field($data['personal_accreditation'] ?? ''),
|
||||
'business_name' => sanitize_text_field($data['business_name'] ?? ''),
|
||||
'business_phone' => sanitize_text_field($data['business_phone'] ?? ''),
|
||||
'business_email' => sanitize_email($data['business_email'] ?? ''),
|
||||
'business_website' => esc_url_raw($data['business_website'] ?? ''),
|
||||
'business_description' => sanitize_textarea_field($data['business_description'] ?? ''),
|
||||
'user_country' => sanitize_text_field($data['user_country'] ?? ''),
|
||||
'user_state' => sanitize_text_field($data['user_state'] ?? ''), // Includes 'Other' or selected state
|
||||
'user_state_other' => ($data['user_state'] ?? '') === 'Other' ? sanitize_text_field($data['user_state_other'] ?? '') : '', // Only save if 'Other' is selected
|
||||
'user_city' => sanitize_text_field($data['user_city'] ?? ''),
|
||||
'user_zip' => sanitize_text_field($data['user_zip'] ?? ''),
|
||||
'business_type' => sanitize_text_field($data['business_type'] ?? ''),
|
||||
'training_audience' => $data['training_audience'] ?? [], // Already validated as array or empty
|
||||
'training_formats' => $data['training_formats'] ?? [],
|
||||
'training_locations' => $data['training_locations'] ?? [],
|
||||
'training_resources' => $data['training_resources'] ?? [],
|
||||
];
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Updating user meta: ' . print_r($meta_to_update, true));
|
||||
foreach ($meta_to_update as $key => $value) {
|
||||
update_user_meta($user_id, $key, $value);
|
||||
}
|
||||
|
||||
// Handle profile image upload/deletion
|
||||
if ($delete_image) {
|
||||
$current_image_id = get_user_meta($user_id, 'profile_image_id', true);
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Deleting profile image. Current ID: ' . $current_image_id);
|
||||
if ($current_image_id) {
|
||||
wp_delete_attachment($current_image_id, true);
|
||||
delete_user_meta($user_id, 'profile_image_id');
|
||||
}
|
||||
} elseif (!empty($image_data['tmp_name'])) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Processing profile image upload: ' . print_r($image_data, true));
|
||||
// Need wp-admin/includes/file.php, wp-admin/includes/image.php, wp-admin/includes/media.php
|
||||
require_once(ABSPATH . 'wp-admin/includes/file.php');
|
||||
require_once(ABSPATH . 'wp-admin/includes/image.php');
|
||||
require_once(ABSPATH . 'wp-admin/includes/media.php');
|
||||
|
||||
// Handle the upload
|
||||
// Note: media_handle_upload() expects the file input name ('profile_image' in this case)
|
||||
$attachment_id = media_handle_upload('profile_image', 0); // 0 means no parent post
|
||||
|
||||
if (is_wp_error($attachment_id)) {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Profile image upload failed: ' . $attachment_id->get_error_message());
|
||||
// Optionally return error, or just log it and continue
|
||||
// return new WP_Error('image_upload_failed', 'Could not upload profile image: ' . $attachment_id->get_error_message());
|
||||
} else {
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Profile image uploaded successfully. Attachment ID: ' . $attachment_id);
|
||||
// Delete old image if exists
|
||||
$current_image_id = get_user_meta($user_id, 'profile_image_id', true);
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Deleting old profile image. Current ID: ' . $current_image_id);
|
||||
if ($current_image_id && $current_image_id != $attachment_id) {
|
||||
wp_delete_attachment($current_image_id, true);
|
||||
}
|
||||
// Store new attachment ID
|
||||
update_user_meta($user_id, 'profile_image_id', $attachment_id);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement logic to update linked TEC Organizer/Venue posts if necessary
|
||||
// This might involve fetching the linked venue ID and updating its details based on profile changes.
|
||||
|
||||
error_log('[DEBUG UPDATE_ACCOUNT] Update process completed successfully for user ID: ' . $user_id . '. Returning true.');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of countries.
|
||||
* Could be expanded or loaded from a config/API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_country_list() {
|
||||
return array(
|
||||
'US' => 'United States',
|
||||
'CA' => 'Canada',
|
||||
'GB' => 'United Kingdom',
|
||||
'AU' => 'Australia',
|
||||
// Add more countries as needed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of US states.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_us_states() {
|
||||
return array(
|
||||
'AL' => 'Alabama', 'AK' => 'Alaska', 'AZ' => 'Arizona', 'AR' => 'Arkansas', 'CA' => 'California',
|
||||
'CO' => 'Colorado', 'CT' => 'Connecticut', 'DE' => 'Delaware', 'DC' => 'District Of Columbia', 'FL' => 'Florida',
|
||||
'GA' => 'Georgia', 'HI' => 'Hawaii', 'ID' => 'Idaho', 'IL' => 'Illinois', 'IN' => 'Indiana', 'IA' => 'Iowa',
|
||||
'KS' => 'Kansas', 'KY' => 'Kentucky', 'LA' => 'Louisiana', 'ME' => 'Maine', 'MD' => 'Maryland',
|
||||
'MA' => 'Massachusetts', 'MI' => 'Michigan', 'MN' => 'Minnesota', 'MS' => 'Mississippi', 'MO' => 'Missouri',
|
||||
'MT' => 'Montana', 'NE' => 'Nebraska', 'NV' => 'Nevada', 'NH' => 'New Hampshire', 'NJ' => 'New Jersey',
|
||||
'NM' => 'New Mexico', 'NY' => 'New York', 'NC' => 'North Carolina', 'ND' => 'North Dakota', 'OH' => 'Ohio',
|
||||
'OK' => 'Oklahoma', 'OR' => 'Oregon', 'PA' => 'Pennsylvania', 'RI' => 'Rhode Island', 'SC' => 'South Carolina',
|
||||
'SD' => 'South Dakota', 'TN' => 'Tennessee', 'TX' => 'Texas', 'UT' => 'Utah', 'VT' => 'Vermont',
|
||||
'VA' => 'Virginia', 'WA' => 'Washington', 'WV' => 'West Virginia', 'WI' => 'Wisconsin', 'WY' => 'Wyoming'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Canadian provinces.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_canadian_provinces() {
|
||||
return array(
|
||||
'AB' => 'Alberta', 'BC' => 'British Columbia', 'MB' => 'Manitoba', 'NB' => 'New Brunswick',
|
||||
'NL' => 'Newfoundland and Labrador', 'NS' => 'Nova Scotia', 'ON' => 'Ontario', 'PE' => 'Prince Edward Island',
|
||||
'QC' => 'Quebec', 'SK' => 'Saskatchewan', 'NT' => 'Northwest Territories', 'NU' => 'Nunavut', 'YT' => 'Yukon'
|
||||
);
|
||||
}
|
||||
|
||||
} // End class HVAC_Profile
|
||||
174
hvac-community-events/includes/community/class-login-handler.php
Normal file
174
hvac-community-events/includes/community/class-login-handler.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles the Community Login page functionality.
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace HVAC_Community_Events\Community;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login_Handler Class
|
||||
*/
|
||||
class Login_Handler {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Hooks into WordPress.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_shortcode( 'hvac_community_login', array( $this, 'render_login_form' ) );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); // Enqueue scripts/styles
|
||||
|
||||
// Add action hooks for authentication and redirection (Task 2.2 & 2.5)
|
||||
add_action( 'wp_authenticate', array( $this, 'handle_authentication' ), 30, 2 ); // Allow custom auth checks
|
||||
// REMOVED: add_action( 'login_form_login', array( $this, 'redirect_on_login_failure' ) ); // This was causing premature redirects
|
||||
|
||||
add_action( 'wp_login_failed', array( $this, 'handle_login_failure' ) ); // Handle failed login redirect
|
||||
add_filter( 'login_redirect', array( $this, 'custom_login_redirect' ), 10, 3 ); // Handle success redirect
|
||||
|
||||
// Redirect logged-in users away from the login page
|
||||
add_action( 'template_redirect', array( $this, 'redirect_logged_in_user' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the login form using the custom template.
|
||||
*
|
||||
* @param array $atts Shortcode attributes.
|
||||
* @return string HTML output of the login form.
|
||||
*/
|
||||
public function render_login_form( $atts ) {
|
||||
// Logged-in user check and redirect moved to redirect_logged_in_user() hooked to template_redirect
|
||||
|
||||
// Start output buffering to capture the template output.
|
||||
ob_start();
|
||||
|
||||
// Check for login errors passed via query parameters
|
||||
if ( isset( $_GET['login'] ) && $_GET['login'] === 'failed' ) {
|
||||
// You might want to use a more user-friendly message or integrate with theme notices
|
||||
echo '<div class="hvac-login-error" style="color: red; border: 1px solid red; padding: 10px; margin-bottom: 15px;">' . esc_html__( 'Invalid username or password.', 'hvac-community-events' ) . '</div>';
|
||||
}
|
||||
|
||||
// Define variables needed by the template (if any)
|
||||
// $caption = __( 'Please log in to access the trainer area.', 'hvac-community-events' );
|
||||
|
||||
// Include the custom login form template.
|
||||
// Use a helper function to locate the template, allowing theme overrides.
|
||||
$template_path = HVAC_CE_PLUGIN_DIR . 'templates/community/login-form.php'; // Correct constant name
|
||||
if ( file_exists( $template_path ) ) {
|
||||
include $template_path;
|
||||
} else {
|
||||
// Fallback or error message if template is missing
|
||||
echo '<p>Error: Login form template not found.</p>';
|
||||
}
|
||||
|
||||
// Return the buffered content.
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues scripts and styles for the login page.
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
global $post;
|
||||
|
||||
// Only enqueue if the shortcode is present on the current page.
|
||||
if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'hvac_community_login' ) ) {
|
||||
wp_enqueue_style(
|
||||
'hvac-community-login-style',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/css/community-login.css',
|
||||
array(), // Add dependencies like theme stylesheet if needed
|
||||
HVAC_CE_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles custom authentication logic (if needed).
|
||||
* Placeholder for Task 2.2.
|
||||
*
|
||||
* @param string $username Username or email address.
|
||||
* @param string $password Password.
|
||||
*/
|
||||
public function handle_authentication( &$username, &$password ) {
|
||||
// Custom validation or checks can go here.
|
||||
// For now, rely on default WordPress authentication.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles redirecting the user back to the custom login page on authentication failure.
|
||||
*
|
||||
* Hooked to 'wp_login_failed'.
|
||||
*/
|
||||
public function handle_login_failure() {
|
||||
// Check if the request originated from our custom login page.
|
||||
// This prevents interference with the standard wp-login.php flow if accessed directly.
|
||||
$referrer = wp_get_referer();
|
||||
$login_page_slug = 'community-login'; // The slug of your custom login page
|
||||
|
||||
if ( $referrer && strpos( $referrer, $login_page_slug ) !== false ) {
|
||||
$login_page_url = home_url( '/' . $login_page_slug . '/' );
|
||||
// Redirect back to the custom login page with a failure flag.
|
||||
wp_safe_redirect( add_query_arg( 'login', 'failed', $login_page_url ) );
|
||||
exit;
|
||||
}
|
||||
// If not referred from our custom login page, let WordPress handle the failure (usually redisplays wp-login.php).
|
||||
}
|
||||
|
||||
// REMOVED: Unnecessary redirect_on_login_failure method.
|
||||
// WordPress handles redirecting back to the referring page (our custom login page)
|
||||
// on authentication failure automatically when using wp_login_form().
|
||||
// The 'login_redirect' filter handles the success case.
|
||||
|
||||
/**
|
||||
* Custom redirect logic after successful login.
|
||||
* Placeholder for Task 2.5.
|
||||
* Filters the login redirect URL based on user role.
|
||||
*
|
||||
* @param string $redirect_to The redirect destination URL.
|
||||
* @param string $requested_redirect_to The requested redirect destination URL (if provided).
|
||||
* @param WP_User|WP_Error $user WP_User object if login successful, WP_Error object otherwise.
|
||||
* @return string Redirect URL.
|
||||
*/
|
||||
public function custom_login_redirect( $redirect_to, $requested_redirect_to, $user ) {
|
||||
// Check if login was successful and user is not an error object
|
||||
if ( $user && ! is_wp_error( $user ) ) {
|
||||
// Check if the user has the 'hvac_trainer' role
|
||||
if ( in_array( 'hvac_trainer', (array) $user->roles ) ) {
|
||||
// Redirect HVAC trainers to their dashboard
|
||||
// Assumes dashboard page slug is 'hvac-dashboard'. Adjust if needed.
|
||||
$dashboard_url = home_url( '/hvac-dashboard/' );
|
||||
return $dashboard_url;
|
||||
} else {
|
||||
// For other roles (like admin), redirect to the standard WP admin dashboard.
|
||||
// If $requested_redirect_to is set (e.g., trying to access a specific admin page), respect it.
|
||||
return $requested_redirect_to ? $requested_redirect_to : admin_url();
|
||||
}
|
||||
}
|
||||
|
||||
// If login failed ($user is WP_Error), return the default $redirect_to.
|
||||
// Our redirect_on_login_failure should ideally catch this first, but this is a fallback.
|
||||
return $redirect_to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects logged-in users away from the custom login page.
|
||||
* Hooked to 'template_redirect'.
|
||||
*/
|
||||
public function redirect_logged_in_user() {
|
||||
// Check if we are on the custom login page (adjust slug if needed)
|
||||
if ( is_page( 'community-login' ) && is_user_logged_in() ) {
|
||||
// Redirect logged-in users to the dashboard
|
||||
$dashboard_url = home_url( '/hvac-dashboard/' );
|
||||
wp_safe_redirect( $dashboard_url );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
67
hvac-community-events/templates/community/login-form.php
Normal file
67
hvac-community-events/templates/community/login-form.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Community Events: Custom Login Form Template
|
||||
*
|
||||
* This template provides the structure for the custom login page,
|
||||
* integrating with the Astra theme's styling.
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// Get Astra theme layout settings if needed, e.g., container width
|
||||
// $container_class = astra_get_content_layout(); // Example
|
||||
|
||||
?>
|
||||
|
||||
<div class="hvac-community-login-wrapper"> <?php // Custom wrapper for potential styling ?>
|
||||
<div class="ast-container"> <?php // Astra theme container ?>
|
||||
<div class="hvac-login-form-card"> <?php // Card styling based on design reference ?>
|
||||
|
||||
<?php
|
||||
// Display login errors if any
|
||||
// Example: Check for login errors passed via query parameters or session
|
||||
// if ( isset( $_GET['login'] ) && $_GET['login'] === 'failed' ) {
|
||||
// echo '<p class="login-error">Invalid username or password.</p>';
|
||||
// }
|
||||
?>
|
||||
|
||||
<?php
|
||||
// Arguments for wp_login_form
|
||||
$args = array(
|
||||
'echo' => true,
|
||||
// 'redirect' is handled by the 'login_redirect' filter in Login_Handler class
|
||||
'form_id' => 'hvac_community_loginform',
|
||||
'label_username' => __( 'Username or Email Address', 'hvac-community-events' ),
|
||||
'label_password' => __( 'Password', 'hvac-community-events' ),
|
||||
'label_remember' => __( 'Remember Me', 'hvac-community-events' ), // Task 2.3
|
||||
'label_log_in' => __( 'Log In', 'hvac-community-events' ),
|
||||
'id_username' => 'user_login',
|
||||
'id_password' => 'user_pass',
|
||||
'id_remember' => 'rememberme',
|
||||
'id_submit' => 'wp-submit',
|
||||
'remember' => true, // Task 2.3
|
||||
'value_username' => '',
|
||||
'value_remember' => false, // Set to true to default the checkbox to checked
|
||||
);
|
||||
|
||||
wp_login_form( $args );
|
||||
?>
|
||||
|
||||
<div class="hvac-login-links">
|
||||
<?php if ( get_option( 'users_can_register' ) ) : ?>
|
||||
<a class="hvac-register-link" href="<?php echo esc_url( wp_registration_url() ); ?>">
|
||||
<?php esc_html_e( 'Register', 'hvac-community-events' ); ?>
|
||||
</a> |
|
||||
<?php endif; ?>
|
||||
<a class="hvac-lostpassword-link" href="<?php echo esc_url( wp_lostpassword_url() ); ?>">
|
||||
<?php esc_html_e( 'Lost your password?', 'hvac-community-events' ); // Task 2.4 ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div> <?php // .hvac-login-form-card ?>
|
||||
</div> <?php // .ast-container ?>
|
||||
</div> <?php // .hvac-community-login-wrapper ?>
|
||||
222
hvac-community-events/templates/single-hvac-event-summary.php
Normal file
222
hvac-community-events/templates/single-hvac-event-summary.php
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
/**
|
||||
* Template for displaying single HVAC Event Summary.
|
||||
*
|
||||
* This template overrides the default single event template provided by The Events Calendar
|
||||
* when viewed by users with appropriate permissions (or potentially all users, depending on requirements).
|
||||
* It leverages the Astra theme structure where possible.
|
||||
*
|
||||
* Design Reference: design_references/upskillhvac.com_hce-event-summary__event_id=1662 (1).png
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// Ensure the data class is available
|
||||
if ( ! class_exists( 'HVAC_Event_Summary_Data' ) ) {
|
||||
// Attempt to include it if not loaded - adjust path as needed
|
||||
$class_path = plugin_dir_path( __FILE__ ) . '../includes/community/class-event-summary-data.php';
|
||||
if ( file_exists( $class_path ) ) {
|
||||
require_once $class_path;
|
||||
} else {
|
||||
// Handle error: Class not found, cannot display summary
|
||||
echo "<p>Error: Event Summary data handler not found.</p>";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get_header();
|
||||
|
||||
?>
|
||||
|
||||
<div id="primary" <?php astra_primary_class(); ?>>
|
||||
|
||||
<?php astra_primary_content_top(); ?>
|
||||
|
||||
<?php astra_content_loop(); // This typically includes the have_posts() and the_post() loop ?>
|
||||
|
||||
<?php
|
||||
// Ensure we are inside the loop and it's the correct post type
|
||||
if ( have_posts() && get_post_type() === Tribe__Events__Main::POSTTYPE ) {
|
||||
the_post();
|
||||
|
||||
$event_id = get_the_ID();
|
||||
$summary_data_handler = new HVAC_Event_Summary_Data( $event_id );
|
||||
|
||||
if ( $summary_data_handler->is_valid_event() ) {
|
||||
$event_details = $summary_data_handler->get_event_details();
|
||||
$venue_details = $summary_data_handler->get_event_venue_details();
|
||||
$organizer_details = $summary_data_handler->get_event_organizer_details();
|
||||
$transactions = $summary_data_handler->get_event_transactions();
|
||||
?>
|
||||
|
||||
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
|
||||
|
||||
<header class="entry-header <?php astra_entry_header_class(); ?>">
|
||||
<!-- Task 5.3: Implement breadcrumb navigation using theme's breadcrumb component -->
|
||||
<?php
|
||||
// Check if Astra breadcrumb function exists and call it
|
||||
if ( function_exists( 'astra_get_breadcrumb' ) ) {
|
||||
astra_get_breadcrumb();
|
||||
} else {
|
||||
// Fallback or alternative breadcrumb can be added here if needed
|
||||
echo '<!-- Breadcrumb not available -->';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
|
||||
|
||||
<!-- Add Edit Event Button (Task 5.6) - Conditionally shown for trainer -->
|
||||
<?php
|
||||
// Check if the current user can edit this specific event post
|
||||
// Using 'edit_post' capability for now, might need refinement to a custom cap later
|
||||
if ( current_user_can( 'edit_post', $event_id ) ) {
|
||||
// Get the URL of the 'manage-event' page
|
||||
$manage_event_page = get_page_by_path( 'manage-event' );
|
||||
if ( $manage_event_page ) {
|
||||
$edit_url = get_permalink( $manage_event_page->ID );
|
||||
$edit_url = add_query_arg( 'event_id', $event_id, $edit_url );
|
||||
// Apply Astra button classes
|
||||
printf(
|
||||
'<a href="%s" class="button astra-button">%s</a>',
|
||||
esc_url( $edit_url ),
|
||||
esc_html__( 'Edit Event', 'hvac-community-events' )
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- View Public Page Button -->
|
||||
<a href="<?php echo esc_url( get_permalink($event_id) ); ?>" class="button astra-button" target="_blank" style="margin-left: 1em;">View Public Event Page</a>
|
||||
|
||||
<!-- Email Attendees Button (Phase 2) -->
|
||||
<?php
|
||||
// TODO: Add capability check for emailing attendees (e.g., 'email_hvac_attendees')
|
||||
$can_email = current_user_can( 'edit_post', $event_id ); // Placeholder: Use edit cap for now
|
||||
if ( $can_email ) {
|
||||
// TODO: Link to actual Email Attendees page (Phase 2)
|
||||
$email_attendees_url = '#'; // Placeholder URL
|
||||
printf(
|
||||
'<a href="%s" class="button astra-button" style="margin-left: 1em;">%s</a>',
|
||||
esc_url( $email_attendees_url ),
|
||||
esc_html__( 'Email Attendees', 'hvac-community-events' )
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
</header> <!-- .entry-header -->
|
||||
|
||||
<div class="entry-content clear" <?php astra_schema_e( 'text' ); ?>>
|
||||
|
||||
<?php astra_entry_content_before(); ?>
|
||||
|
||||
<!-- Task 5.2 & 5.4: Display Event Details in theme-styled card sections / Format content -->
|
||||
<div class="hvac-event-summary-details">
|
||||
<h2>Event Details</h2>
|
||||
<?php if ( $event_details ) { ?>
|
||||
<p><strong>Date:</strong> <?php
|
||||
if ( function_exists( 'tribe_events_event_schedule_details' ) ) {
|
||||
echo tribe_events_event_schedule_details( $event_id );
|
||||
} else {
|
||||
// Fallback display if function doesn't exist
|
||||
echo esc_html( $event_details['start_date'] ?? 'N/A' ) . ' - ' . esc_html( $event_details['end_date'] ?? 'N/A' );
|
||||
}
|
||||
?></p>
|
||||
<p><strong>Cost:</strong> <?php echo esc_html( $event_details['cost'] ?? 'N/A' ); ?></p>
|
||||
<div class="event-description">
|
||||
<?php echo wp_kses_post( $event_details['description'] ); ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ( $venue_details ) { ?>
|
||||
<h3>Venue</h3>
|
||||
<p><strong>Name:</strong> <?php echo esc_html( $venue_details['name'] ); ?></p>
|
||||
<?php if ( ! empty( $venue_details['address'] ) ) : ?>
|
||||
<p><strong>Address:</strong> <?php echo esc_html( $venue_details['address'] ); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $venue_details['phone'] ) ) : ?>
|
||||
<p><strong>Phone:</strong> <?php echo esc_html( $venue_details['phone'] ); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $venue_details['website'] ) ) : ?>
|
||||
<p><strong>Website:</strong> <?php echo wp_kses_post( $venue_details['website'] ); // Allow link HTML ?></p>
|
||||
<?php endif; ?>
|
||||
<?php // TODO: Add Map Link / Directions Link if needed ?>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ( $organizer_details ) { ?>
|
||||
<h3>Organizer</h3>
|
||||
<p><strong>Name:</strong> <?php echo esc_html( $organizer_details['name'] ); ?></p>
|
||||
<?php if ( ! empty( $organizer_details['phone'] ) ) : ?>
|
||||
<p><strong>Phone:</strong> <?php echo esc_html( $organizer_details['phone'] ); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $organizer_details['email'] ) ) : ?>
|
||||
<p><strong>Email:</strong> <a href="mailto:<?php echo esc_attr( $organizer_details['email'] ); ?>"><?php echo esc_html( $organizer_details['email'] ); ?></a></p>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $organizer_details['website'] ) ) : ?>
|
||||
<p><strong>Website:</strong> <?php echo wp_kses_post( $organizer_details['website'] ); // Allow link HTML ?></p>
|
||||
<?php endif; ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<!-- Task 5.5: Implement Transactions Table using theme's table styling -->
|
||||
<div class="hvac-event-summary-transactions">
|
||||
<h2>Transactions / Attendees</h2>
|
||||
<?php if ( ! empty( $transactions ) ) { ?>
|
||||
<table class="hvac-transactions-table astra-table-cls"> <!-- Add theme table class -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attendee Name</th>
|
||||
<th>Email</th>
|
||||
<th>Ticket Type</th>
|
||||
<th>Order ID</th>
|
||||
<th>Checked In</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $transactions as $txn ) { ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html( $txn['purchaser_name'] ?? 'N/A' ); ?></td>
|
||||
<td><?php echo esc_html( $txn['purchaser_email'] ?? 'N/A' ); ?></td>
|
||||
<td><?php echo esc_html( $txn['ticket_type_name'] ?? 'N/A' ); ?></td>
|
||||
<td><?php echo esc_html( $txn['order_id'] ?? 'N/A' ); ?></td>
|
||||
<td><?php echo $txn['checked_in'] ? 'Yes' : 'No'; ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php } else { ?>
|
||||
<p>No transactions found for this event.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php wp_link_pages( /* ... */ ); ?>
|
||||
|
||||
<?php astra_entry_content_after(); ?>
|
||||
|
||||
</div><!-- .entry-content -->
|
||||
|
||||
</article><!-- #post-<?php the_ID(); ?> -->
|
||||
|
||||
<?php
|
||||
} else {
|
||||
// Handle case where event data couldn't be loaded
|
||||
echo '<p>Could not load event summary data.</p>';
|
||||
}
|
||||
|
||||
} else {
|
||||
// Handle case where it's not a tribe_events post or no posts found
|
||||
astra_content_page_loop(); // Fallback to default page loop? Or show error.
|
||||
echo '<p>Event not found or invalid post type.</p>';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php astra_primary_content_bottom(); ?>
|
||||
|
||||
</div><!-- #primary -->
|
||||
|
||||
<?php get_sidebar(); ?>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
220
hvac-community-events/templates/template-hvac-dashboard.php
Normal file
220
hvac-community-events/templates/template-hvac-dashboard.php
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: HVAC Trainer Dashboard
|
||||
*
|
||||
* This template handles the display of the HVAC Trainer Dashboard.
|
||||
* It checks for user login and role, then displays stats and events.
|
||||
*
|
||||
* @package HVAC Community Events
|
||||
* @subpackage Templates
|
||||
* @author Roo
|
||||
* @version 1.0.1
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- Security Check & Data Loading ---
|
||||
|
||||
// Ensure user is logged in and has the correct role
|
||||
if ( ! is_user_logged_in() || ! current_user_can( 'view_hvac_dashboard' ) ) {
|
||||
// Redirect to login page or show an error message
|
||||
wp_safe_redirect( home_url( '/community-login/' ) ); // Redirect to the custom login page
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get the current user ID
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Include and instantiate the dashboard data class
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
|
||||
$dashboard_data = new HVAC_Dashboard_Data( $user_id );
|
||||
|
||||
// Fetch data
|
||||
$total_events = $dashboard_data->get_total_events_count();
|
||||
$upcoming_events = $dashboard_data->get_upcoming_events_count();
|
||||
$past_events = $dashboard_data->get_past_events_count();
|
||||
$total_sold = $dashboard_data->get_total_tickets_sold();
|
||||
$total_revenue = $dashboard_data->get_total_revenue();
|
||||
$revenue_target = $dashboard_data->get_annual_revenue_target();
|
||||
|
||||
// --- Template Start ---
|
||||
|
||||
get_header(); // Use theme's header
|
||||
|
||||
?>
|
||||
<div id="primary" class="content-area primary ast-container"> <!-- Use Astra container -->
|
||||
<main id="main" class="site-main">
|
||||
|
||||
<!-- Dashboard Header & Navigation -->
|
||||
<div class="hvac-dashboard-header ast-flex ast-justify-content-space-between ast-align-items-center">
|
||||
<h1 class="entry-title">Trainer Dashboard</h1> <!-- Standard WP title class -->
|
||||
<div class="hvac-dashboard-nav">
|
||||
<?php // TODO: Add icons to buttons via CSS ?>
|
||||
<a href="<?php echo esc_url( home_url( '/trainer-profile/' ) ); ?>" class="ast-button ast-button-primary">Edit Profile</a> <?php // TODO: Implement trainer profile page ?>
|
||||
<a href="<?php echo esc_url( home_url( '/manage-event/' ) ); ?>" class="ast-button ast-button-primary">Create Event</a>
|
||||
<a href="<?php echo esc_url( wp_logout_url( home_url( '/community-login/' ) ) ); ?>" class="ast-button ast-button-primary">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Section -->
|
||||
<section class="hvac-dashboard-stats">
|
||||
<h2 class="section-title">Your Stats</h2>
|
||||
<div class="ast-row hvac-stats-grid"> <!-- Use Astra grid row - Add custom class for 5 columns -->
|
||||
|
||||
<!-- Stat Card: Total Events -->
|
||||
<div class="ast-col"> <!-- Use default col for flex/custom grid -->
|
||||
<div class="hvac-stat-card">
|
||||
<?php // TODO: Add icon via CSS ?>
|
||||
<span class="stat-value"><?php echo esc_html( $total_events ); ?></span>
|
||||
<span class="stat-label">Total Events</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stat Card: Upcoming Events -->
|
||||
<div class="ast-col">
|
||||
<div class="hvac-stat-card">
|
||||
<?php // TODO: Add icon via CSS ?>
|
||||
<span class="stat-value"><?php echo esc_html( $upcoming_events ); ?></span>
|
||||
<span class="stat-label">Upcoming Events</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stat Card: Past Events -->
|
||||
<div class="ast-col">
|
||||
<div class="hvac-stat-card">
|
||||
<?php // TODO: Add icon via CSS ?>
|
||||
<span class="stat-value"><?php echo esc_html( $past_events ); ?></span>
|
||||
<span class="stat-label">Past Events</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stat Card: Total Tickets Sold -->
|
||||
<div class="ast-col">
|
||||
<div class="hvac-stat-card">
|
||||
<?php // TODO: Add icon via CSS ?>
|
||||
<span class="stat-value"><?php echo esc_html( $total_sold ); ?></span>
|
||||
<span class="stat-label">Tickets Sold</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stat Card: Total Revenue -->
|
||||
<div class="ast-col">
|
||||
<div class="hvac-stat-card">
|
||||
<?php // TODO: Add icon via CSS ?>
|
||||
<span class="stat-value">$<?php echo esc_html( number_format( $total_revenue, 2 ) ); ?></span>
|
||||
<span class="stat-label">Total Revenue</span>
|
||||
<?php // Note: Target revenue not shown in desired design ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /.ast-row -->
|
||||
</section>
|
||||
|
||||
<!-- Events Table Section -->
|
||||
<section class="hvac-dashboard-events">
|
||||
<h2 class="section-title">Your Events</h2>
|
||||
|
||||
<!-- Tab Filters -->
|
||||
<?php
|
||||
// --- Event Filters (Tabs) ---
|
||||
$dashboard_url = get_permalink(); // Get the current page URL
|
||||
$current_filter = isset( $_GET['event_time'] ) ? sanitize_key( $_GET['event_time'] ) : 'all'; // Change query param
|
||||
$filter_tabs = [ // Change filter options
|
||||
'all' => 'All Events',
|
||||
'upcoming' => 'Upcoming',
|
||||
'past' => 'Past'
|
||||
];
|
||||
?>
|
||||
<div class="hvac-event-tabs">
|
||||
<ul class="hvac-tabs-nav">
|
||||
<?php foreach ($filter_tabs as $key => $label) :
|
||||
$url = add_query_arg( 'event_time', $key, $dashboard_url );
|
||||
$class = 'hvac-tab-link';
|
||||
if ($key === $current_filter) {
|
||||
$class .= ' active'; // Add active class
|
||||
}
|
||||
// Special case for 'all' filter link
|
||||
if ($key === 'all') {
|
||||
$url = remove_query_arg( 'event_time', $dashboard_url );
|
||||
}
|
||||
?>
|
||||
<li><a href="<?php echo esc_url( $url ); ?>" class="<?php echo esc_attr( $class ); ?>"><?php echo esc_html( $label ); ?></a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Events Table -->
|
||||
<?php
|
||||
// Fetch events based on the new time filter ('all', 'upcoming', 'past')
|
||||
// Note: get_events_table_data likely needs modification to handle time filtering instead of status
|
||||
$events = $dashboard_data->get_events_table_data( $current_filter ); // TODO: Update HVAC_Dashboard_Data::get_events_table_data to accept 'all', 'upcoming', 'past'
|
||||
?>
|
||||
|
||||
<div class="hvac-events-table-wrapper">
|
||||
<table class="wp-list-table widefat fixed striped events-table hvac-events-table"> <!-- Add custom class for styling -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="manage-column column-status">Status</th>
|
||||
<th scope="col" class="manage-column column-title">Event Name</th>
|
||||
<th scope="col" class="manage-column column-date">Date</th>
|
||||
<th scope="col" class="manage-column column-organizer">Organizer</th>
|
||||
<th scope="col" class="manage-column column-capacity">Capacity</th>
|
||||
<th scope="col" class="manage-column column-sold">Sold</th>
|
||||
<th scope="col" class="manage-column column-revenue">Revenue</th>
|
||||
<th scope="col" class="manage-column column-actions">Actions</th> <!-- Added Actions Column -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="the-list">
|
||||
<?php if ( ! empty( $events ) ) : ?>
|
||||
<?php foreach ( $events as $event ) : ?>
|
||||
<tr>
|
||||
<td class="column-status"><?php echo esc_html( ucfirst( $event['status'] ) ); ?></td>
|
||||
<td class="column-title">
|
||||
<strong><a href="<?php echo esc_url( $event['link'] ); ?>" target="_blank"><?php echo esc_html( $event['name'] ); ?></a></strong>
|
||||
<!-- Add Edit/View links below title if needed -->
|
||||
</td>
|
||||
<td class="column-date"><?php echo esc_html( date( 'Y-m-d H:i', $event['start_date_ts'] ) ); ?></td>
|
||||
<td class="column-organizer"><?php
|
||||
// Check if tribe_get_organizer function exists before calling
|
||||
if ( function_exists( 'tribe_get_organizer' ) ) {
|
||||
echo esc_html( tribe_get_organizer( $event['organizer_id'] ) );
|
||||
} else {
|
||||
echo 'Organizer ID: ' . esc_html( $event['organizer_id'] ); // Fallback
|
||||
}
|
||||
?></td>
|
||||
<td class="column-capacity"><?php echo esc_html( $event['capacity'] ); ?></td>
|
||||
<td class="column-sold"><?php echo esc_html( $event['sold'] ); ?></td>
|
||||
<td class="column-revenue">$<?php echo esc_html( number_format( $event['revenue'], 2 ) ); ?></td>
|
||||
<td class="column-actions">
|
||||
<?php
|
||||
// Link to the page containing the TEC CE submission form shortcode
|
||||
$edit_url = add_query_arg( 'event_id', $event['id'], home_url( '/manage-event/' ) );
|
||||
// Link to the custom event summary page
|
||||
$summary_url = get_permalink( $event['id'] ); // Assumes custom template is loaded for event post type
|
||||
?>
|
||||
<?php // TODO: Add icons via CSS ?>
|
||||
<a href="<?php echo esc_url( $edit_url ); ?>" class="ast-button ast-button-primary button-small">Edit</a>
|
||||
<a href="<?php echo esc_url( $summary_url ); ?>" class="ast-button ast-button-primary button-small">View Summary</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="8">No events found.</td> <!-- Updated colspan -->
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</main> <!-- #main -->
|
||||
</div> <!-- #primary -->
|
||||
|
||||
<?php
|
||||
get_footer(); // Use theme's footer
|
||||
?>
|
||||
Loading…
Reference in a new issue