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