version = HVAC_PLUGIN_VERSION; $this->load_manifest(); $this->init_hooks(); } /** * Load webpack manifest with integrity validation */ private function load_manifest() { $manifest_path = HVAC_PLUGIN_DIR . 'assets/js/dist/manifest.json'; if (!file_exists($manifest_path)) { return false; } // Add integrity validation $manifest_content = file_get_contents($manifest_path); if ($manifest_content === false) { error_log('HVAC: Failed to read manifest file'); return false; } $manifest_hash = hash('sha256', $manifest_content); $expected_hash = get_option('hvac_manifest_hash'); // Validate manifest integrity if hash exists if ($expected_hash && $expected_hash !== $manifest_hash) { error_log('HVAC: Manifest integrity check failed - possible tampering detected'); return false; } $decoded_manifest = json_decode($manifest_content, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log('HVAC: Invalid manifest JSON - ' . json_last_error_msg()); return false; } // Validate manifest structure if (!is_array($decoded_manifest)) { error_log('HVAC: Manifest is not a valid array'); return false; } // Store hash for future validation update_option('hvac_manifest_hash', $manifest_hash); $this->manifest = $decoded_manifest; return true; } /** * Initialize hooks */ private function init_hooks() { // CRITICAL FIX: Only initialize bundled assets if NOT using legacy Scripts_Styles system // This prevents dual script loading that causes jQuery dependency conflicts if (!$this->should_use_legacy_scripts_system()) { add_action('wp_enqueue_scripts', array($this, 'enqueue_bundled_assets'), 5); // Load early add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_bundled_assets'), 5); add_action('wp_head', array($this, 'add_bundle_preload_hints'), 5); } } /** * Check if we should use legacy Scripts_Styles system instead of bundles * CRITICAL: Prevents dual script loading conflicts * * @return bool */ private function should_use_legacy_scripts_system() { // Force legacy mode if HVAC_FORCE_LEGACY_SCRIPTS is defined if (defined('HVAC_FORCE_LEGACY_SCRIPTS') && HVAC_FORCE_LEGACY_SCRIPTS) { return true; } // Use legacy system in development by default (safer for debugging) if (defined('WP_DEBUG') && WP_DEBUG && (!defined('HVAC_USE_BUNDLES') || !HVAC_USE_BUNDLES)) { return true; } // Safari browsers should use legacy system to prevent cascade issues if (class_exists('HVAC_Browser_Detection')) { $browser_detection = HVAC_Browser_Detection::instance(); if ($browser_detection->is_safari_browser()) { error_log('HVAC Bundled Assets: Using legacy scripts for Safari compatibility'); return true; } } return false; } /** * Check if we should use bundled assets * * @return bool */ private function should_use_bundled_assets() { // Don't use bundles if we should use legacy system if ($this->should_use_legacy_scripts_system()) { return false; } // Don't use bundles if forced to legacy mode if ($this->should_use_legacy_fallback()) { return false; } // Don't use bundles if manifest is empty (loading failed) if (empty($this->manifest)) { error_log('HVAC: Manifest is empty, falling back to legacy assets'); return false; } // Use bundled assets in production or if HVAC_USE_BUNDLES is true return (!defined('WP_DEBUG') || !WP_DEBUG) || (defined('HVAC_USE_BUNDLES') && HVAC_USE_BUNDLES); } /** * Check if current page needs HVAC assets * * @return bool */ private function is_plugin_page() { // Check if we're in a page template context if (defined('HVAC_IN_PAGE_TEMPLATE') && HVAC_IN_PAGE_TEMPLATE) { return true; } // Check using template loader if available if (class_exists('HVAC_Template_Loader')) { return HVAC_Template_Loader::is_plugin_template(); } return false; } /** * Enqueue bundled frontend assets */ public function enqueue_bundled_assets() { if (!$this->is_plugin_page() || !$this->should_use_bundled_assets()) { return; } $browser_detection = HVAC_Browser_Detection::instance(); // Always load core bundle on plugin pages $this->enqueue_bundle('hvac-core'); // Safari compatibility bundle for Safari browsers if ($browser_detection->is_safari_browser()) { $this->enqueue_bundle('hvac-safari-compat'); } // Context-specific bundles based on page type if ($this->is_dashboard_page()) { $this->enqueue_bundle('hvac-dashboard'); } if ($this->is_certificate_page()) { $this->enqueue_bundle('hvac-certificates'); } if ($this->is_master_trainer_page()) { $this->enqueue_bundle('hvac-master'); } if ($this->is_trainer_page()) { $this->enqueue_bundle('hvac-trainer'); } if ($this->is_event_page()) { $this->enqueue_bundle('hvac-events'); } // Localization for core bundle $this->localize_core_bundle(); } /** * Enqueue admin bundled assets */ public function enqueue_admin_bundled_assets() { if (!$this->should_use_bundled_assets()) { return; } // Admin bundle for WordPress admin areas if ($this->is_hvac_admin_page()) { $this->enqueue_bundle('hvac-admin'); } } /** * Enqueue a specific bundle with validation and graceful degradation * * @param string $bundle_name */ private function enqueue_bundle($bundle_name) { $js_file = $this->get_bundle_file($bundle_name, 'js'); $css_file = $this->get_bundle_file($bundle_name, 'css'); // Validate and enqueue JavaScript bundle with security and performance monitoring if ($js_file) { $js_path = HVAC_PLUGIN_DIR . 'assets/js/dist/' . $js_file; if (file_exists($js_path)) { // Security: Validate file size is reasonable (prevent potential DoS) $file_size = filesize($js_path); if ($file_size > 1024 * 1024) { // 1MB limit error_log("[HVAC] Bundle {$js_file} exceeds size limit ({$file_size} bytes) - falling back"); $this->enqueue_legacy_fallback($bundle_name); return; } // Security: Validate filename contains only safe characters if (!preg_match('/^[a-zA-Z0-9._-]+$/', $js_file)) { error_log("[HVAC] Invalid bundle filename: {$js_file} - falling back"); $this->enqueue_legacy_fallback($bundle_name); return; } $version = filemtime($js_path); // Use file modification time for cache busting // CRITICAL FIX: Ensure jQuery is always loaded first wp_enqueue_script('jquery'); wp_enqueue_script( $bundle_name, HVAC_PLUGIN_URL . 'assets/js/dist/' . $js_file, array('jquery'), $version, true ); // Add performance monitoring attributes $this->add_bundle_performance_monitoring($bundle_name); if (defined('WP_DEBUG') && WP_DEBUG) { error_log('[HVAC Bundled Assets] Loaded JS bundle: ' . $bundle_name . ' (' . round($file_size / 1024, 1) . 'KB)'); } } else { error_log("[HVAC] Missing JS bundle: {$js_file} - falling back to legacy scripts"); $this->enqueue_legacy_fallback($bundle_name); } } // Validate and enqueue CSS bundle if exists if ($css_file) { $css_path = HVAC_PLUGIN_DIR . 'assets/js/dist/' . $css_file; if (file_exists($css_path)) { wp_enqueue_style( $bundle_name . '-style', HVAC_PLUGIN_URL . 'assets/js/dist/' . $css_file, array(), filemtime($css_path) ); if (defined('WP_DEBUG') && WP_DEBUG) { error_log('[HVAC Bundled Assets] Loaded CSS bundle: ' . $bundle_name); } } else { if (defined('WP_DEBUG') && WP_DEBUG) { error_log("[HVAC] Missing CSS bundle: {$css_file}"); } } } } /** * Get bundle file path from manifest with chunk support * * @param string $bundle_name * @param string $type js|css * @return string|null */ private function get_bundle_file($bundle_name, $type = 'js') { $key = $bundle_name . '.' . $type; if (isset($this->manifest[$key])) { return $this->manifest[$key]; } // Check for chunk files (lazy-loaded components) $chunk_key = $bundle_name . '.chunk.' . $type; if (isset($this->manifest[$chunk_key])) { return $this->manifest[$chunk_key]; } // Fallback to expected filename pattern $suffix = (defined('WP_DEBUG') && WP_DEBUG) ? '' : '.min'; return $bundle_name . '.bundle' . $suffix . '.' . $type; } /** * Get all chunk files for a bundle (for preloading) * * @param string $bundle_name * @return array */ private function get_bundle_chunks($bundle_name) { $chunks = []; // Look for all chunk files related to this bundle foreach ($this->manifest as $key => $filename) { // Match chunk files like "trainer-profile.chunk.js", "event-editing.chunk.js" if (preg_match("/({$bundle_name}-.+|.+-{$bundle_name})\.chunk\.(js|css)$/", $key)) { $chunks[$key] = $filename; } } return $chunks; } /** * Fallback to legacy individual scripts when bundles fail * * @param string $bundle_name */ private function enqueue_legacy_fallback($bundle_name) { // Check if we're already in too many error scenarios if ($this->should_use_legacy_fallback()) { return; } // Increment error count $error_count = get_transient('hvac_bundle_errors') ?: 0; set_transient('hvac_bundle_errors', $error_count + 1, HOUR_IN_SECONDS); // Map bundle names to legacy script methods $legacy_map = array( 'hvac-core' => 'enqueue_core_scripts', 'hvac-dashboard' => 'enqueue_dashboard_scripts', 'hvac-certificates' => 'enqueue_certificate_scripts', 'hvac-master' => 'enqueue_master_trainer_scripts', 'hvac-trainer' => 'enqueue_trainer_scripts', 'hvac-events' => 'enqueue_event_scripts', 'hvac-admin' => 'enqueue_admin_scripts' ); if (isset($legacy_map[$bundle_name]) && class_exists('HVAC_Scripts_Styles')) { $scripts_instance = HVAC_Scripts_Styles::instance(); $method = $legacy_map[$bundle_name]; if (method_exists($scripts_instance, $method)) { $scripts_instance->$method(); error_log("[HVAC] Fallback activated for bundle: {$bundle_name}"); } } } /** * Check if we should use legacy fallback due to too many errors * * @return bool */ private function should_use_legacy_fallback() { $error_count = get_transient('hvac_bundle_errors'); if ($error_count > 5) { // Too many errors, use legacy for 1 hour set_transient('hvac_force_legacy', true, HOUR_IN_SECONDS); return true; } return get_transient('hvac_force_legacy'); } /** * Localize core bundle with WordPress data and security monitoring */ private function localize_core_bundle() { wp_localize_script('hvac-core', 'hvacBundleData', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('hvac_bundle_nonce'), 'rest_url' => rest_url('hvac/v1/'), 'current_user_id' => get_current_user_id(), 'is_safari' => HVAC_Browser_Detection::instance()->is_safari_browser(), 'debug' => defined('WP_DEBUG') && WP_DEBUG, 'version' => $this->version, // Security monitoring configuration 'security' => array( 'report_errors' => true, 'error_endpoint' => rest_url('hvac/v1/bundle-errors'), 'performance_monitoring' => !defined('HVAC_DISABLE_PERF_MONITORING'), 'max_load_time' => 5000, // 5 seconds 'retry_attempts' => 2 ) )); // Add client-side error detection and performance monitoring $this->add_client_side_monitoring(); } /** * Add client-side error detection and performance monitoring */ private function add_client_side_monitoring() { $monitoring_script = " (function() { 'use strict'; var hvacSecurity = { errors: [], performance: {}, // Report bundle loading errors reportError: function(error, bundle) { if (!window.hvacBundleData || !window.hvacBundleData.security.report_errors) return; this.errors.push({ error: error, bundle: bundle, timestamp: Date.now(), userAgent: navigator.userAgent.substring(0, 100), // Limit length for security url: window.location.href }); // Report to server if REST API available if (window.hvacBundleData.security.error_endpoint) { this.sendErrorReport(); } }, // Send error reports to server sendErrorReport: function() { if (this.errors.length === 0) return; fetch(window.hvacBundleData.security.error_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.hvacBundleData.nonce }, body: JSON.stringify({ errors: this.errors.splice(0, 5), // Send max 5 errors at once page: window.location.pathname, version: window.hvacBundleData.version }) }).catch(function(e) { console.warn('HVAC: Failed to report bundle errors', e); }); }, // Monitor bundle loading performance monitorPerformance: function(bundleName, startTime) { if (!window.hvacBundleData.security.performance_monitoring) return; var loadTime = Date.now() - startTime; this.performance[bundleName] = loadTime; var maxLoadTime = window.hvacBundleData.security.max_load_time || 5000; if (loadTime > maxLoadTime) { this.reportError('Bundle load time exceeded ' + maxLoadTime + 'ms (' + loadTime + 'ms)', bundleName); } } }; // Global error handler for script loading failures window.addEventListener('error', function(e) { if (e.target && e.target.tagName === 'SCRIPT' && e.target.src && e.target.src.includes('hvac')) { var bundleName = e.target.src.match(/hvac-([^.]+)/) ? e.target.src.match(/hvac-([^.]+)/)[1] : 'unknown'; hvacSecurity.reportError('Script load failed: ' + e.message, 'hvac-' + bundleName); } }); // Attach to global scope for bundle monitoring window.hvacSecurity = hvacSecurity; // Auto-report errors every 30 seconds if any exist setInterval(function() { if (hvacSecurity.errors.length > 0) { hvacSecurity.sendErrorReport(); } }, 30000); })(); "; wp_add_inline_script('hvac-core', $monitoring_script, 'before'); } /** * Add bundle preload hints for critical resources */ public function add_bundle_preload_hints() { if (!$this->is_plugin_page() || !$this->should_use_bundled_assets()) { return; } // Always preload core bundle on plugin pages $this->add_preload_hint('hvac-core', 'script'); // Context-specific preloading for critical bundles if ($this->is_dashboard_page()) { $this->add_preload_hint('hvac-dashboard', 'script'); } if ($this->is_master_trainer_page()) { $this->add_preload_hint('hvac-master', 'script'); } // Safari compatibility bundle for Safari browsers $browser_detection = HVAC_Browser_Detection::instance(); if ($browser_detection->is_safari_browser()) { $this->add_preload_hint('hvac-safari-compat', 'script'); } } /** * Add individual preload hint with security validation * * @param string $bundle_name * @param string $type script|style */ private function add_preload_hint($bundle_name, $type) { $file_type = $type === 'script' ? 'js' : 'css'; $bundle_file = $this->get_bundle_file($bundle_name, $file_type); if (!$bundle_file) { return; } // Validate file exists and is safe $file_path = HVAC_PLUGIN_DIR . 'assets/js/dist/' . $bundle_file; if (!file_exists($file_path)) { return; } // Security: Validate filename contains only safe characters if (!preg_match('/^[a-zA-Z0-9._-]+$/', $bundle_file)) { error_log('HVAC: Invalid bundle filename for preload: ' . $bundle_file); return; } $bundle_url = esc_url(HVAC_PLUGIN_URL . 'assets/js/dist/' . $bundle_file); $as_attribute = $type === 'script' ? 'script' : 'style'; // Add integrity hash for additional security $integrity_hash = base64_encode(hash('sha384', file_get_contents($file_path), true)); echo '' . "\n"; if (defined('WP_DEBUG') && WP_DEBUG) { error_log("[HVAC Bundled Assets] Preload hint added for: {$bundle_name}.{$file_type}"); } } /** * Add performance monitoring attributes to bundle scripts * * @param string $bundle_name */ private function add_bundle_performance_monitoring($bundle_name) { // Add inline script to monitor bundle loading performance $monitoring_script = " (function() { var startTime = performance.now(); var bundleName = '{$bundle_name}'; // Monitor when this script finishes loading if (window.hvacSecurity && window.hvacSecurity.monitorPerformance) { document.addEventListener('DOMContentLoaded', function() { window.hvacSecurity.monitorPerformance(bundleName, startTime); }); } // Fallback error detection if hvacSecurity not loaded window.addEventListener('error', function(e) { if (e.target && e.target.src && e.target.src.includes(bundleName)) { console.error('HVAC Bundle Error: Failed to load ' + bundleName); } }); })(); "; wp_add_inline_script($bundle_name, $monitoring_script, 'before'); } /** * Page detection methods */ private function is_dashboard_page() { return class_exists('HVAC_Scripts_Styles') && method_exists('HVAC_Scripts_Styles', 'instance') && method_exists(HVAC_Scripts_Styles::instance(), 'is_dashboard_page') ? HVAC_Scripts_Styles::instance()->is_dashboard_page() : false; } private function is_certificate_page() { return class_exists('HVAC_Scripts_Styles') && method_exists('HVAC_Scripts_Styles', 'instance') && method_exists(HVAC_Scripts_Styles::instance(), 'is_certificate_page') ? HVAC_Scripts_Styles::instance()->is_certificate_page() : false; } private function is_master_trainer_page() { return $this->is_pending_approvals_page() || $this->is_master_events_page() || $this->is_import_export_page() || is_page('master-dashboard') || strpos($_SERVER['REQUEST_URI'], 'master-trainer/') !== false; } private function is_trainer_page() { return $this->is_trainer_profile_page() || $this->is_registration_page() || strpos($_SERVER['REQUEST_URI'], '/trainer/') !== false; } private function is_event_page() { return $this->is_event_manage_page() || $this->is_create_event_page() || $this->is_edit_event_page() || $this->is_organizers_page() || $this->is_venues_page(); } private function is_hvac_admin_page() { $screen = get_current_screen(); return $screen && strpos($screen->id, 'hvac') !== false; } // Helper methods (delegate to existing HVAC_Scripts_Styles if available) private function is_pending_approvals_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_pending_approvals_page') ? HVAC_Scripts_Styles::instance()->is_pending_approvals_page() : false; } private function is_master_events_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_master_events_page') ? HVAC_Scripts_Styles::instance()->is_master_events_page() : false; } private function is_import_export_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_import_export_page') ? HVAC_Scripts_Styles::instance()->is_import_export_page() : false; } private function is_trainer_profile_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_trainer_profile_page') ? HVAC_Scripts_Styles::instance()->is_trainer_profile_page() : false; } private function is_registration_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_registration_page') ? HVAC_Scripts_Styles::instance()->is_registration_page() : false; } private function is_event_manage_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_event_manage_page') ? HVAC_Scripts_Styles::instance()->is_event_manage_page() : false; } private function is_create_event_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_create_event_page') ? HVAC_Scripts_Styles::instance()->is_create_event_page() : false; } private function is_edit_event_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_edit_event_page') ? HVAC_Scripts_Styles::instance()->is_edit_event_page() : false; } private function is_organizers_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_organizers_page') ? HVAC_Scripts_Styles::instance()->is_organizers_page() : false; } private function is_venues_page() { return class_exists('HVAC_Scripts_Styles') && method_exists(HVAC_Scripts_Styles::instance(), 'is_venues_page') ? HVAC_Scripts_Styles::instance()->is_venues_page() : false; } }