- Add comprehensive Training Leads system for HVAC trainers * New /trainer/training-leads/ page with tabular contact submission display * HVAC_Training_Leads class with AJAX status updates and filtering * Empty state messaging and profile sharing CTA * Database integration with existing contact forms system - Restructure trainer navigation menu for better UX * Rename "Customize" to "Profile" with logical groupings * Move "Logout" under "Profile" submenu * Change "Personal Profile" to "Trainer Profile" * Add "Training Leads" under Profile section * Update help menu to show only question mark icon positioned far right - Enhance documentation system * Fix /trainer/documentation/ page styling and navigation integration * Update content to reflect current platform features * Add Training Leads documentation and navigation guide * Implement proper WordPress template structure - Update user management * Change joe@upskillhvac.com display name to "Joe Medosch" * Assign Joe as author of measureQuick headquarters venue * Assign Joe as author of measureQuick and Upskill HVAC organizers 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
337 lines
No EOL
12 KiB
PHP
337 lines
No EOL
12 KiB
PHP
<?php
|
|
/**
|
|
* HVAC QR Code Generator
|
|
*
|
|
* Generates QR codes for trainer profile sharing using Google Charts API
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* HVAC QR Code Generator Class
|
|
*/
|
|
class HVAC_QR_Generator {
|
|
|
|
/**
|
|
* Instance
|
|
*
|
|
* @var HVAC_QR_Generator
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize WordPress hooks
|
|
*/
|
|
private function init_hooks() {
|
|
// AJAX handlers for profile sharing
|
|
add_action('wp_ajax_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']);
|
|
add_action('wp_ajax_nopriv_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']);
|
|
|
|
// URL rewrite rules for direct profile access
|
|
add_action('init', [$this, 'add_profile_rewrite_rules']);
|
|
add_filter('query_vars', [$this, 'add_profile_query_vars']);
|
|
}
|
|
|
|
/**
|
|
* Generate QR code URL using QR Server API
|
|
*
|
|
* @param string $data Data to encode in QR code
|
|
* @param int $size QR code size in pixels (default: 200)
|
|
* @param string $error_correction Error correction level (L, M, Q, H)
|
|
* @return string QR code image URL
|
|
*/
|
|
public function generate_qr_url($data, $size = 200, $error_correction = 'M') {
|
|
// Encode the data for URL
|
|
$encoded_data = urlencode($data);
|
|
|
|
// QR Server API URL (free and reliable alternative to Google Charts)
|
|
$qr_url = sprintf(
|
|
'https://api.qrserver.com/v1/create-qr-code/?size=%dx%d&data=%s&ecc=%s',
|
|
$size,
|
|
$size,
|
|
$encoded_data,
|
|
strtoupper($error_correction)
|
|
);
|
|
|
|
return $qr_url;
|
|
}
|
|
|
|
/**
|
|
* Generate QR code for trainer profile
|
|
*
|
|
* @param int $profile_id Trainer profile ID
|
|
* @param int $size QR code size in pixels
|
|
* @return string|false QR code URL or false on error
|
|
*/
|
|
public function generate_trainer_profile_qr($profile_id, $size = 200) {
|
|
if (!$profile_id) {
|
|
return false;
|
|
}
|
|
|
|
// Generate the profile URL
|
|
$profile_url = $this->get_trainer_profile_share_url($profile_id);
|
|
|
|
if (!$profile_url) {
|
|
return false;
|
|
}
|
|
|
|
return $this->generate_qr_url($profile_url, $size);
|
|
}
|
|
|
|
/**
|
|
* Get shareable trainer profile URL
|
|
*
|
|
* @param int $profile_id Trainer profile ID
|
|
* @return string|false Profile URL or false on error
|
|
*/
|
|
public function get_trainer_profile_share_url($profile_id) {
|
|
if (!$profile_id) {
|
|
return false;
|
|
}
|
|
|
|
// Get the Find a Trainer page URL
|
|
$find_trainer_page = get_page_by_path('find-a-trainer');
|
|
if (!$find_trainer_page) {
|
|
return false;
|
|
}
|
|
|
|
$base_url = get_permalink($find_trainer_page->ID);
|
|
|
|
// Create the profile-specific URL (with trailing slash for WordPress rewrite rules)
|
|
$profile_url = trailingslashit($base_url) . 'profile/' . $profile_id . '/';
|
|
|
|
return $profile_url;
|
|
}
|
|
|
|
/**
|
|
* Get trainer profile data for sharing
|
|
*
|
|
* @param int $profile_id Trainer profile ID
|
|
* @return array|false Profile data or false on error
|
|
*/
|
|
public function get_trainer_share_data($profile_id) {
|
|
if (!$profile_id) {
|
|
return false;
|
|
}
|
|
|
|
// Get the profile post
|
|
$profile = get_post($profile_id);
|
|
if (!$profile || $profile->post_type !== 'trainer_profile') {
|
|
return false;
|
|
}
|
|
|
|
// Get profile metadata
|
|
$user_id = get_post_meta($profile_id, 'user_id', true);
|
|
if (!$user_id) {
|
|
return false;
|
|
}
|
|
|
|
// Get user data
|
|
$user = get_userdata($user_id);
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
// Compile share data
|
|
$share_data = [
|
|
'profile_id' => $profile_id,
|
|
'user_id' => $user_id,
|
|
'trainer_name' => get_post_meta($profile_id, 'trainer_display_name', true) ?: $user->display_name,
|
|
'business_name' => get_user_meta($user_id, 'business_name', true),
|
|
'trainer_city' => get_post_meta($profile_id, 'trainer_city', true),
|
|
'trainer_state' => get_post_meta($profile_id, 'trainer_state', true),
|
|
'certification_type' => get_post_meta($profile_id, 'certification_type', true),
|
|
'profile_image' => get_post_meta($profile_id, 'profile_image_url', true),
|
|
'share_url' => $this->get_trainer_profile_share_url($profile_id),
|
|
'qr_code_url' => $this->generate_trainer_profile_qr($profile_id, 200)
|
|
];
|
|
|
|
return $share_data;
|
|
}
|
|
|
|
/**
|
|
* Parse trainer profile ID from URL
|
|
*
|
|
* @param string $url URL to parse
|
|
* @return int|false Profile ID or false if not found
|
|
*/
|
|
public function parse_profile_id_from_url($url = null) {
|
|
// First check if we have a query variable (from rewrite rule)
|
|
$profile_id = get_query_var('trainer_profile_id');
|
|
if ($profile_id) {
|
|
return intval($profile_id);
|
|
}
|
|
|
|
// Fallback to URL parsing
|
|
if (!$url) {
|
|
$url = $_SERVER['REQUEST_URI'];
|
|
}
|
|
|
|
// Check if URL matches pattern: /find-a-trainer/profile/{profile_id}
|
|
if (preg_match('/\/find-a-trainer\/profile\/(\d+)\/?/', $url, $matches)) {
|
|
return intval($matches[1]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generate profile card HTML for sharing
|
|
*
|
|
* @param int $profile_id Trainer profile ID
|
|
* @param array $options Display options
|
|
* @return string|false HTML content or false on error
|
|
*/
|
|
public function generate_profile_card_html($profile_id, $options = []) {
|
|
$share_data = $this->get_trainer_share_data($profile_id);
|
|
if (!$share_data) {
|
|
return false;
|
|
}
|
|
|
|
// Default options
|
|
$options = wp_parse_args($options, [
|
|
'show_qr' => true,
|
|
'qr_size' => 150,
|
|
'card_width' => 600,
|
|
'card_height' => 300
|
|
]);
|
|
|
|
$qr_url = $options['show_qr'] ? $this->generate_trainer_profile_qr($profile_id, $options['qr_size']) : '';
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-share-profile-card" style="width: <?php echo esc_attr($options['card_width']); ?>px; height: <?php echo esc_attr($options['card_height']); ?>px; border: 2px solid #e0e0e0; border-radius: 12px; padding: 20px; background: #fff; display: flex; align-items: center; gap: 20px; font-family: Arial, sans-serif;">
|
|
|
|
<!-- Profile Image -->
|
|
<div class="hvac-share-avatar" style="width: 120px; height: 120px; flex-shrink: 0; position: relative;">
|
|
<?php if (!empty($share_data['profile_image'])): ?>
|
|
<img src="<?php echo esc_url($share_data['profile_image']); ?>"
|
|
alt="<?php echo esc_attr($share_data['trainer_name']); ?>"
|
|
style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%; background: #ddd;">
|
|
<?php else: ?>
|
|
<div style="width: 100%; height: 100%; background: #6c757d; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 40px; font-weight: bold;">
|
|
<?php echo esc_html(substr($share_data['trainer_name'], 0, 1)); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- mQ Certified Badge -->
|
|
<?php if ($share_data['certification_type'] === 'Certified measureQuick Trainer'): ?>
|
|
<div style="position: absolute; top: -5px; right: -5px; width: 35px; height: 35px;">
|
|
<img src="/wp-content/uploads/2025/08/mQ-Certified-trainer.png"
|
|
alt="measureQuick Certified Trainer"
|
|
style="width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));">
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Profile Details -->
|
|
<div class="hvac-share-details" style="flex: 1; min-width: 0;">
|
|
<h2 style="margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #333;">
|
|
<?php echo esc_html($share_data['trainer_name']); ?>
|
|
</h2>
|
|
|
|
<?php if (!empty($share_data['business_name'])): ?>
|
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #666; font-weight: 500;">
|
|
<?php echo esc_html($share_data['business_name']); ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
|
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #666;">
|
|
<?php echo esc_html($share_data['trainer_city'] . ', ' . $share_data['trainer_state']); ?>
|
|
</p>
|
|
|
|
<?php if (!empty($share_data['certification_type'])): ?>
|
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #0073aa; font-weight: 500;">
|
|
<?php echo esc_html($share_data['certification_type']); ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- QR Code -->
|
|
<?php if ($options['show_qr'] && $qr_url): ?>
|
|
<div class="hvac-share-qr" style="width: <?php echo esc_attr($options['qr_size']); ?>px; height: <?php echo esc_attr($options['qr_size']); ?>px; flex-shrink: 0;">
|
|
<img src="<?php echo esc_url($qr_url); ?>"
|
|
alt="QR Code for <?php echo esc_attr($share_data['trainer_name']); ?>"
|
|
style="width: 100%; height: 100%; object-fit: contain;">
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting profile share data
|
|
*/
|
|
public function ajax_get_profile_share_data() {
|
|
// Verify nonce
|
|
if (!wp_verify_nonce($_POST['nonce'], 'hvac_profile_sharing')) {
|
|
wp_send_json_error(['message' => 'Security check failed']);
|
|
return;
|
|
}
|
|
|
|
$profile_id = intval($_POST['profile_id']);
|
|
if (!$profile_id) {
|
|
wp_send_json_error(['message' => 'Invalid profile ID']);
|
|
return;
|
|
}
|
|
|
|
// Get the share data
|
|
$share_data = $this->get_trainer_share_data($profile_id);
|
|
|
|
if (!$share_data) {
|
|
wp_send_json_error(['message' => 'Profile not found or not accessible']);
|
|
return;
|
|
}
|
|
|
|
wp_send_json_success($share_data);
|
|
}
|
|
|
|
/**
|
|
* Add rewrite rules for direct profile access
|
|
*/
|
|
public function add_profile_rewrite_rules() {
|
|
// Add rewrite rule for /find-a-trainer/profile/{profile_id}
|
|
add_rewrite_rule(
|
|
'^find-a-trainer/profile/([0-9]+)/?$',
|
|
'index.php?pagename=find-a-trainer&trainer_profile_id=$matches[1]',
|
|
'top'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add custom query variables
|
|
*/
|
|
public function add_profile_query_vars($vars) {
|
|
$vars[] = 'trainer_profile_id';
|
|
return $vars;
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
HVAC_QR_Generator::instance();
|