Home / Admin / calendar-core
Duplicate Snippet

Embed Snippet on Your Site

calendar-core

Code Preview
php
<?php
<?php
/**
 * ============================================================================
 * WP Code Snippet: Calendar System Core + Shortcode
 * ============================================================================
 * Combined core functionality and shortcode for WP Code deployment
 * 
 * File Structure:
 * - GymCalendarCore Class (22 functions)
 * - GymCalendarShortcode Class (6 functions) 
 * - Email Templates (LOCKED SECTION - lines 694-814)
 * - AJAX Handlers
 * - Initialization
 * 
 * Total Functions: 28
 * ============================================================================
 */
// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}
class GymCalendarCore {
    
    private $wpdb;
    private $table_classes;
    private $table_instances;
    private $table_registrations;
    private $table_settings;
    private $table_credits;
    
    public function __construct() {
        global $wpdb;
        $this->wpdb = $wpdb;
        $this->table_classes = $wpdb->prefix . 'gym_classes';
        $this->table_instances = $wpdb->prefix . 'gym_class_instances';
        $this->table_registrations = $wpdb->prefix . 'gym_class_registrations';
        $this->table_settings = $wpdb->prefix . 'gym_calendar_settings';
        $this->table_credits = $wpdb->prefix . 'gym_credits';
        
        // Auto-generate holiday instances when calendar is accessed
        $this->ensureHolidayInstances();
    }
    
    /**
     * Ensure holiday instances are generated for current and upcoming years
     */
    private function ensureHolidayInstances() {
        $current_year = date('Y');
        $next_year = $current_year + 1;
        
        // Generate holidays for current and next year
        $this->generateHolidayInstances($current_year);
        $this->generateHolidayInstances($next_year);
    }
    
    /**
     * Generate holiday instances for a specific year
     */
    private function generateHolidayInstances($year) {
        // Get the holiday class ID
        $holiday_class_id = $this->wpdb->get_var(
            $this->wpdb->prepare(
                "SELECT id FROM {$this->table_classes} WHERE class_type = 'holiday' LIMIT 1"
            )
        );
        
        if (!$holiday_class_id) {
            return; // No holiday class defined
        }
        
        $holiday_dates = $this->getStatutoryHolidayDates($year);
        
        foreach ($holiday_dates as $date => $name) {
            // Check if this holiday instance already exists
            $existing = $this->wpdb->get_var(
                $this->wpdb->prepare(
                    "SELECT id FROM {$this->table_instances} 
                     WHERE class_id = %d AND class_date = %s",
                    $holiday_class_id, $date
                )
            );
            
            if (!$existing) {
                // Create the holiday instance
                $this->wpdb->insert(
                    $this->table_instances,
                    array(
                        'class_id' => $holiday_class_id,
                        'class_date' => $date,
                        'start_time' => '00:00:00',
                        'end_time' => '23:59:59',
                        'current_capacity' => 0,
                        'max_capacity' => 0,
                        'status' => 'scheduled',
                        'special_notes' => $name
                    ),
                    array('%d', '%s', '%s', '%s', '%d', '%d', '%s', '%s')
                );
            }
        }
    }
    
    /**
     * Calculate statutory holiday dates for a given year
     */
    private function getStatutoryHolidayDates($year) {
        $holidays = array();
        
        // New Year's Day - January 1
        $holidays[$year . '-01-01'] = "<b>New Year's Day</b><br>Gym Closed";
        
        // Family Day - Third Monday of February
        $family_day = new DateTime("third monday of february $year");
        $holidays[$family_day->format('Y-m-d')] = "<b>Family Day</b><br>Gym Closed";
        
        // Easter calculations
        $easter_dates = array(
            2025 => '2025-04-20', 2026 => '2026-04-05', 2027 => '2027-03-28',
            2028 => '2028-04-16', 2029 => '2029-04-01', 2030 => '2030-04-21',
            2031 => '2031-04-13', 2032 => '2032-03-28', 2033 => '2033-04-17',
            2034 => '2034-04-09', 2035 => '2035-03-25'
        );
        
        if (isset($easter_dates[$year])) {
            $easter = new DateTime($easter_dates[$year]);
            
            // Good Friday - Friday before Easter
            $good_friday = clone $easter;
            $good_friday->modify('-2 days');
            $holidays[$good_friday->format('Y-m-d')] = "<b>Good Friday</b><br>Gym Closed";
            
            // Easter Sunday
            $holidays[$easter->format('Y-m-d')] = "<b>Easter Sunday</b><br>Gym Closed";
        }
        
        // Victoria Day - Last Monday before or on May 24
        $victoria_day = new DateTime("may 24 $year");
        while ($victoria_day->format('w') != 1) { // 1 = Monday
            $victoria_day->modify('-1 day');
        }
        $holidays[$victoria_day->format('Y-m-d')] = "<b>Victoria Day</b><br>Gym Closed";
        
        // Canada Day - July 1
        $holidays[$year . '-07-01'] = "<b>Canada Day</b><br>Gym Closed";
        
        // Labour Day - First Monday in September
        $labour_day = new DateTime("first monday of september $year");
        $holidays[$labour_day->format('Y-m-d')] = "<b>Labour Day</b><br>Gym Closed";
        
        // Thanksgiving - Second Monday in October
        $thanksgiving = new DateTime("second monday of october $year");
        $holidays[$thanksgiving->format('Y-m-d')] = "<b>Thanksgiving Day</b><br>Gym Closed";
        
        // Christmas Day - December 25
        $holidays[$year . '-12-25'] = "<b>Christmas Day</b><br>Gym Closed";
        
        return $holidays;
    }
    
    /**
     * Get calendar data for a specific month/year
     */
    public function getCalendarData($year, $month) {
        $start_date = date('Y-m-01', mktime(0, 0, 0, $month, 1, $year));
        $end_date = date('Y-m-t', mktime(0, 0, 0, $month, 1, $year));
        
        // Get class instances for the month
        $query = "
            SELECT 
                ci.id as instance_id,
                ci.class_date,
                ci.start_time,
                ci.end_time,
                ci.current_capacity,
                ci.max_capacity,
                ci.status,
                ci.special_notes,
                gc.id as class_id,
                gc.class_name,
                gc.description,
                gc.age_restriction,
                gc.gender_restriction,
                gc.instructor,
                gc.class_type
            FROM {$this->table_instances} ci
            JOIN {$this->table_classes} gc ON ci.class_id = gc.id
            WHERE ci.class_date BETWEEN %s AND %s
            AND ci.status = 'scheduled'
            AND gc.is_active = 1
            ORDER BY ci.class_date, ci.start_time
        ";
        
        $all_classes = $this->wpdb->get_results(
            $this->wpdb->prepare($query, $start_date, $end_date),
            ARRAY_A
        );
        
        // Filter out regular classes on holiday dates
        $holiday_dates = [];
        $filtered_classes = [];
        
        // First pass: identify holiday dates
        foreach ($all_classes as $class) {
            if ($class['class_type'] === 'holiday') {
                $holiday_dates[] = $class['class_date'];
            }
        }
        
        // Second pass: include holidays and exclude regular classes on holiday dates
        foreach ($all_classes as $class) {
            if ($class['class_type'] === 'holiday') {
                // Always include holidays
                $filtered_classes[] = $class;
            } elseif (!in_array($class['class_date'], $holiday_dates)) {
                // Include regular classes only if not on a holiday date
                $filtered_classes[] = $class;
            }
        }
        
        return $filtered_classes;
    }
    
    /**
     * Get user's registered classes
     */
    public function getUserRegistrations($user_id, $start_date = null, $end_date = null) {
        if (!$start_date) {
            $start_date = date('Y-m-d');
        }
        if (!$end_date) {
            $end_date = date('Y-m-d', strtotime('+6 months'));
        }
        
        $query = "
            SELECT 
                cr.id as registration_id,
                cr.status as registration_status,
                cr.registration_date,
                cr.credits_used,
                ci.id as instance_id,
                ci.class_date,
                ci.start_time,
                ci.end_time,
                gc.class_name,
                gc.instructor,
                gc.class_type
            FROM {$this->table_registrations} cr
            JOIN {$this->table_instances} ci ON cr.class_instance_id = ci.id
            JOIN {$this->table_classes} gc ON ci.class_id = gc.id
            WHERE cr.user_id = %d
            AND ci.class_date BETWEEN %s AND %s
            AND cr.status IN ('registered', 'attended')
            ORDER BY ci.class_date, ci.start_time
        ";
        
        return $this->wpdb->get_results(
            $this->wpdb->prepare($query, $user_id, $start_date, $end_date),
            ARRAY_A
        );
    }
    
    /**
     * Register user for a class instance
     */
    public function registerUserForClass($user_id, $instance_id, $credits_to_use = 60) {
        // Validate user has sufficient credits
        if (!$this->hasUserSufficientCredits($user_id, $credits_to_use)) {
            return ['success' => false, 'message' => 'Insufficient credits'];
        }
        
        // Check if class has capacity
        $instance = $this->getClassInstance($instance_id);
        if (!$instance) {
            return ['success' => false, 'message' => 'Class not found'];
        }
        
        if ($instance['current_capacity'] >= $instance['max_capacity']) {
            return ['success' => false, 'message' => 'Class is full'];
        }
        
        // Check if user already registered
        $existing = $this->wpdb->get_var(
            $this->wpdb->prepare(
                "SELECT id FROM {$this->table_registrations} 
                 WHERE user_id = %d AND class_instance_id = %d",
                $user_id, $instance_id
            )
        );
        
        if ($existing) {
            return ['success' => false, 'message' => 'Already registered for this class'];
        }
        
        // Start transaction
        $this->wpdb->query('START TRANSACTION');
        
        try {
            // Insert registration
            $result = $this->wpdb->insert(
                $this->table_registrations,
                [
                    'user_id' => $user_id,
                    'class_instance_id' => $instance_id,
                    'credits_used' => $credits_to_use,
                    'status' => 'registered'
                ],
                ['%d', '%d', '%d', '%s']
            );
            
            if ($result === false) {
                throw new Exception('Failed to insert registration');
            }
            
            // Update class capacity
            $this->wpdb->query(
                $this->wpdb->prepare(
                    "UPDATE {$this->table_instances} 
                     SET current_capacity = current_capacity + 1 
                     WHERE id = %d",
                    $instance_id
                )
            );
            
            // Deduct credits from user
            $this->deductUserCredits($user_id, $credits_to_use);
            
            $registration_id = $this->wpdb->insert_id;
            
            $this->wpdb->query('COMMIT');
            
            // Send confirmation email
            $this->sendConfirmationEmail($user_id, $registration_id);
            
            return [
                'success' => true, 
                'message' => 'Successfully registered for class',
                'registration_id' => $registration_id
            ];
            
        } catch (Exception $e) {
            $this->wpdb->query('ROLLBACK');
            return ['success' => false, 'message' => 'Registration failed: ' . $e->getMessage()];
        }
    }
    
    /**
     * Register user for multiple weeks of the same class
     */
    public function registerUserForMultipleWeeks($user_id, $instance_id, $weeks_count, $credits_per_class = 60) {
        error_log("CALENDAR DEBUG - registerUserForMultipleWeeks called:");
        error_log("user_id: " . $user_id);
        error_log("instance_id: " . $instance_id);
        error_log("weeks_count: " . $weeks_count);
        error_log("credits_per_class: " . $credits_per_class);
        
        // Get the base instance to determine class and timing
        $base_instance = $this->getClassInstance($instance_id);
        if (!$base_instance) {
            error_log("CALENDAR DEBUG - Base instance not found for ID: " . $instance_id);
            return ['success' => false, 'message' => 'Class not found'];
        }
        
        error_log("CALENDAR DEBUG - Base instance found: " . print_r($base_instance, true));
        
        // Calculate total credits needed
        $total_credits_needed = $credits_per_class * $weeks_count;
        
        // Validate user has sufficient credits
        if (!$this->hasUserSufficientCredits($user_id, $total_credits_needed)) {
            return ['success' => false, 'message' => "Insufficient credits. Need {$total_credits_needed} credits for {$weeks_count} weeks"];
        }
        
        // Find all instances for the requested weeks
        $instances_to_book = [];
        $base_date = new DateTime($base_instance['class_date']);
        
        for ($week = 0; $week < $weeks_count; $week++) {
            $target_date = clone $base_date;
            $target_date->add(new DateInterval('P' . ($week * 7) . 'D'));
            
            $instance = $this->wpdb->get_row(
                $this->wpdb->prepare(
                    "SELECT * FROM {$this->table_instances} 
                     WHERE class_id = %d AND class_date = %s AND start_time = %s",
                    $base_instance['class_id'], 
                    $target_date->format('Y-m-d'), 
                    $base_instance['start_time']
                ),
                ARRAY_A
            );
            
            if (!$instance) {
                return ['success' => false, 'message' => "Class not available for week " . ($week + 1) . " on " . $target_date->format('M j, Y')];
            }
            
            // Check capacity
            if ($instance['current_capacity'] >= $instance['max_capacity']) {
                return ['success' => false, 'message' => "Class is full for week " . ($week + 1) . " on " . $target_date->format('M j, Y')];
            }
            
            // Check if already registered
            $existing = $this->wpdb->get_var(
                $this->wpdb->prepare(
                    "SELECT id FROM {$this->table_registrations} 
                     WHERE user_id = %d AND class_instance_id = %d",
                    $user_id, $instance['id']
                )
            );
            
            if ($existing) {
                return ['success' => false, 'message' => "Already registered for week " . ($week + 1) . " on " . $target_date->format('M j, Y')];
            }
            
            $instances_to_book[] = $instance;
        }
        
        // Start transaction for all registrations
        $this->wpdb->query('START TRANSACTION');
        
        try {
            $registration_ids = [];
            
            // Register for each instance
            foreach ($instances_to_book as $instance) {
                // Insert registration
                $result = $this->wpdb->insert(
                    $this->table_registrations,
                    [
                        'user_id' => $user_id,
                        'class_instance_id' => $instance['id'],
                        'credits_used' => $credits_per_class,
                        'status' => 'registered'
                    ],
                    ['%d', '%d', '%d', '%s']
                );
                
                if ($result === false) {
                    throw new Exception('Failed to insert registration for ' . $instance['class_date']);
                }
                
                $registration_ids[] = $this->wpdb->insert_id;
                
                // Update class capacity
                $this->wpdb->query(
                    $this->wpdb->prepare(
                        "UPDATE {$this->table_instances} 
                         SET current_capacity = current_capacity + 1 
                         WHERE id = %d",
                        $instance['id']
                    )
                );
            }
            
            // Deduct total credits from user
            $this->deductUserCredits($user_id, $total_credits_needed);
            
            $this->wpdb->query('COMMIT');
            
            // Send confirmation email with all registrations
            $this->sendMultiWeekConfirmationEmail($user_id, $registration_ids);
            
            return [
                'success' => true, 
                'message' => "Successfully registered for {$weeks_count} weeks",
                'registration_ids' => $registration_ids,
                'weeks_count' => $weeks_count
            ];
            
        } catch (Exception $e) {
            $this->wpdb->query('ROLLBACK');
            return ['success' => false, 'message' => 'Multi-week registration failed: ' . $e->getMessage()];
        }
    }
 
    /**
     * Generate class instances for future dates
     */
    public function generateClassInstances($weeks_ahead = 8) {
        $classes = $this->wpdb->get_results(
            "SELECT * FROM {$this->table_classes} WHERE is_active = 1",
            ARRAY_A
        );
        
        $start_date = date('Y-m-d');
        $end_date = date('Y-m-d', strtotime("+{$weeks_ahead} weeks"));
        
        foreach ($classes as $class) {
            $this->generateInstancesForClass($class, $start_date, $end_date);
        }
    }
    
    /**
     * Generate instances for a specific class
     */
    private function generateInstancesForClass($class, $start_date, $end_date) {
        $current_date = new DateTime($start_date);
        $end_date_obj = new DateTime($end_date);
        
        while ($current_date <= $end_date_obj) {
            // Check if current date matches class day of week
            if ($current_date->format('w') == $class['day_of_week']) {
                // Check if instance already exists
                $existing = $this->wpdb->get_var(
                    $this->wpdb->prepare(
                        "SELECT id FROM {$this->table_instances} 
                         WHERE class_id = %d AND class_date = %s AND start_time = %s",
                        $class['id'], $current_date->format('Y-m-d'), $class['start_time']
                    )
                );
                
                if (!$existing) {
                    $this->wpdb->insert(
                        $this->table_instances,
                        [
                            'class_id' => $class['id'],
                            'class_date' => $current_date->format('Y-m-d'),
                            'start_time' => $class['start_time'],
                            'end_time' => $class['end_time'],
                            'max_capacity' => $class['max_capacity'],
                            'status' => 'scheduled'
                        ],
                        ['%d', '%s', '%s', '%s', '%d', '%s']
                    );
                }
            }
            $current_date->add(new DateInterval('P1D'));
        }
    }
 
    private function getClassInstance($instance_id) {
        return $this->wpdb->get_row(
            $this->wpdb->prepare(
                "SELECT * FROM {$this->table_instances} WHERE id = %d",
                $instance_id
            ),
            ARRAY_A
        );
    }
    
    private function hasUserSufficientCredits($user_id, $required_credits) {
        // Check for unlimited membership (admin users)
        $user_meta = get_userdata($user_id);
        if ($user_meta && in_array('administrator', $user_meta->roles)) {
            return true; // Admin users have unlimited membership
        }
        
        $credits = $this->wpdb->get_var(
            $this->wpdb->prepare(
                "SELECT credits_remaining FROM {$this->table_credits} WHERE user_id = %d AND is_active = 1",
                $user_id
            )
        );
        
        return ($credits >= $required_credits);
    }
    
    private function deductUserCredits($user_id, $credits) {
        $this->wpdb->query(
            $this->wpdb->prepare(
                "UPDATE {$this->table_credits} 
                 SET credits_remaining = credits_remaining - %d,
                     updated_at = NOW()
                 WHERE user_id = %d AND is_active = 1",
                $credits, $user_id
            )
        );
    }
    
    private function addUserCredits($user_id, $credits) {
        $this->wpdb->query(
            $this->wpdb->prepare(
                "UPDATE {$this->table_credits} 
                 SET credits_remaining = credits_remaining + %d,
                     updated_at = NOW()
                 WHERE user_id = %d AND is_active = 1",
                $credits, $user_id
            )
        );
    }
    
    private function getSetting($setting_name, $default = null) {
        $value = $this->wpdb->get_var(
            $this->wpdb->prepare(
                "SELECT setting_value FROM {$this->table_settings} WHERE setting_name = %s",
                $setting_name
            )
        );
        
        return $value !== null ? $value : $default;
    }
      
    /**
     * Send multi-week confirmation email to user
     */
    private function sendMultiWeekConfirmationEmail($user_id, $registration_ids) {
        $user = get_user_by('id', $user_id);
        if (!$user) {
            error_log("User not found for ID: {$user_id}");
            return false;
        }
        
        // Get all registration data
        $registrations_data = [];
        foreach ($registration_ids as $registration_id) {
            $data = $this->getRegistrationData($registration_id);
            if ($data) {
                $registrations_data[] = $data;
            }
        }
        
        if (empty($registrations_data)) {
            error_log("No registration data found for IDs: " . implode(', ', $registration_ids));
            return false;
        }
        
        // Sort by date
        usort($registrations_data, function($a, $b) {
            return strtotime($a['class_date']) - strtotime($b['class_date']);
        });
        
        $first_class = $registrations_data[0];
        $weeks_count = count($registrations_data);
        
        $subject = "Class Registration Confirmation - {$first_class['class_name']} ({$weeks_count} weeks)";
        $message = $this->generateMultiWeekEmailContent($user, $registrations_data);
        
        // Set email headers
        $headers = [
            'Content-Type: text/html; charset=UTF-8',
            'From: The Den Mixed Martial Arts <[email protected]>'
        ];
        
        // Generate multi-week iCal attachment if enabled
        $attachments = [];
        if ($this->isICalEnabled()) {
            $ical_file = $this->generateMultiWeekICalFile($registrations_data);
            if ($ical_file) {
                $attachments[] = $ical_file;
            }
        }
        
        // Check if email confirmations are enabled
        if ($this->getSetting('email_confirmations', 'true') !== 'true') {
            error_log("Email confirmations disabled - skipping multi-week email for registrations: " . implode(', ', $registration_ids));
            return true;
        }
        
        // Send email
        $sent = wp_mail($user->user_email, $subject, $message, $headers, $attachments);
        
        // Clean up temporary iCal file
        if (!empty($attachments)) {
            foreach ($attachments as $file) {
                if (file_exists($file)) {
                    unlink($file);
                }
            }
        }
        
        if ($sent) {
            error_log("Multi-week confirmation email sent successfully to {$user->user_email} for {$weeks_count} registrations");
        } else {
            error_log("CRITICAL: Failed to send multi-week confirmation email to {$user->user_email}");
        }
        
        return $sent;
    }
    
    /**
     * Send confirmation email after successful registration
     */
    private function sendConfirmationEmail($user_id, $registration_id) {
        // Get registration data with class details
        $registration_data = $this->getRegistrationData($registration_id);
        if (!$registration_data) {
            error_log("No registration data found for ID: {$registration_id}");
            return false;
        }
        
        $user = get_user_by('id', $user_id);
        if (!$user) {
            error_log("User not found for ID: {$user_id}");
            return false;
        }
        
        $subject = "Class Registration Confirmation - {$registration_data['class_name']}";
        $message = $this->generateEmailContent($user, $registration_data);
        
        // Set email headers
        $headers = [
            'Content-Type: text/html; charset=UTF-8',
            'From: The Den Mixed Martial Arts <[email protected]>'
        ];
        
        // Generate iCal attachment if enabled
        $attachments = [];
        if ($this->isICalEnabled()) {
            $ical_file = $this->generateICalFile($registration_data);
            if ($ical_file) {
                $attachments[] = $ical_file;
            }
        }
        
        // Check if email confirmations are enabled
        if ($this->getSetting('email_confirmations', 'true') !== 'true') {
            error_log("Email confirmations disabled - skipping email for registration: {$registration_id}");
            return true;
        }
        
        // Send email
        $sent = wp_mail($user->user_email, $subject, $message, $headers, $attachments);
        
        // Clean up temporary iCal file
        if (!empty($attachments) && file_exists($attachments[0])) {
            unlink($attachments[0]);
        }
        
        if ($sent) {
            error_log("Confirmation email sent successfully to {$user->user_email} for registration {$registration_id}");
        } else {
            error_log("CRITICAL: Failed to send confirmation email to {$user->user_email} for registration {$registration_id}");
        }
        
        return $sent;
    }
     
    private function generateEmailContent($user, $registration_data) {
        $class_date_formatted = date('l, F j, Y', strtotime($registration_data['class_date']));
        $start_time_formatted = date('g:i A', strtotime($registration_data['start_time']));
        $end_time_formatted = date('g:i A', strtotime($registration_data['end_time']));
        
        return "
        <style>
            body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
            .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
            .header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 3px solid #800000; }
            .header img { max-height: 80px; margin-bottom: 10px; }
            .header h1 { color: #800000; margin: 0; font-size: 28px; }
            .class-details { background: #f8f9fa; padding: 15px; margin: 15px 0; border-radius: 5px; border-left: 4px solid #800000; }
            .detail-row { margin: 8px 0; }
            .detail-label { font-weight: bold; color: #800000; }
            .detail-value { margin-left: 10px; }
            .btn { background: #800000; color: white !important; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block; margin: 10px 5px; }
            .logo { max-height: 60px; vertical-align: middle; margin-right: 15px; }
        </style>
        
        <div class='container'>
            <div class='header'>
                <img src='https://thedenmartialarts.ca/wp-content/uploads/2025/05/cropped-logoBlk.png' alt='The Den Mixed Martial Arts' class='logo'>
                <h1>Class Registration Confirmed!</h1>
            </div>
            
            <p>Hello {$user->first_name},</p>
            
            <p>Great news! You've successfully registered for a class at The Den Mixed Martial Arts. Here are your class details:</p>
            
            <div class='class-details'>
                <div class='detail-row'>
                    <span class='detail-label'>Class:</span>
                    <span class='detail-value'>{$registration_data['class_name']}</span>
                </div>
                <div class='detail-row'>
                    <span class='detail-label'>Date:</span>
                    <span class='detail-value'>{$class_date_formatted}</span>
                </div>
                <div class='detail-row'>
                    <span class='detail-label'>Time:</span>
                    <span class='detail-value'>{$start_time_formatted} - {$end_time_formatted}</span>
                </div>
                " . ($registration_data['instructor'] ? "
                <div class='detail-row'>
                    <span class='detail-label'>Instructor:</span>
                    <span class='detail-value'>{$registration_data['instructor']}</span>
                </div>
                " : "") . "
                <div class='detail-row'>
                    <span class='detail-label'>Credits Used:</span>
                    <span class='detail-value'>{$registration_data['credits_used']} minutes</span>
                </div>
            </div>
            
            <p><strong>Important:</strong> Please arrive 10-15 minutes before your class starts. Don't forget to bring water and a towel!</p>
            
            <div style='text-align: center; margin: 30px 0;'>
                <a href='https://thedenmartialarts.ca/my-membership/' class='btn'>View My Classes</a>
                <a href='https://thedenmartialarts.ca/gym-calendar/' class='btn'>Browse More Classes</a>
            </div>
            
            <p>If you need to cancel or reschedule, please contact us as soon as possible.</p>
            
            <p>Looking forward to seeing you on the mats!</p>
            
            <p>Best regards,<br>
            The Den Mixed Martial Arts Team<br>
            <a href='mailto:[email protected]'>[email protected]</a><br>
            (613) 831-3362</p>
        </div>";
    }
    
    private function generateMultiWeekEmailContent($user, $registrations_data) {
        $first_class = $registrations_data[0];
        $weeks_count = count($registrations_data);
        $total_credits = array_sum(array_column($registrations_data, 'credits_used'));
        
        return "
        <body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f5f5f5;\">
            <div class=\"container\" style=\"background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);\">
                <div class=\"header\" style=\"text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 3px solid #800000;\">
                    <img src=\"https://thedenmartialarts.ca/wp-content/uploads/2025/05/cropped-logoBlk.png\" alt=\"The Den Mixed Martial Arts\" style=\"max-height: 80px; margin-bottom: 10px;\">
                    <h1 style=\"color: #800000; margin: 0; font-size: 28px;\">Multi-Week Registration Confirmed!</h1>
                </div>
                
                <p>Hello {$user->first_name},</p>
                
                <p>Excellent! You've successfully registered for <strong>{$weeks_count} weeks</strong> of <strong>{$first_class['class_name']}</strong> at The Den Mixed Martial Arts.</p>
                
                <div class=\"class-schedule\" style=\"background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #800000;\">
                    <h3 style=\"color: #800000; margin-top: 0;\">Your Class Schedule</h3>";
        
        foreach ($registrations_data as $index => $registration) {
            $class_date_formatted = date('l, F j, Y', strtotime($registration['class_date']));
            $start_time_formatted = date('g:i A', strtotime($registration['start_time']));
            $end_time_formatted = date('g:i A', strtotime($registration['end_time']));
            
            $content .= "
                    <div class=\"week-entry\" style=\"margin-bottom: 15px; padding: 10px; background: white; border-radius: 5px;\">
                        <div style=\"font-weight: bold; color: #800000;\">Week " . ($index + 1) . "</div>
                        <div>{$class_date_formatted}</div>
                        <div>{$start_time_formatted} - {$end_time_formatted}</div>
                        " . ($registration['instructor'] ? "<div>{$registration['instructor']}</div>" : "") . "
                        <div>💳 {$registration['credits_used']} credits</div>
                    </div>";
        }
        
        $content .= "
                    <div style=\"margin-top: 15px; padding-top: 10px; border-top: 1px solid #ddd;\">
                        <strong>Total Credits Used: {$total_credits} minutes</strong>
                    </div>
                </div>
                
                <p><strong>What's Next?</strong></p>
                <ul>
                    <li>Mark your calendar for all {$weeks_count} classes</li>
                    <li>Arrive 10-15 minutes early for each session</li>
                    <li>Bring water and a towel to every class</li>
                    <li>Contact us if you need to reschedule any individual sessions</li>
                </ul>
                
                <div style=\"text-align: center; margin: 30px 0;\">
                    <a href=\"https://thedenmartialarts.ca/my-membership/\" style=\"display: inline-block; background: #800000; color: white; padding: 12px 25px; text-decoration: none; border-radius: 5px; margin: 0 10px;\">View My Classes</a>
                    <a href=\"https://thedenmartialarts.ca/gym-calendar/\" style=\"display: inline-block; background: #6c757d; color: white; padding: 12px 25px; text-decoration: none; border-radius: 5px; margin: 0 10px;\">Browse More Classes</a>
                </div>
                
                <p>We're excited to see your progress over the next {$weeks_count} weeks! Consistency is key to mastering martial arts.</p>
                
                <p>Best regards,<br>
                The Den Mixed Martial Arts Team<br>
                <a href=\"mailto:[email protected]\">[email protected]</a><br>
                (613) 831-3362</p>
            </div>
            <div class=\"footer\" style=\"text-align: center; margin-top: 30px; color: #666; font-size: 12px;\">
                <p>The Den Mixed Martial Arts | Stittsville, Ontario, Canada</p>
            </div>
        </body>";
        
        return $content;
    }
    
    private function getRegistrationData($registration_id) {
        return $this->wpdb->get_row(
            $this->wpdb->prepare(
                "SELECT 
                    cr.id as registration_id,
                    cr.credits_used,
                    cr.registration_date,
                    ci.class_date,
                    ci.start_time,
                    ci.end_time,
                    gc.class_name,
                    gc.description,
                    gc.instructor
                FROM {$this->table_registrations} cr
                JOIN {$this->table_instances} ci ON cr.class_instance_id = ci.id
                JOIN {$this->table_classes} gc ON ci.class_id = gc.id
                WHERE cr.id = %d",
                $registration_id
            ),
            ARRAY_A
        );
    }
    
    private function isICalEnabled() {
        return $this->getSetting('ical_attachments', 'true') === 'true';
    }
    
    /**
     * Generate iCal file for calendar import
     */
    private function generateICalFile($registration_data) {
        $class_date = $registration_data['class_date'];
        $start_time = $registration_data['start_time'];
        $end_time = $registration_data['end_time'];
        $class_name = $registration_data['class_name'];
        $description = $registration_data['description'] ?: '';
        $instructor = $registration_data['instructor'] ?: '';
        
        // Create datetime objects
        $start_datetime = new DateTime($class_date . ' ' . $start_time);
        $end_datetime = new DateTime($class_date . ' ' . $end_time);
        
        // Format for iCal (UTC)
        $start_datetime->setTimezone(new DateTimeZone('UTC'));
        $end_datetime->setTimezone(new DateTimeZone('UTC'));
        
        $start_ical = $start_datetime->format('Ymd\THis\Z');
        $end_ical = $end_datetime->format('Ymd\THis\Z');
        $now_ical = (new DateTime())->format('Ymd\THis\Z');
        
        // Generate unique ID
        $uid = 'gym-class-' . $registration_data['registration_id'] . '@thedenmartialarts.ca';
        
        // Create iCal content
        $ical_content = "BEGIN:VCALENDAR\r\n";
        $ical_content .= "VERSION:2.0\r\n";
        $ical_content .= "PRODID:-//The Den Mixed Martial Arts//Class Registration//EN\r\n";
        $ical_content .= "CALSCALE:GREGORIAN\r\n";
        $ical_content .= "METHOD:PUBLISH\r\n";
        $ical_content .= "BEGIN:VEVENT\r\n";
        $ical_content .= "UID:" . $uid . "\r\n";
        $ical_content .= "DTSTAMP:" . $now_ical . "\r\n";
        $ical_content .= "DTSTART:" . $start_ical . "\r\n";
        $ical_content .= "DTEND:" . $end_ical . "\r\n";
        $ical_content .= "SUMMARY:" . $class_name . "\r\n";
        
        $event_description = $description;
        if ($instructor) {
            $event_description .= ($event_description ? "\n" : "") . "Instructor: " . $instructor;
        }
        $event_description .= ($event_description ? "\n" : "") . "Location: The Den Mixed Martial Arts, Kanata, ON";
        
        if ($event_description) {
            $ical_content .= "DESCRIPTION:" . str_replace(["\n", "\r"], ["\\n", ""], $event_description) . "\r\n";
        }
        
        $ical_content .= "LOCATION:The Den Mixed Martial Arts, Stittsville, ON\r\n";
        $ical_content .= "STATUS:CONFIRMED\r\n";
        $ical_content .= "END:VEVENT\r\n";
        $ical_content .= "END:VCALENDAR\r\n";
        
        // Create temporary file
        $temp_file = tempnam(sys_get_temp_dir(), 'gym_class_') . '.ics';
        file_put_contents($temp_file, $ical_content);
        
        return $temp_file;
    }
    
    /**
     * Generate multi-week iCal file
     */
    private function generateMultiWeekICalFile($registrations_data) {
        if (empty($registrations_data)) {
            return false;
        }
        
        $now_ical = (new DateTime())->format('Ymd\THis\Z');
        
        // Create iCal content
        $ical_content = "BEGIN:VCALENDAR\r\n";
        $ical_content .= "VERSION:2.0\r\n";
        $ical_content .= "PRODID:-//The Den Mixed Martial Arts//Multi-Week Class Registration//EN\r\n";
        $ical_content .= "CALSCALE:GREGORIAN\r\n";
        $ical_content .= "METHOD:PUBLISH\r\n";
        
        foreach ($registrations_data as $registration) {
            $class_date = $registration['class_date'];
            $start_time = $registration['start_time'];
            $end_time = $registration['end_time'];
            $class_name = $registration['class_name'];
            $description = $registration['description'] ?: '';
            $instructor = $registration['instructor'] ?: '';
            
            // Create datetime objects
            $start_datetime = new DateTime($class_date . ' ' . $start_time);
            $end_datetime = new DateTime($class_date . ' ' . $end_time);
            
            // Format for iCal (UTC)
            $start_datetime->setTimezone(new DateTimeZone('UTC'));
            $end_datetime->setTimezone(new DateTimeZone('UTC'));
            
            $start_ical = $start_datetime->format('Ymd\THis\Z');
            $end_ical = $end_datetime->format('Ymd\THis\Z');
            
            // Generate unique ID
            $uid = 'gym-class-' . $registration['registration_id'] . '@thedenmartialarts.ca';
            
            $ical_content .= "BEGIN:VEVENT\r\n";
            $ical_content .= "UID:" . $uid . "\r\n";
            $ical_content .= "DTSTAMP:" . $now_ical . "\r\n";
            $ical_content .= "DTSTART:" . $start_ical . "\r\n";
            $ical_content .= "DTEND:" . $end_ical . "\r\n";
            $ical_content .= "SUMMARY:" . $class_name . "\r\n";
            
            $event_description = $description;
            if ($instructor) {
                $event_description .= ($event_description ? "\n" : "") . "Instructor: " . $instructor;
            }
            $event_description .= ($event_description ? "\n" : "") . "Location: The Den Mixed Martial Arts, Kanata, ON";
            
            if ($event_description) {
                $ical_content .= "DESCRIPTION:" . str_replace(["\n", "\r"], ["\\n", ""], $event_description) . "\r\n";
            }
            
            $ical_content .= "LOCATION:The Den Mixed Martial Arts, Stittsville, ON\r\n";
            $ical_content .= "STATUS:CONFIRMED\r\n";
            $ical_content .= "END:VEVENT\r\n";
        }
        
        $ical_content .= "END:VCALENDAR\r\n";
        
        // Create temporary file
        $temp_file = tempnam(sys_get_temp_dir(), 'gym_multiweek_') . '.ics';
        file_put_contents($temp_file, $ical_content);
        
        return $temp_file;
    }
    
    /**
     * Test email system functionality
     */
    public function testEmailSystem($test_email) {
        // Create test registration data
        $test_data = [
            'registration_id' => 999999,
            'class_name' => 'Test Class - Brazilian Jiu-Jitsu',
            'class_date' => date('Y-m-d', strtotime('+1 day')),
            'start_time' => '19:00:00',
            'end_time' => '20:30:00',
            'instructor' => 'Test Instructor',
            'description' => 'This is a test email to verify the email system is working correctly.',
            'credits_used' => 90
        ];
        
        // Create test user object
        $test_user = (object) [
            'first_name' => 'Test',
            'last_name' => 'Member',
            'user_email' => $test_email
        ];
        
        $subject = "Test Email - Calendar System Working";
        $message = $this->generateEmailContent($test_user, $test_data);
        
        $headers = [
            'Content-Type: text/html; charset=UTF-8',
            'From: The Den Mixed Martial Arts <[email protected]>'
        ];
        
        $sent = wp_mail($test_email, $subject, $message, $headers);
        
        if ($sent) {
            return ['success' => true, 'message' => "Test email sent successfully to {$test_email}"];
        } else {
            return ['success' => false, 'message' => "Failed to send test email to {$test_email}"];
        }
    }
}
class GymCalendarShortcode {
    
    private $calendar_core;
    
    public function __construct() {
        $this->calendar_core = new GymCalendarCore();
        
        // Register shortcode
        add_shortcode('gym_calendar', [$this, 'renderCalendar']);
        
        // Register AJAX handlers
        add_action('wp_ajax_gym_calendar_action', [$this, 'handleAjaxRequest']);
        add_action('wp_ajax_nopriv_gym_calendar_action', [$this, 'handleAjaxRequest']);
        
        // Enqueue scripts and styles
        add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
    }
    
    /**
     * Enqueue necessary scripts and styles
     */
    public function enqueueAssets() {
        // Only enqueue on pages that use the shortcode
        global $post;
        if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'gym_calendar')) {
            wp_enqueue_script('jquery');
        }
    }
    
    /**
     * Render the calendar shortcode
     */
    public function renderCalendar($atts = []) {
        // Set default attributes
        $atts = shortcode_atts([
            'month' => date('n'),
            'year' => date('Y')
        ], $atts);
        
        $month = intval($atts['month']);
        $year = intval($atts['year']);
        
        // Get calendar data
        $classes = $this->calendar_core->getCalendarData($year, $month);
        
        // Get user registrations if logged in
        $user_classes = [];
        if (is_user_logged_in()) {
            $user_id = get_current_user_id();
            $start_date = date('Y-m-01', mktime(0, 0, 0, $month, 1, $year));
            $end_date = date('Y-m-t', mktime(0, 0, 0, $month, 1, $year));
            $user_classes = $this->calendar_core->getUserRegistrations($user_id, $start_date, $end_date);
        }
        
        // Generate calendar HTML
        $html = $this->generateCalendarHTML($year, $month, $classes, $user_classes);
        $html .= $this->generateModalHTML();
        $html .= $this->generateJavaScript();
        
        return $html;
    }
    
    /**
     * Generate the main calendar HTML structure
     */
    private function generateCalendarHTML($year, $month, $classes, $user_classes) {
        $month_name = date('F', mktime(0, 0, 0, $month, 1, $year));
        
        // Navigation dates
        $prev_month = $month - 1;
        $prev_year = $year;
        if ($prev_month < 1) {
            $prev_month = 12;
            $prev_year--;
        }
        
        $next_month = $month + 1;
        $next_year = $year;
        if ($next_month > 12) {
            $next_month = 1;
            $next_year++;
        }
        
        $html = '<div id="gym-calendar-container">';
        $html .= '<div class="calendar-header">';
        $html .= '<div class="calendar-nav">';
        $html .= '<button class="nav-btn prev-month" data-year="' . $prev_year . '" data-month="' . $prev_month . '">‹ Previous</button>';
        $html .= '<h2 class="calendar-title">' . $month_name . ' ' . $year . '</h2>';
        $html .= '<button class="nav-btn next-month" data-year="' . $next_year . '" data-month="' . $next_month . '">Next ›</button>';
        $html .= '</div>';
        $html .= '</div>';
        
        $html .= '<div class="calendar-grid">';
        
        // Header row with day names
        $html .= '<div class="calendar-row calendar-header-row">';
        $days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
        foreach ($days as $day) {
            $html .= '<div class="calendar-day-header">' . $day . '</div>';
        }
        $html .= '</div>';
        
        // Generate calendar days
        $first_day = date('w', mktime(0, 0, 0, $month, 1, $year));
        $days_in_month = date('t', mktime(0, 0, 0, $month, 1, $year));
        $today = date('Y-m-d');
        
        // Group classes by date
        $classes_by_date = [];
        foreach ($classes as $class) {
            $date = $class['class_date'];
            if (!isset($classes_by_date[$date])) {
                $classes_by_date[$date] = [];
            }
            $classes_by_date[$date][] = $class;
        }
        
        $current_day = 1;
        $week_count = 0;
        
        while ($current_day <= $days_in_month) {
            $html .= '<div class="calendar-row">';
            
            for ($day_of_week = 0; $day_of_week < 7; $day_of_week++) {
                if ($week_count == 0 && $day_of_week < $first_day) {
                    // Empty cells before month starts
                    $html .= '<div class="calendar-day empty"></div>';
                } elseif ($current_day > $days_in_month) {
                    // Empty cells after month ends
                    $html .= '<div class="calendar-day empty"></div>';
                } else {
                    // Actual calendar day
                    $date = sprintf('%04d-%02d-%02d', $year, $month, $current_day);
                    $day_classes = $classes_by_date[$date] ?? [];
                    
                    $today_class = ($date === $today) ? ' today' : '';
                    
                    $html .= '<div class="calendar-day' . $today_class . '" data-date="' . $date . '">';
                    $html .= '<div class="day-number">' . $current_day . '</div>';
                    
                    if (!empty($day_classes)) {
                        $html .= '<div class="day-classes">';
                        foreach ($day_classes as $class) {
                            $html .= $this->generateClassItemHTML($class, $user_classes);
                        }
                        $html .= '</div>';
                    }
                    
                    $html .= '</div>';
                    $current_day++;
                }
            }
            
            $html .= '</div>';
            $week_count++;
        }
        
        $html .= '</div>'; // calendar-grid
        $html .= '</div>'; // gym-calendar-container
        
        return $html;
    }
    
    /**
     * Generate HTML for individual class items
     */
    private function generateClassItemHTML($class, $user_classes) {
        $start_time = date('g:i A', strtotime($class['start_time']));
        $is_registered = $this->isUserRegistered($class['instance_id'], $user_classes);
        $is_full = ($class['current_capacity'] >= $class['max_capacity']);
        $is_holiday = ($class['class_type'] === 'holiday');
        
        $css_classes = ['class-item'];
        
        if ($is_holiday) {
            $css_classes[] = 'holiday';
            $html = '<div class="' . implode(' ', $css_classes) . '">';
            $html .= '<span class="holiday-name"><b>' . htmlspecialchars($class['special_notes']) . '</b><br>Gym Closed</span>';
            $html .= '</div>';
            return $html;
        }
        
        if ($is_registered) {
            $css_classes[] = 'registered';
        } elseif ($is_full) {
            $css_classes[] = 'full';
        }
        
        $html = '<div class="' . implode(' ', $css_classes) . '"';
        $html .= ' data-instance-id="' . $class['instance_id'] . '"';
        $html .= ' data-class-name="' . htmlspecialchars($class['class_name']) . '"';
        $html .= ' data-start-time="' . $class['start_time'] . '"';
        $html .= ' data-end-time="' . $class['end_time'] . '"';
        $html .= ' data-capacity="' . $class['max_capacity'] . '"';
        $html .= ' data-current="' . $class['current_capacity'] . '"';
        $html .= ' data-is-registered="' . ($is_registered ? 'true' : 'false') . '"';
        
        if ($is_registered) {
            $registration = $this->getUserRegistration($class['instance_id'], $user_classes);
            $html .= ' data-registration-id="' . $registration['registration_id'] . '"';
        }
        
        $html .= '>';
        $html .= '<span class="class-time">' . $start_time . '</span>';
        $html .= '<span class="class-name">' . htmlspecialchars($class['class_name']) . '</span>';
        
        if ($is_registered) {
            $html .= '<span class="registered-indicator">✓</span>';
        } elseif ($is_full) {
            $html .= '<span class="full-indicator">FULL</span>';
        }
        
        $html .= '</div>';
        
        return $html;
    }
    
    /**
     * Generate modal HTML structure
     */
    private function generateModalHTML() {
        return '
        <!-- Booking Modal -->
        <div id="booking-modal" class="modal-overlay" style="display: none;">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 id="modal-class-title"></h3>
                    <button class="modal-close">&times;</button>
                </div>
                <div class="modal-body">
                    <div id="modal-class-details"></div>
                    <div id="modal-booking-options"></div>
                </div>
                <div class="modal-footer">
                    <button id="confirm-booking" class="btn btn-primary">Register for Class</button>
                </div>
            </div>
        </div>
        
        <!-- Notification Modal -->
        <div id="notification-modal" class="modal-overlay" style="display: none;">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 id="notification-title"></h3>
                    <button class="modal-close">&times;</button>
                </div>
                <div class="modal-body">
                    <div id="notification-message"></div>
                </div>
                <div class="modal-footer">
                    <button id="notification-ok" class="btn btn-primary">OK</button>
                </div>
            </div>
        </div>';
    }
     
    /**
     * Generate JavaScript for calendar interactions
     */
    private function generateJavaScript() {
        // Get current user data
        $user_id = get_current_user_id();
        $is_logged_in = is_user_logged_in() ? 'true' : 'false';
        $nonce = wp_create_nonce('gym_calendar_nonce');
        
        // Get user credits if logged in
        $user_credits = 0;
        $unlimited_membership = false;
        
        if ($user_id > 0) {
            global $wpdb;
            $table_credits = $wpdb->prefix . 'gym_credits';
            
            $credits_result = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT credits_remaining, membership_type FROM {$table_credits} WHERE user_id = %d AND is_active = 1",
                    $user_id
                ),
                ARRAY_A
            );
            
            if ($credits_result) {
                $user_credits = intval($credits_result['credits_remaining']);
                $membership_type = $credits_result['membership_type'];
            } else {
                $user_credits = 0;
                $membership_type = '';
            }
            
            // Check if user is admin
            $is_admin = current_user_can('administrator');
            
            // Check for unlimited membership conditions - be more permissive for testing
            $unlimited_membership = (
                $membership_type === 'unlimited' || 
                $user_credits == -1 || 
                $user_credits === '-1' ||
                $is_admin ||
                $membership_type === 'admin' ||
                $membership_type === 'staff' ||
                $membership_type === 'trainer' ||
                // Temporary: Allow all logged in users for debugging
                ($user_id > 0 && empty($membership_type) && $user_credits == 0)
            );
        }
        
        return "
        <script>
        (function() {
            'use strict';
            
            // Calendar namespace isolation - prevent conflicts with other scripts
            const CalendarSystem = {
                initialized: false,
                
                // Safe initialization with error handling
                init: function() {
                    if (this.initialized) return;
                    
                    try {
                        this.waitForDependencies();
                    } catch (error) {
                        console.error('Calendar System initialization error:', error);
                        this.fallbackInit();
                    }
                },
                
                // Wait for required dependencies with timeout
                waitForDependencies: function() {
                    const self = this;
                    let attempts = 0;
                    const maxAttempts = 50; // 5 seconds max wait
                    
                    const checkDependencies = function() {
                        attempts++;
                        
                        if (typeof jQuery !== 'undefined' && document.readyState === 'complete') {
                            self.setupCalendar();
                            return;
                        }
                        
                        if (attempts < maxAttempts) {
                            setTimeout(checkDependencies, 100);
                        } else {
                            console.warn('Calendar System: Dependencies timeout, attempting fallback');
                            self.fallbackInit();
                        }
                    };
                    
                    checkDependencies();
                },
                
                // Fallback initialization if dependencies fail
                fallbackInit: function() {
                    console.log('Calendar System: Using fallback initialization');
                    // Basic functionality without full jQuery support
                    this.setupBasicEventHandlers();
                },
                
                // Main calendar setup with error isolation
                setupCalendar: function() {
                    const $ = jQuery;
                    
                    try {
                        // Calendar AJAX configuration - isolated in namespace
                        this.config = {
                            ajaxurl: '" . admin_url('admin-ajax.php') . "',
                            nonce: '" . $nonce . "',
                            is_user_logged_in: " . $is_logged_in . ",
                            current_user_id: " . $user_id . ",
                            user_credits: " . $user_credits . ",
                            unlimited_membership: " . ($unlimited_membership ? 'true' : 'false') . "
                        };
                        
                        // Also set global for backward compatibility, but safely
                        if (!window.gymCalendar) {
                            window.gymCalendar = this.config;
                        }
                        
                        this.setupEventHandlers($);
                        this.initialized = true;
                        
                        console.log('Calendar System: Successfully initialized');
                        
                    } catch (error) {
                        console.error('Calendar System setup error:', error);
                        this.fallbackInit();
                    }
                },
                
                // Setup event handlers with error protection
                setupEventHandlers: function($) {
                    const self = this;
                    
                    try {
                        // Navigation button clicks with error handling
                        $(document).on('click', '.prev-month, .next-month', function(e) {
                            try {
                                e.preventDefault();
                                const year = $(this).data('year');
                                const month = $(this).data('month');
                                self.loadCalendarMonth(year, month, $);
                            } catch (error) {
                                console.error('Navigation click error:', error);
                            }
                        });
                        
                        // Class item clicks with error protection
                        $(document).on('click', '.class-item', function(e) {
                            try {
                                self.handleClassClick(e, $(this), $);
                            } catch (error) {
                                console.error('Class click error:', error);
                            }
                        });
                        
                        // Modal event handlers with error protection
                        $(document).on('click', '#confirm-booking', function() {
                            try {
                                self.handleBookingConfirm($(this), $);
                            } catch (error) {
                                console.error('Booking confirm error:', error);
                            }
                        });
                        
                        // Other modal handlers
                        $(document).on('change', 'input[name=\"booking-type\"]', function() {
                            try {
                                if ($(this).val() === 'recurring') {
                                    $('#weeks-selection').show();
                                } else {
                                    $('#weeks-selection').hide();
                                }
                            } catch (error) {
                                console.error('Booking type change error:', error);
                            }
                        });
                        
                    } catch (error) {
                        console.error('Event handler setup error:', error);
                    }
                },
                
                // Load calendar month with error handling
                loadCalendarMonth: function(year, month, $) {
                    try {
                        const container = $('#gym-calendar-container');
                        if (container.length === 0) {
                            console.error('Calendar container not found');
                            return;
                        }
                        
                        container.html('<div style=\"text-align: center; padding: 40px;\">Loading...</div>');
                        
                        $.ajax({
                            url: this.config.ajaxurl,
                            type: 'POST',
                            data: {
                                action: 'gym_calendar_action',
                                calendar_action: 'load_month',
                                year: year,
                                month: month,
                                nonce: this.config.nonce
                            },
                            success: function(response) {
                                try {
                                    if (response.success) {
                                        container.html(response.data.html);
                                    } else {
                                        container.html('<div style=\"text-align: center; padding: 40px; color: red;\">Error loading calendar: ' + (response.data || 'Unknown error') + '</div>');
                                    }
                                } catch (error) {
                                    console.error('AJAX success handler error:', error);
                                    container.html('<div style=\"text-align: center; padding: 40px; color: red;\">Error processing response</div>');
                                }
                            },
                            error: function(xhr, status, error) {
                                console.error('AJAX request failed:', error);
                                container.html('<div style=\"text-align: center; padding: 40px; color: red;\">Failed to load calendar. Please refresh the page.</div>');
                            }
                        });
                        
                    } catch (error) {
                        console.error('loadCalendarMonth error:', error);
                    }
                },
                
                // Handle class item clicks with error protection
                handleClassClick: function(e, element, $) {
                    try {
                        e.preventDefault();
                        e.stopPropagation();
                        
                        if (element.hasClass('full')) {
                            this.showNotificationModal('Class Full', 'This class is currently full. Please try another class or check back later.', $);
                            return;
                        }
                        
                        if (element.hasClass('holiday')) {
                            console.log('Holiday hover - cursor should remain default, no color change');
                            return;
                        }
                        
                        if (!this.config.is_user_logged_in) {
                            this.showNotificationModal('Login Required', 'Please log in to register for classes.', $);
                            return;
                        }
                        
                        // Check credit availability (skip for unlimited membership)
                        if (!this.config.unlimited_membership && this.config.user_credits < 1) {
                            this.showNotificationModal('Insufficient Credits', 'You don\\'t have enough credits to register for this class. Please purchase more credits or upgrade to an unlimited membership.', $);
                            return;
                        }
                        
                        const instanceId = element.data('instance-id');
                        const className = element.data('class-name');
                        const startTime = element.data('start-time');
                        const endTime = element.data('end-time');
                        const capacity = element.data('capacity');
                        const isRegistered = element.data('is-registered');
                        const registrationId = element.data('registration-id');
                        const date = element.closest('.calendar-day').data('date');
                        
                        $('#modal-class-title').text(isRegistered ? 'Reserved Spot' : className);
                        
                        if (isRegistered) {
                            $('#modal-class-details').html('<p>You have a reserved spot in this class. You will not be charged until you arrive at the Gym. If you cannot make this class, kindly let us know. Thanks!</p>');
                            $('#modal-booking-options').hide();
                            $('#confirm-booking').text('OK').data('action', 'acknowledge').data('instance-id', instanceId);
                        } else {
                            const startFormatted = this.formatTime(startTime);
                            const endFormatted = this.formatTime(endTime);
                            
                            // Calculate actual duration in minutes
                            const durationMinutes = this.calculateDuration(startTime, endTime);
                            
                            const current = element.data('current');
                            const max = capacity;
                            const remaining = max - current;
                            
                            $('#modal-class-details').html(
                                '<div class=\"class-detail-item\">' +
                                '<div class=\"class-detail-label\">Time:</div>' +
                                '<div class=\"class-detail-value\">' + startFormatted + ' - ' + endFormatted + '</div>' +
                                '</div>' +
                                '<div class=\"class-detail-item\">' +
                                '<div class=\"class-detail-label\">Remaining spots available:</div>' +
                                '<div class=\"class-detail-value\">' + remaining + '</div>' +
                                '</div>'
                            );
                            
                            // Show registration options using template approach
                            var registrationTemplate = document.createElement('div');
                            registrationTemplate.innerHTML = [
                                '<h4>Registration Options</h4>',
                                '<div class=\"registration-options\">',
                                '<label class=\"registration-label\">',
                                '<input type=\"radio\" name=\"booking-type\" value=\"single\" checked class=\"registration-radio\">',
                                'Single Class (' + durationMinutes + ' minutes)',
                                '</label>',
                                '<label class=\"registration-label\">',
                                '<input type=\"radio\" name=\"booking-type\" value=\"recurring\" class=\"registration-radio\">',
                                'Multiple Weeks',
                                '</label>',
                                '</div>',
                                '<div id=\"weeks-selection\" class=\"weeks-selection\">',
                                '<label for=\"weeks-count\" class=\"weeks-count-label\">Number of weeks:</label>',
                                '<select id=\"weeks-count\" class=\"weeks-count-select\">',
                                '<option value=\"1\">1 week</option>',
                                '<option value=\"2\">2 weeks</option>',
                                '<option value=\"3\">3 weeks</option>',
                                '<option value=\"4\">4 weeks</option>',
                                '<option value=\"5\">5 weeks</option>',
                                '<option value=\"6\">6 weeks</option>',
                                '<option value=\"7\">7 weeks</option>',
                                '<option value=\"8\">8 weeks</option>',
                                '<option value=\"9\">9 weeks</option>',
                                '<option value=\"10\">10 weeks</option>',
                                '<option value=\"11\">11 weeks</option>',
                                '<option value=\"12\">12 weeks</option>',
                                '</select>',
                                '</div>'
                            ].join('');
                            
                            $('#modal-booking-options').html(registrationTemplate.innerHTML).show();
                            
                            $('#confirm-booking').text('Register for Class').data('action', 'register').data('instance-id', instanceId);
                        }
                        
                        $('body').addClass('modal-open');
                        $('#booking-modal').fadeIn(300);
                        
                    } catch (error) {
                        console.error('handleClassClick error:', error);
                    }
                },
                
                // Handle booking confirmation with error protection
                handleBookingConfirm: function(button, $) {
                    try {
                        const action = button.data('action');
                        const instanceId = button.data('instance-id');
                        
                        if (action === 'acknowledge') {
                            // Simply close the modal for acknowledgment
                            this.closeModal($);
                            return;
                        }
                        
                        if (action === 'register') {
                            const bookingType = $('input[name=\"booking-type\"]:checked').val();
                            
                            if (bookingType === 'recurring') {
                                const weeksCount = parseInt($('#weeks-count').val());
                                this.registerForMultipleWeeks(instanceId, weeksCount, $);
                            } else {
                                this.registerForSingleClass(instanceId, $);
                            }
                        }
                        
                    } catch (error) {
                        console.error('handleBookingConfirm error:', error);
                    }
                },
                
                // Register for single class with error handling
                registerForSingleClass: function(instanceId, $) {
                    try {
                        const self = this;
                        
                        $.ajax({
                            url: this.config.ajaxurl,
                            type: 'POST',
                            data: {
                                action: 'gym_calendar_action',
                                calendar_action: 'register',
                                instance_id: instanceId,
                                nonce: this.config.nonce
                            },
                            success: function(response) {
                                try {
                                    if (response.success) {
                                        self.showNotificationModal('Registration Successful', response.data.message, $);
                                        // Reload current month to update display
                                        const currentDate = new Date();
                                        self.loadCalendarMonth(currentDate.getFullYear(), currentDate.getMonth() + 1, $);
                                    } else {
                                        self.showNotificationModal('Registration Failed', response.data || 'An error occurred during registration.', $);
                                    }
                                } catch (error) {
                                    console.error('Registration success handler error:', error);
                                    self.showNotificationModal('Error', 'An unexpected error occurred.', $);
                                }
                            },
                            error: function(xhr, status, error) {
                                console.error('Registration AJAX error:', error);
                                self.showNotificationModal('Error', 'Failed to register for class. Please try again.', $);
                            }
                        });
                        
                    } catch (error) {
                        console.error('registerForSingleClass error:', error);
                    }
                },
                
                // Register for multiple weeks with error handling
                registerForMultipleWeeks: function(instanceId, weeksCount, $) {
                    try {
                        const self = this;
                        
                        $.ajax({
                            url: this.config.ajaxurl,
                            type: 'POST',
                            data: {
                                action: 'gym_calendar_action',
                                calendar_action: 'register_multiple',
                                instance_id: instanceId,
                                weeks_count: weeksCount,
                                nonce: this.config.nonce
                            },
                            success: function(response) {
                                try {
                                    if (response.success) {
                                        self.showNotificationModal('Multi-Week Registration Successful', response.data.message, $);
                                        // Reload current month to update display
                                        const currentDate = new Date();
                                        self.loadCalendarMonth(currentDate.getFullYear(), currentDate.getMonth() + 1, $);
                                    } else {
                                        self.showNotificationModal('Registration Failed', response.data || 'An error occurred during multi-week registration.', $);
                                    }
                                } catch (error) {
                                    console.error('Multi-week registration success handler error:', error);
                                    self.showNotificationModal('Error', 'An unexpected error occurred.', $);
                                }
                            },
                            error: function(xhr, status, error) {
                                console.error('Multi-week registration AJAX error:', error);
                                self.showNotificationModal('Error', 'Failed to register for multiple weeks. Please try again.', $);
                            }
                        });
                        
                    } catch (error) {
                        console.error('registerForMultipleWeeks error:', error);
                    }
                },
                
                // Show notification modal with error protection
                showNotificationModal: function(title, message, $) {
                    try {
                        $('#notification-title').text(title);
                        $('#notification-message').html(message);
                        this.closeModal($);
                        $('#notification-modal').fadeIn(300);
                        
                        // Auto-close notification modal after clicking OK
                        $('#notification-ok').off('click').on('click', function() {
                            $('#notification-modal').fadeOut(300);
                            $('body').removeClass('modal-open');
                        });
                        
                        // Close on background click
                        $('#notification-modal').off('click').on('click', function(e) {
                            if (e.target === this) {
                                $(this).fadeOut(300);
                                $('body').removeClass('modal-open');
                            }
                        });
                        
                    } catch (error) {
                        console.error('showNotificationModal error:', error);
                    }
                },
                
                // Close modal with error protection
                closeModal: function($) {
                    try {
                        $('#booking-modal').fadeOut(300);
                        $('body').removeClass('modal-open');
                    } catch (error) {
                        console.error('closeModal error:', error);
                    }
                },
                
                // Setup basic event handlers for fallback mode
                setupBasicEventHandlers: function() {
                    try {
                        // Basic modal close functionality without jQuery
                        document.addEventListener('click', function(e) {
                            if (e.target.classList.contains('modal-close')) {
                                const modal = e.target.closest('.modal-overlay');
                                if (modal) {
                                    modal.style.display = 'none';
                                    document.body.classList.remove('modal-open');
                                }
                            }
                        });
                        
                        console.log('Calendar System: Basic event handlers setup complete');
                    } catch (error) {
                        console.error('setupBasicEventHandlers error:', error);
                    }
                },
                
                // Utility functions with error protection
                calculateDuration: function(startTime, endTime) {
                    try {
                        const start = this.timeToMinutes(startTime);
                        const end = this.timeToMinutes(endTime);
                        return Math.max(0, end - start);
                    } catch (error) {
                        console.error('calculateDuration error:', error);
                        return 60; // Default fallback
                    }
                },
                
                formatTime: function(timeStr) {
                    try {
                        const [hours, minutes] = timeStr.split(':').map(Number);
                        const date = new Date();
                        date.setHours(hours, minutes);
                        return date.toLocaleTimeString('en-US', { 
                            hour: 'numeric', 
                            minute: '2-digit',
                            hour12: true 
                        });
                    } catch (error) {
                        return timeStr;
                    }
                },
                
                timeToMinutes: function(timeStr) {
                    try {
                        const [hours, minutes] = timeStr.split(':').map(Number);
                        return hours * 60 + minutes;
                    } catch (error) {
                        return 0;
                    }
                }
            };
            
            // Initialize calendar system
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', function() {
                    CalendarSystem.init();
                });
            } else {
                CalendarSystem.init();
            }
            
        })();
        
        // Legacy compatibility - remove old duplicate handlers
        if (typeof loadCalendarMonth !== 'undefined') {
            delete window.loadCalendarMonth;
        }
        
        </script>";
    }
    
    /**
     * Check if user is registered for a specific class instance
     */
    private function isUserRegistered($instance_id, $user_classes) {
        foreach ($user_classes as $class) {
            if ($class['instance_id'] == $instance_id) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Get user registration details for a specific instance
     */
    private function getUserRegistration($instance_id, $user_classes) {
        foreach ($user_classes as $class) {
            if ($class['instance_id'] == $instance_id) {
                return $class;
            }
        }
        return null;
    }
     
    /**
     * Handle AJAX requests from the calendar frontend
     */
    public function handleAjaxRequest() {
        // Verify nonce
        if (!wp_verify_nonce($_POST['nonce'], 'gym_calendar_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        
        $calendar_action = sanitize_text_field($_POST['calendar_action']);
        
        switch ($calendar_action) {
            case 'load_month':
                $this->handleLoadMonth();
                break;
                
            case 'register':
                $this->handleRegistration();
                break;
                
            case 'register_multiple':
                $this->handleMultipleWeekRegistration();
                break;
                
            case 'test_email':
                $this->handleTestEmail();
                break;
                
            default:
                wp_send_json_error('Invalid action');
        }
    }
    
    private function handleLoadMonth() {
        $year = intval($_POST['year']);
        $month = intval($_POST['month']);
        
        if ($year < 2020 || $year > 2030 || $month < 1 || $month > 12) {
            wp_send_json_error('Invalid date');
            return;
        }
        
        // Get calendar data
        $classes = $this->calendar_core->getCalendarData($year, $month);
        
        // Get user registrations if logged in
        $user_classes = [];
        if (is_user_logged_in()) {
            $user_id = get_current_user_id();
            $start_date = date('Y-m-01', mktime(0, 0, 0, $month, 1, $year));
            $end_date = date('Y-m-t', mktime(0, 0, 0, $month, 1, $year));
            $user_classes = $this->calendar_core->getUserRegistrations($user_id, $start_date, $end_date);
        }
        
        // Generate new calendar HTML
        $html = $this->generateCalendarHTML($year, $month, $classes, $user_classes);
        
        wp_send_json_success(['html' => $html]);
    }
    
    private function handleRegistration() {
        if (!is_user_logged_in()) {
            wp_send_json_error('Must be logged in to register');
            return;
        }
        
        $instance_id = intval($_POST['instance_id']);
        $user_id = get_current_user_id();
        
        $result = $this->calendar_core->registerUserForClass($user_id, $instance_id);
        
        if ($result['success']) {
            wp_send_json_success($result);
        } else {
            wp_send_json_error($result['message']);
        }
    }
    
    private function handleMultipleWeekRegistration() {
        if (!is_user_logged_in()) {
            wp_send_json_error('Must be logged in to register');
            return;
        }
        
        $instance_id = intval($_POST['instance_id']);
        $weeks_count = intval($_POST['weeks_count']);
        $user_id = get_current_user_id();
        
        if ($weeks_count < 1 || $weeks_count > 12) {
            wp_send_json_error('Invalid weeks count');
            return;
        }
        
        $result = $this->calendar_core->registerUserForMultipleWeeks($user_id, $instance_id, $weeks_count);
        
        if ($result['success']) {
            wp_send_json_success($result);
        } else {
            wp_send_json_error($result['message']);
        }
    }
    
    private function handleTestEmail() {
        if (!current_user_can('administrator')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        $test_email = sanitize_email($_POST['test_email'] ?? '');
        if (!$test_email) {
            $user = wp_get_current_user();
            $test_email = $user->user_email;
        }
        
        $result = $this->calendar_core->testEmailSystem($test_email);
        
        if ($result['success']) {
            wp_send_json_success($result['message']);
        } else {
            wp_send_json_error($result['message']);
        }
    }
}
// Initialize the calendar system
new GymCalendarShortcode();

Comments

Add a Comment