upskill-event-manager/includes/class-hvac-safari-request-debugger.php
bengizmo 4117f730c5 feat: Implement comprehensive Safari browser compatibility system
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>
2025-08-08 21:13:43 -03:00

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();
}