## Major Enhancements ### 🏗️ Architecture & Infrastructure - Implement comprehensive Docker testing infrastructure with hermetic environment - Add Forgejo Actions CI/CD pipeline for automated deployments - Create Page Object Model (POM) testing architecture reducing test duplication by 90% - Establish security-first development patterns with input validation and output escaping ### 🧪 Testing Framework Modernization - Migrate 146+ tests from 80 duplicate files to centralized architecture - Add comprehensive E2E test suites for all user roles and workflows - Implement WordPress error detection with automatic site health monitoring - Create robust browser lifecycle management with proper cleanup ### 📚 Documentation & Guides - Add comprehensive development best practices guide - Create detailed administrator setup documentation - Establish user guides for trainers and master trainers - Document security incident reports and migration guides ### 🔧 Core Plugin Features - Enhance trainer profile management with certification system - Improve find trainer functionality with advanced filtering - Strengthen master trainer area with content management - Add comprehensive venue and organizer management ### 🛡️ Security & Reliability - Implement security-first patterns throughout codebase - Add comprehensive input validation and output escaping - Create secure credential management system - Establish proper WordPress role-based access control ### 🎯 WordPress Integration - Strengthen singleton pattern implementation across all classes - Enhance template hierarchy with proper WordPress integration - Improve page manager with hierarchical URL structure - Add comprehensive shortcode and menu system ### 🔍 Developer Experience - Add extensive debugging and troubleshooting tools - Create comprehensive test data seeding scripts - Implement proper error handling and logging - Establish consistent code patterns and standards ### 📊 Performance & Optimization - Optimize database queries and caching strategies - Improve asset loading and script management - Enhance template rendering performance - Streamline user experience across all interfaces 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
555 lines
No EOL
19 KiB
PHP
555 lines
No EOL
19 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Menu System
|
|
*
|
|
* Implements proper WordPress menu API for HVAC trainer pages
|
|
* Following WordPress best practices for menu registration and display
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class HVAC_Menu_System {
|
|
|
|
/**
|
|
* Plugin instance
|
|
*
|
|
* @var HVAC_Menu_System
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get plugin instance
|
|
*
|
|
* @return HVAC_Menu_System
|
|
*/
|
|
public static function instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Alias for instance() to match template calls
|
|
*
|
|
* @return HVAC_Menu_System
|
|
*/
|
|
public static function get_instance() {
|
|
return self::instance();
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
add_action('init', array($this, 'register_menu_locations'));
|
|
add_action('wp', array($this, 'setup_trainer_menu'));
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_menu_styles'));
|
|
|
|
// CRITICAL FIX: Also enqueue on wp_head to catch late-loaded templates
|
|
// This ensures scripts load even when templates are loaded via template_include filter
|
|
add_action('wp_head', array($this, 'ensure_scripts_loaded'), 1);
|
|
|
|
add_filter('wp_nav_menu_items', array($this, 'add_logout_to_menu'), 10, 2);
|
|
}
|
|
|
|
/**
|
|
* Detect if user is using Safari browser
|
|
* Uses centralized browser detection service
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function is_safari_browser() {
|
|
return HVAC_Browser_Detection::instance()->is_safari_browser();
|
|
}
|
|
|
|
/**
|
|
* Get script path based on browser compatibility
|
|
* Uses centralized browser detection service
|
|
*
|
|
* @param string $script_name
|
|
* @return string
|
|
*/
|
|
private function get_compatible_script_path($script_name) {
|
|
$browser_detection = HVAC_Browser_Detection::instance();
|
|
|
|
// If Safari and doesn't support ES6, load Safari-compatible version
|
|
if ($browser_detection->is_safari_browser() && !$browser_detection->safari_supports_es6()) {
|
|
$safari_script = HVAC_PLUGIN_DIR . 'assets/js/' . $script_name . '-safari-compatible.js';
|
|
if (file_exists($safari_script)) {
|
|
return HVAC_PLUGIN_URL . 'assets/js/' . $script_name . '-safari-compatible.js';
|
|
}
|
|
}
|
|
|
|
// Default to standard version
|
|
return HVAC_PLUGIN_URL . 'assets/js/' . $script_name . '.js';
|
|
}
|
|
|
|
/**
|
|
* Register menu locations
|
|
*/
|
|
public function register_menu_locations() {
|
|
register_nav_menus(array(
|
|
'hvac_trainer_menu' => __('HVAC Trainer Navigation', 'hvac-community-events'),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Setup trainer menu on appropriate pages
|
|
*/
|
|
public function setup_trainer_menu() {
|
|
// Menu will be rendered directly in templates, not injected via JavaScript
|
|
}
|
|
|
|
/**
|
|
* Check if current page is a trainer page
|
|
*/
|
|
private function is_trainer_page() {
|
|
global $wp;
|
|
$current_url = home_url(add_query_arg(array(), $wp->request));
|
|
return (strpos($current_url, '/trainer/') !== false || strpos($current_url, '/master-trainer/') !== false);
|
|
}
|
|
|
|
/**
|
|
* Check if user can access trainer area
|
|
*/
|
|
private function user_can_access_trainer_area() {
|
|
if (!is_user_logged_in()) {
|
|
return false;
|
|
}
|
|
|
|
return current_user_can('hvac_trainer') ||
|
|
current_user_can('hvac_master_trainer') ||
|
|
current_user_can('manage_options');
|
|
}
|
|
|
|
/**
|
|
* Render the trainer menu
|
|
*/
|
|
public function render_trainer_menu() {
|
|
if (!$this->user_can_access_trainer_area()) {
|
|
return;
|
|
}
|
|
|
|
$menu_items = $this->get_menu_structure();
|
|
|
|
echo '<div class="hvac-trainer-menu-wrapper">';
|
|
echo '<nav class="hvac-trainer-nav" role="navigation">';
|
|
|
|
// Add hamburger button for mobile
|
|
echo '<button class="hvac-hamburger-menu" id="hvac-hamburger-menu" aria-label="Toggle menu" aria-expanded="false">';
|
|
echo '<span class="hvac-hamburger-line"></span>';
|
|
echo '<span class="hvac-hamburger-line"></span>';
|
|
echo '<span class="hvac-hamburger-line"></span>';
|
|
echo '</button>';
|
|
|
|
echo '<ul class="hvac-trainer-menu" id="hvac-trainer-menu">';
|
|
|
|
foreach ($menu_items as $item) {
|
|
$this->render_menu_item($item);
|
|
}
|
|
|
|
echo '</ul>';
|
|
echo '</nav>';
|
|
echo '</div>';
|
|
}
|
|
|
|
/**
|
|
* Alias for render_trainer_menu() to match template calls
|
|
*/
|
|
public function render_navigation_menu() {
|
|
return $this->render_trainer_menu();
|
|
}
|
|
|
|
/**
|
|
* Static method for templates calling render_navigation()
|
|
*/
|
|
public static function render_navigation() {
|
|
return self::instance()->render_trainer_menu();
|
|
}
|
|
|
|
/**
|
|
* Get menu structure for regular trainers
|
|
*/
|
|
private function get_menu_structure() {
|
|
$menu = array();
|
|
|
|
// Events section
|
|
$menu[] = array(
|
|
'title' => 'Events',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-calendar-alt',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'Dashboard',
|
|
'url' => home_url('/trainer/dashboard/'),
|
|
'icon' => 'dashicons-dashboard'
|
|
),
|
|
array(
|
|
'title' => 'New Event',
|
|
'url' => home_url('/trainer/event/manage/'),
|
|
'icon' => 'dashicons-plus-alt'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Certificates section
|
|
$menu[] = array(
|
|
'title' => 'Certificates',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-awards',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'Reports',
|
|
'url' => home_url('/trainer/certificate-reports/'),
|
|
'icon' => 'dashicons-analytics'
|
|
),
|
|
array(
|
|
'title' => 'New Certificate',
|
|
'url' => home_url('/trainer/generate-certificates/'),
|
|
'icon' => 'dashicons-plus-alt'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Profile section (previously Customize)
|
|
$menu[] = array(
|
|
'title' => 'Profile',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-admin-users',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'Trainer Profile',
|
|
'url' => home_url('/trainer/profile/'),
|
|
'icon' => 'dashicons-admin-users'
|
|
),
|
|
array(
|
|
'title' => 'Training Leads',
|
|
'url' => home_url('/trainer/profile/training-leads/'),
|
|
'icon' => 'dashicons-email-alt'
|
|
),
|
|
array(
|
|
'title' => 'Resources',
|
|
'url' => home_url('/trainer/resources/'),
|
|
'icon' => 'dashicons-media-default'
|
|
),
|
|
array(
|
|
'title' => 'Training Organizers',
|
|
'url' => home_url('/trainer/organizer/list/'),
|
|
'icon' => 'dashicons-groups',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'Add New Organizer',
|
|
'url' => home_url('/trainer/organizer/manage/'),
|
|
'icon' => 'dashicons-plus-alt'
|
|
)
|
|
)
|
|
),
|
|
array(
|
|
'title' => 'Training Venues',
|
|
'url' => home_url('/trainer/venue/list/'),
|
|
'icon' => 'dashicons-location-alt',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'Add New Venue',
|
|
'url' => home_url('/trainer/venue/manage/'),
|
|
'icon' => 'dashicons-plus-alt'
|
|
)
|
|
)
|
|
),
|
|
array(
|
|
'title' => 'Logout',
|
|
'url' => wp_logout_url(home_url('/training-login/')),
|
|
'icon' => 'dashicons-exit'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Help section (moved to end, with question mark icon)
|
|
$menu[] = array(
|
|
'title' => '?',
|
|
'url' => home_url('/trainer/documentation/'),
|
|
'icon' => 'dashicons-editor-help',
|
|
'class' => 'hvac-help-menu-item'
|
|
);
|
|
|
|
return $menu;
|
|
}
|
|
|
|
/**
|
|
* Get master trainer menu structure
|
|
*/
|
|
private function get_master_menu_structure() {
|
|
$menu = array();
|
|
|
|
// Master Dashboard
|
|
$menu[] = array(
|
|
'title' => 'Master Dashboard',
|
|
'url' => home_url('/master-trainer/master-dashboard/'),
|
|
'icon' => 'dashicons-star-filled',
|
|
'class' => 'master-menu-item'
|
|
);
|
|
|
|
// Announcements (Master Trainers only)
|
|
$menu[] = array(
|
|
'title' => 'Announcements',
|
|
'url' => home_url('/master-trainer/announcements/'),
|
|
'icon' => 'dashicons-megaphone',
|
|
'class' => 'master-menu-item'
|
|
);
|
|
|
|
// Events section
|
|
$menu[] = array(
|
|
'title' => 'Events',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-calendar-alt',
|
|
'class' => 'master-menu-item',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'All Events',
|
|
'url' => home_url('/trainer/dashboard/'),
|
|
'icon' => 'dashicons-dashboard'
|
|
),
|
|
array(
|
|
'title' => 'New Event',
|
|
'url' => home_url('/trainer/event/manage/'),
|
|
'icon' => 'dashicons-plus-alt'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Trainers Management
|
|
$menu[] = array(
|
|
'title' => 'Trainers',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-groups',
|
|
'class' => 'master-menu-item',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'All Trainers',
|
|
'url' => home_url('/master-trainer/trainers/'),
|
|
'icon' => 'dashicons-list-view'
|
|
),
|
|
array(
|
|
'title' => 'Pending Approvals',
|
|
'url' => home_url('/master-trainer/pending-approvals/'),
|
|
'icon' => 'dashicons-clock'
|
|
),
|
|
array(
|
|
'title' => 'Trainer Stats',
|
|
'url' => home_url('/master-trainer/trainer-stats/'),
|
|
'icon' => 'dashicons-chart-bar'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Certificates section
|
|
$menu[] = array(
|
|
'title' => 'Certificates',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-awards',
|
|
'class' => 'master-menu-item',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'All Reports',
|
|
'url' => home_url('/trainer/certificate-reports/'),
|
|
'icon' => 'dashicons-analytics'
|
|
),
|
|
array(
|
|
'title' => 'Generate Certificates',
|
|
'url' => home_url('/trainer/generate-certificates/'),
|
|
'icon' => 'dashicons-plus-alt'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Tools & Settings
|
|
$menu[] = array(
|
|
'title' => 'Tools',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-admin-tools',
|
|
'class' => 'master-menu-item',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'Communication Templates',
|
|
'url' => home_url('/trainer/communication-templates/'),
|
|
'icon' => 'dashicons-email'
|
|
),
|
|
array(
|
|
'title' => 'Google Sheets',
|
|
'url' => home_url('/master-trainer/google-sheets/'),
|
|
'icon' => 'dashicons-media-spreadsheet'
|
|
),
|
|
array(
|
|
'title' => 'Import/Export',
|
|
'url' => home_url('/master-trainer/import-export/'),
|
|
'icon' => 'dashicons-database-import'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Profile section
|
|
$menu[] = array(
|
|
'title' => 'Profile',
|
|
'url' => '#',
|
|
'icon' => 'dashicons-admin-users',
|
|
'class' => 'master-menu-item',
|
|
'children' => array(
|
|
array(
|
|
'title' => 'My Profile',
|
|
'url' => home_url('/trainer/profile/'),
|
|
'icon' => 'dashicons-admin-users'
|
|
),
|
|
array(
|
|
'title' => 'Resources',
|
|
'url' => home_url('/trainer/resources/'),
|
|
'icon' => 'dashicons-media-default'
|
|
),
|
|
array(
|
|
'title' => 'Logout',
|
|
'url' => wp_logout_url(home_url('/training-login/')),
|
|
'icon' => 'dashicons-exit'
|
|
)
|
|
)
|
|
);
|
|
|
|
// Help section
|
|
$menu[] = array(
|
|
'title' => '?',
|
|
'url' => home_url('/trainer/documentation/'),
|
|
'icon' => 'dashicons-editor-help',
|
|
'class' => 'hvac-help-menu-item master-menu-item'
|
|
);
|
|
|
|
return $menu;
|
|
}
|
|
|
|
/**
|
|
* Render individual menu item
|
|
*/
|
|
private function render_menu_item($item, $level = 0) {
|
|
$has_children = !empty($item['children']);
|
|
$icon = !empty($item['icon']) ? '<span class="dashicons ' . esc_attr($item['icon']) . '"></span>' : '';
|
|
$classes = array('menu-item');
|
|
|
|
if ($has_children) {
|
|
$classes[] = 'has-children';
|
|
}
|
|
|
|
if ($level > 0) {
|
|
$classes[] = 'sub-menu-item';
|
|
$classes[] = 'level-' . $level;
|
|
}
|
|
|
|
// Add custom class if specified
|
|
if (!empty($item['class'])) {
|
|
$classes[] = esc_attr($item['class']);
|
|
}
|
|
|
|
echo '<li class="' . implode(' ', $classes) . '">';
|
|
|
|
// Check if this is the help menu item (show only icon)
|
|
$is_help_menu = !empty($item['class']) && strpos($item['class'], 'hvac-help-menu-item') !== false;
|
|
$title_content = $is_help_menu ? '' : esc_html($item['title']);
|
|
|
|
if ($item['url'] === '#' && $has_children) {
|
|
echo '<span class="menu-toggle">' . $icon . $title_content . '<span class="dropdown-arrow">▼</span></span>';
|
|
} else {
|
|
echo '<a href="' . esc_url($item['url']) . '">' . $icon . $title_content . '</a>';
|
|
}
|
|
|
|
if ($has_children) {
|
|
echo '<ul class="sub-menu">';
|
|
foreach ($item['children'] as $child) {
|
|
$this->render_menu_item($child, $level + 1);
|
|
}
|
|
echo '</ul>';
|
|
}
|
|
|
|
echo '</li>';
|
|
}
|
|
|
|
/**
|
|
* Enqueue menu styles and scripts
|
|
*/
|
|
public function enqueue_menu_styles() {
|
|
// AGGRESSIVE: Enqueue on ALL pages to prevent missing JavaScript issues
|
|
// The menu is only rendered when user has proper access, so this is safe
|
|
|
|
wp_enqueue_style(
|
|
'hvac-menu-system',
|
|
HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css',
|
|
array(),
|
|
HVAC_PLUGIN_VERSION
|
|
);
|
|
|
|
// CRITICAL: Enqueue JavaScript on ALL pages to ensure dropdowns work
|
|
// The JavaScript only activates when menu elements are present
|
|
wp_enqueue_script(
|
|
'hvac-navigation-robust',
|
|
HVAC_PLUGIN_URL . 'assets/js/hvac-navigation-robust.js',
|
|
array('jquery'),
|
|
HVAC_PLUGIN_VERSION,
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Ensure scripts are loaded even for late-loaded templates
|
|
* This fixes the issue where dashboard pages loaded via template_include don't get scripts
|
|
*/
|
|
public function ensure_scripts_loaded() {
|
|
// Check if we're on a trainer page that needs the menu
|
|
if (!is_user_logged_in()) {
|
|
return;
|
|
}
|
|
|
|
// ALWAYS output the scripts directly in wp_head for dashboard pages
|
|
// This ensures they load regardless of enqueue status
|
|
global $wp;
|
|
$current_url = home_url(add_query_arg(array(), $wp->request));
|
|
$is_trainer_page = strpos($current_url, '/trainer/') !== false || strpos($current_url, '/master-trainer/') !== false;
|
|
|
|
if ($is_trainer_page) {
|
|
// Only add direct scripts if they're not already enqueued
|
|
if (!wp_script_is('hvac-navigation-robust', 'enqueued')) {
|
|
?>
|
|
<link rel="stylesheet" id="hvac-menu-system-direct" href="<?php echo esc_url(HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css?ver=' . HVAC_PLUGIN_VERSION); ?>" />
|
|
<script>
|
|
// Wait for jQuery to be available before loading navigation script
|
|
(function checkJQuery() {
|
|
if (typeof jQuery !== 'undefined') {
|
|
var script = document.createElement('script');
|
|
script.id = 'hvac-navigation-robust-direct';
|
|
script.src = '<?php echo esc_url(HVAC_PLUGIN_URL . 'assets/js/hvac-navigation-robust.js?ver=' . HVAC_PLUGIN_VERSION); ?>';
|
|
document.head.appendChild(script);
|
|
} else {
|
|
setTimeout(checkJQuery, 50);
|
|
}
|
|
})();
|
|
</script>
|
|
<?php
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add logout link to WordPress menu (if using wp_nav_menu)
|
|
*/
|
|
public function add_logout_to_menu($items, $args) {
|
|
if ($args->theme_location === 'hvac_trainer_menu' && $this->user_can_access_trainer_area()) {
|
|
$logout_link = '<li class="menu-item menu-item-logout"><a href="' . wp_logout_url(home_url('/training-login/')) . '"><span class="dashicons dashicons-exit"></span>Logout</a></li>';
|
|
$items .= $logout_link;
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
HVAC_Menu_System::instance();
|