Resolves critical Safari hanging issues through multi-layered protection: Core Safari Resource Loading Bypass: - Added Safari-specific minimal asset loading in HVAC_Scripts_Styles - Prevents 35+ CSS file cascade that overwhelmed Safari rendering - Implements intelligent browser detection with fallback systems - Loads only essential CSS/JS files for Safari browsers - Dequeues non-critical assets to prevent resource overload Browser Detection Infrastructure: - Created HVAC_Browser_Detection class with accurate Safari identification - Added User-Agent parsing with version detection - Implements fallback detection methods for edge cases - Provides centralized browser compatibility services Find Trainer Assets Management: - Added HVAC_Find_Trainer_Assets class for proper WordPress hook timing - Ensures Safari-compatible script loading order - Prevents asset loading conflicts with theme integration Safari Debugging System: - Implemented HVAC_Safari_Request_Debugger for server-side monitoring - Added comprehensive Safari debugging with error tracking - Created detailed investigation documentation - Provides real-time Safari compatibility insights Performance Optimizations: - Optimized database queries in find-trainer template to prevent hanging - Implemented lazy component loading in HVAC_Plugin initialization - Reduced Astra theme override hook priorities from 999 to 50 - Removed CSS @import statements causing Safari render blocking MapGeo Integration Fixes: - Fixed JavaScript syntax error (dangling }) in MapGeo integration - Removed problematic console.log override causing Safari conflicts - Maintained full MapGeo functionality while preventing browser hangs Testing Results: - Verified with Playwright WebKit engine (Safari emulation) - Page loads successfully with complete functionality - Interactive map, trainer cards, and navigation all functional - Reduced CSS files from 35+ to 3 core files for optimal performance - No hanging or blank page issues detected 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			361 lines
		
	
	
		
			No EOL
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			361 lines
		
	
	
		
			No EOL
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC Safari Request Debugger
 | |
|  * 
 | |
|  * Investigates Safari-specific server crashes by logging request details
 | |
|  * and monitoring PHP execution for segfaults
 | |
|  *
 | |
|  * @package HVAC_Community_Events
 | |
|  * @since 1.0.8
 | |
|  */
 | |
| 
 | |
| if (!defined('ABSPATH')) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * HVAC Safari Request Debugger Class
 | |
|  */
 | |
| class HVAC_Safari_Request_Debugger {
 | |
|     
 | |
|     /**
 | |
|      * Instance
 | |
|      *
 | |
|      * @var HVAC_Safari_Request_Debugger
 | |
|      */
 | |
|     private static $instance = null;
 | |
|     
 | |
|     /**
 | |
|      * Debug log file
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $debug_log_file;
 | |
|     
 | |
|     /**
 | |
|      * Get instance
 | |
|      */
 | |
|     public static function instance() {
 | |
|         if (null === self::$instance) {
 | |
|             self::$instance = new self();
 | |
|         }
 | |
|         return self::$instance;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Constructor
 | |
|      */
 | |
|     private function __construct() {
 | |
|         $this->debug_log_file = WP_CONTENT_DIR . '/safari-debug.log';
 | |
|         $this->init_hooks();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Initialize hooks
 | |
|      */
 | |
|     private function init_hooks() {
 | |
|         // Hook very early to catch all requests
 | |
|         add_action('init', [$this, 'debug_safari_request'], 1);
 | |
|         
 | |
|         // Log memory usage during page load
 | |
|         add_action('wp_loaded', [$this, 'log_memory_usage']);
 | |
|         
 | |
|         // Monitor plugin loading for Safari requests
 | |
|         add_action('plugins_loaded', [$this, 'log_plugins_loaded']);
 | |
|         
 | |
|         // Catch fatal errors
 | |
|         register_shutdown_function([$this, 'catch_fatal_errors']);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Check if current request is from Safari
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     private function is_safari_request() {
 | |
|         if (!isset($_SERVER['HTTP_USER_AGENT'])) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         $user_agent = $_SERVER['HTTP_USER_AGENT'];
 | |
|         
 | |
|         // Check for Safari but not Chrome (Chrome also contains Safari in UA)
 | |
|         return (strpos($user_agent, 'Safari') !== false && 
 | |
|                 strpos($user_agent, 'Chrome') === false &&
 | |
|                 strpos($user_agent, 'Chromium') === false);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Debug Safari request details
 | |
|      */
 | |
|     public function debug_safari_request() {
 | |
|         if (!$this->is_safari_request()) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         $request_data = [
 | |
|             'timestamp' => current_time('Y-m-d H:i:s'),
 | |
|             'url' => $_SERVER['REQUEST_URI'] ?? '',
 | |
|             'method' => $_SERVER['REQUEST_METHOD'] ?? '',
 | |
|             'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
 | |
|             'accept' => $_SERVER['HTTP_ACCEPT'] ?? '',
 | |
|             'accept_encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
 | |
|             'accept_language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',
 | |
|             'connection' => $_SERVER['HTTP_CONNECTION'] ?? '',
 | |
|             'host' => $_SERVER['HTTP_HOST'] ?? '',
 | |
|             'referer' => $_SERVER['HTTP_REFERER'] ?? '',
 | |
|             'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? '',
 | |
|             'server_name' => $_SERVER['SERVER_NAME'] ?? '',
 | |
|             'memory_limit' => ini_get('memory_limit'),
 | |
|             'max_execution_time' => ini_get('max_execution_time'),
 | |
|             'memory_usage' => $this->format_bytes(memory_get_usage()),
 | |
|             'memory_peak' => $this->format_bytes(memory_get_peak_usage()),
 | |
|             'php_version' => PHP_VERSION
 | |
|         ];
 | |
|         
 | |
|         $this->log_debug('SAFARI REQUEST START', $request_data);
 | |
|         
 | |
|         // Check if this is the find-a-trainer page specifically
 | |
|         if (strpos($_SERVER['REQUEST_URI'], 'find-a-trainer') !== false) {
 | |
|             $this->log_debug('FIND-A-TRAINER PAGE REQUEST', [
 | |
|                 'page' => 'find-a-trainer',
 | |
|                 'plugins_loaded' => did_action('plugins_loaded'),
 | |
|                 'wp_loaded' => did_action('wp_loaded'),
 | |
|                 'theme_setup' => did_action('after_setup_theme')
 | |
|             ]);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Log memory usage at key points
 | |
|      */
 | |
|     public function log_memory_usage() {
 | |
|         if (!$this->is_safari_request()) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         $memory_data = [
 | |
|             'current_usage' => $this->format_bytes(memory_get_usage()),
 | |
|             'peak_usage' => $this->format_bytes(memory_get_peak_usage()),
 | |
|             'real_usage' => $this->format_bytes(memory_get_usage(true)),
 | |
|             'real_peak' => $this->format_bytes(memory_get_peak_usage(true)),
 | |
|             'limit' => ini_get('memory_limit'),
 | |
|             'available' => $this->calculate_available_memory()
 | |
|         ];
 | |
|         
 | |
|         $this->log_debug('MEMORY USAGE AT WP_LOADED', $memory_data);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Log plugins loaded status
 | |
|      */
 | |
|     public function log_plugins_loaded() {
 | |
|         if (!$this->is_safari_request()) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Get list of active plugins
 | |
|         $active_plugins = get_option('active_plugins', []);
 | |
|         $problematic_plugins = [
 | |
|             'interactive-geo-maps/interactive-geo-maps.php',
 | |
|             'formidable/formidable.php',
 | |
|             'the-events-calendar/the-events-calendar.php',
 | |
|             'events-calendar-pro/events-calendar-pro.php'
 | |
|         ];
 | |
|         
 | |
|         $plugin_data = [
 | |
|             'total_active_plugins' => count($active_plugins),
 | |
|             'problematic_plugins_active' => array_intersect($active_plugins, $problematic_plugins),
 | |
|             'hvac_plugin_active' => in_array('hvac-community-events/hvac-community-events.php', $active_plugins),
 | |
|             'memory_after_plugins' => $this->format_bytes(memory_get_usage())
 | |
|         ];
 | |
|         
 | |
|         $this->log_debug('PLUGINS LOADED', $plugin_data);
 | |
|         
 | |
|         // Test specific plugin interactions
 | |
|         $this->test_plugin_interactions();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Test specific plugin interactions that might cause Safari issues
 | |
|      */
 | |
|     private function test_plugin_interactions() {
 | |
|         try {
 | |
|             // Test MapGeo plugin if active
 | |
|             if (class_exists('Interactive_Geo_Maps')) {
 | |
|                 $this->log_debug('MAPGEO PLUGIN DETECTED', [
 | |
|                     'class_exists' => true,
 | |
|                     'memory_usage' => $this->format_bytes(memory_get_usage())
 | |
|                 ]);
 | |
|             }
 | |
|             
 | |
|             // Test Events Calendar integration
 | |
|             if (function_exists('tribe_get_events')) {
 | |
|                 $this->log_debug('EVENTS CALENDAR INTEGRATION', [
 | |
|                     'tribe_functions_available' => true,
 | |
|                     'memory_usage' => $this->format_bytes(memory_get_usage())
 | |
|                 ]);
 | |
|             }
 | |
|             
 | |
|             // Test HVAC plugin classes
 | |
|             if (class_exists('HVAC_Community_Events')) {
 | |
|                 $this->log_debug('HVAC PLUGIN CLASSES', [
 | |
|                     'main_class_loaded' => true,
 | |
|                     'scripts_styles_class' => class_exists('HVAC_Scripts_Styles'),
 | |
|                     'template_loader_class' => class_exists('HVAC_Template_Loader'),
 | |
|                     'memory_usage' => $this->format_bytes(memory_get_usage())
 | |
|                 ]);
 | |
|             }
 | |
|             
 | |
|         } catch (Exception $e) {
 | |
|             $this->log_debug('PLUGIN INTERACTION ERROR', [
 | |
|                 'error' => $e->getMessage(),
 | |
|                 'trace' => $e->getTraceAsString()
 | |
|             ]);
 | |
|         } catch (Error $e) {
 | |
|             $this->log_debug('PLUGIN INTERACTION FATAL', [
 | |
|                 'error' => $e->getMessage(),
 | |
|                 'trace' => $e->getTraceAsString()
 | |
|             ]);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Catch fatal errors and log them
 | |
|      */
 | |
|     public function catch_fatal_errors() {
 | |
|         if (!$this->is_safari_request()) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         $error = error_get_last();
 | |
|         
 | |
|         if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
 | |
|             $this->log_debug('FATAL ERROR DETECTED', [
 | |
|                 'type' => $error['type'],
 | |
|                 'message' => $error['message'],
 | |
|                 'file' => $error['file'],
 | |
|                 'line' => $error['line'],
 | |
|                 'memory_usage' => $this->format_bytes(memory_get_usage()),
 | |
|                 'memory_peak' => $this->format_bytes(memory_get_peak_usage())
 | |
|             ]);
 | |
|         }
 | |
|         
 | |
|         // Log successful completion if no fatal error
 | |
|         if (!$error || !in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
 | |
|             $this->log_debug('REQUEST COMPLETED SUCCESSFULLY', [
 | |
|                 'final_memory_usage' => $this->format_bytes(memory_get_usage()),
 | |
|                 'final_memory_peak' => $this->format_bytes(memory_get_peak_usage())
 | |
|             ]);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Log debug information
 | |
|      *
 | |
|      * @param string $message
 | |
|      * @param array $data
 | |
|      */
 | |
|     private function log_debug($message, $data = []) {
 | |
|         $log_entry = [
 | |
|             'timestamp' => current_time('c'),
 | |
|             'message' => $message,
 | |
|             'data' => $data,
 | |
|             'request_id' => $this->get_request_id()
 | |
|         ];
 | |
|         
 | |
|         $log_line = '[' . date('Y-m-d H:i:s') . '] ' . $message . ' | ' . wp_json_encode($data) . PHP_EOL;
 | |
|         
 | |
|         // Write to custom log file
 | |
|         if (is_writable(dirname($this->debug_log_file))) {
 | |
|             error_log($log_line, 3, $this->debug_log_file);
 | |
|         }
 | |
|         
 | |
|         // Also write to WordPress debug log if enabled
 | |
|         if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
 | |
|             error_log('[SAFARI-REQUEST-DEBUG] ' . $log_line);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Generate unique request ID
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     private function get_request_id() {
 | |
|         static $request_id = null;
 | |
|         
 | |
|         if ($request_id === null) {
 | |
|             $request_id = uniqid('safari_', true);
 | |
|         }
 | |
|         
 | |
|         return $request_id;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Format bytes for readable output
 | |
|      *
 | |
|      * @param int $bytes
 | |
|      * @return string
 | |
|      */
 | |
|     private function format_bytes($bytes) {
 | |
|         $units = ['B', 'KB', 'MB', 'GB'];
 | |
|         $bytes = max($bytes, 0);
 | |
|         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
 | |
|         $pow = min($pow, count($units) - 1);
 | |
|         
 | |
|         $bytes /= pow(1024, $pow);
 | |
|         
 | |
|         return round($bytes, 2) . ' ' . $units[$pow];
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Calculate available memory
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     private function calculate_available_memory() {
 | |
|         $limit = ini_get('memory_limit');
 | |
|         
 | |
|         if ($limit === '-1') {
 | |
|             return 'unlimited';
 | |
|         }
 | |
|         
 | |
|         $limit_bytes = $this->parse_memory_limit($limit);
 | |
|         $used_bytes = memory_get_usage();
 | |
|         $available_bytes = $limit_bytes - $used_bytes;
 | |
|         
 | |
|         return $this->format_bytes($available_bytes);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Parse memory limit string to bytes
 | |
|      *
 | |
|      * @param string $limit
 | |
|      * @return int
 | |
|      */
 | |
|     private function parse_memory_limit($limit) {
 | |
|         $limit = trim($limit);
 | |
|         $last = strtolower($limit[strlen($limit) - 1]);
 | |
|         $limit = (int)$limit;
 | |
|         
 | |
|         switch ($last) {
 | |
|             case 'g':
 | |
|                 $limit *= 1024;
 | |
|             case 'm':
 | |
|                 $limit *= 1024;
 | |
|             case 'k':
 | |
|                 $limit *= 1024;
 | |
|         }
 | |
|         
 | |
|         return $limit;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Initialize only if this is a Safari request
 | |
| if (isset($_SERVER['HTTP_USER_AGENT']) && 
 | |
|     strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') !== false && 
 | |
|     strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome') === false) {
 | |
|     
 | |
|     HVAC_Safari_Request_Debugger::instance();
 | |
| } |