init_dependencies(); $this->register_hooks(); // Debug logging if ( class_exists( 'HVAC_Logger' ) ) { HVAC_Logger::info( 'HVAC_Communication_Scheduler initialized', 'Scheduler' ); } } /** * Initialize dependencies */ private function init_dependencies() { require_once HVAC_PLUGIN_DIR . 'includes/communication/class-communication-schedule-manager.php'; require_once HVAC_PLUGIN_DIR . 'includes/communication/class-communication-trigger-engine.php'; require_once HVAC_PLUGIN_DIR . 'includes/communication/class-communication-logger.php'; $this->schedule_manager = new HVAC_Communication_Schedule_Manager(); $this->trigger_engine = new HVAC_Communication_Trigger_Engine(); $this->logger = new HVAC_Communication_Logger(); } /** * Register WordPress hooks */ private function register_hooks() { // Cron hooks add_action( 'hvac_process_communication_schedules', array( $this, 'process_scheduled_communications' ) ); // Event-based triggers add_action( 'tribe_events_event_save_after', array( $this, 'on_event_saved' ) ); add_action( 'event_tickets_after_add_attendee', array( $this, 'on_attendee_registered' ) ); add_action( 'wp', array( $this, 'check_event_date_changes' ) ); // AJAX handlers add_action( 'wp_ajax_hvac_create_schedule', array( $this, 'ajax_create_schedule' ) ); add_action( 'wp_ajax_hvac_update_schedule', array( $this, 'ajax_update_schedule' ) ); add_action( 'wp_ajax_hvac_delete_schedule', array( $this, 'ajax_delete_schedule' ) ); add_action( 'wp_ajax_hvac_get_schedules', array( $this, 'ajax_get_schedules' ) ); add_action( 'wp_ajax_hvac_toggle_schedule', array( $this, 'ajax_toggle_schedule' ) ); add_action( 'wp_ajax_hvac_preview_recipients', array( $this, 'ajax_preview_recipients' ) ); // Custom cron schedules add_filter( 'cron_schedules', array( $this, 'add_custom_cron_schedules' ) ); // Initialize cron if not scheduled add_action( 'wp_loaded', array( $this, 'setup_cron_schedules' ) ); } /** * Add custom cron schedules */ public function add_custom_cron_schedules( $schedules ) { $schedules['hvac_every_5_minutes'] = array( 'interval' => 300, 'display' => __( 'Every 5 minutes', 'hvac-community-events' ) ); $schedules['hvac_every_15_minutes'] = array( 'interval' => 900, 'display' => __( 'Every 15 minutes', 'hvac-community-events' ) ); return $schedules; } /** * Setup cron schedules */ public function setup_cron_schedules() { if ( ! wp_next_scheduled( 'hvac_process_communication_schedules' ) ) { wp_schedule_event( time(), 'hvac_every_15_minutes', 'hvac_process_communication_schedules' ); if ( class_exists( 'HVAC_Logger' ) ) { HVAC_Logger::info( 'Communication scheduler cron job set up', 'Scheduler' ); } } } /** * Create a new communication schedule * * @param array $schedule_data Schedule configuration * @return int|WP_Error Schedule ID on success, WP_Error on failure */ public function create_schedule( $schedule_data ) { // Validate schedule data $validation_result = $this->schedule_manager->validate_schedule_data( $schedule_data ); if ( is_wp_error( $validation_result ) ) { return $validation_result; } // Check for conflicts $conflict_check = $this->schedule_manager->check_schedule_conflicts( $schedule_data ); if ( is_wp_error( $conflict_check ) ) { return $conflict_check; } // Calculate next run time $next_run = $this->calculate_next_run_time( $schedule_data ); $schedule_data['next_run'] = $next_run; // Save schedule $schedule_id = $this->schedule_manager->save_schedule( $schedule_data ); if ( $schedule_id && class_exists( 'HVAC_Logger' ) ) { HVAC_Logger::info( "Communication schedule created: ID $schedule_id", 'Scheduler' ); } return $schedule_id; } /** * Update an existing communication schedule * * @param int $schedule_id Schedule ID * @param array $schedule_data Updated schedule configuration * @return bool|WP_Error Success status */ public function update_schedule( $schedule_id, $schedule_data ) { // Verify ownership if ( ! $this->schedule_manager->user_can_edit_schedule( $schedule_id ) ) { return new WP_Error( 'permission_denied', __( 'You do not have permission to edit this schedule.', 'hvac-community-events' ) ); } // Validate data $validation_result = $this->schedule_manager->validate_schedule_data( $schedule_data ); if ( is_wp_error( $validation_result ) ) { return $validation_result; } // Recalculate next run time if trigger settings changed $existing_schedule = $this->schedule_manager->get_schedule( $schedule_id ); $trigger_changed = ( $existing_schedule['trigger_type'] !== $schedule_data['trigger_type'] || $existing_schedule['trigger_value'] !== $schedule_data['trigger_value'] || $existing_schedule['trigger_unit'] !== $schedule_data['trigger_unit'] ); if ( $trigger_changed ) { $schedule_data['next_run'] = $this->calculate_next_run_time( $schedule_data ); } return $this->schedule_manager->update_schedule( $schedule_id, $schedule_data ); } /** * Delete a communication schedule * * @param int $schedule_id Schedule ID * @return bool|WP_Error Success status */ public function delete_schedule( $schedule_id ) { // Verify ownership if ( ! $this->schedule_manager->user_can_edit_schedule( $schedule_id ) ) { return new WP_Error( 'permission_denied', __( 'You do not have permission to delete this schedule.', 'hvac-community-events' ) ); } $result = $this->schedule_manager->delete_schedule( $schedule_id ); if ( $result && class_exists( 'HVAC_Logger' ) ) { HVAC_Logger::info( "Communication schedule deleted: ID $schedule_id", 'Scheduler' ); } return $result; } /** * Get schedules for a trainer * * @param int $trainer_id Trainer user ID * @param int $event_id Optional specific event ID * @return array Array of schedules */ public function get_trainer_schedules( $trainer_id, $event_id = null ) { return $this->schedule_manager->get_schedules_by_trainer( $trainer_id, $event_id ); } /** * Process all due scheduled communications */ public function process_scheduled_communications() { $due_schedules = $this->schedule_manager->get_due_schedules(); if ( class_exists( 'HVAC_Logger' ) ) { HVAC_Logger::info( 'Processing ' . count( $due_schedules ) . ' due communication schedules', 'Scheduler' ); } foreach ( $due_schedules as $schedule ) { $this->execute_schedule( $schedule['schedule_id'] ); } } /** * Calculate next run time for a schedule * * @param array $schedule Schedule configuration * @return string MySQL datetime string */ public function calculate_next_run_time( $schedule ) { if ( ! empty( $schedule['event_id'] ) ) { // Event-based scheduling $event_date = get_post_meta( $schedule['event_id'], '_EventStartDate', true ); if ( ! $event_date ) { return null; } return $this->trigger_engine->calculate_trigger_time( $event_date, $schedule ); } else { // Immediate or custom date scheduling if ( $schedule['trigger_type'] === 'custom_date' && ! empty( $schedule['custom_date'] ) ) { return $schedule['custom_date']; } elseif ( $schedule['trigger_type'] === 'on_registration' ) { // This will be triggered immediately on registration return null; } } return null; } /** * Execute a specific schedule * * @param int $schedule_id Schedule ID * @return bool Success status */ public function execute_schedule( $schedule_id ) { $schedule = $this->schedule_manager->get_schedule( $schedule_id ); if ( ! $schedule ) { return false; } try { // Get recipients $recipients = $this->trigger_engine->get_schedule_recipients( $schedule ); if ( empty( $recipients ) ) { $this->logger->log_schedule_execution( $schedule_id, 'skipped', array( 'reason' => 'No recipients found' ) ); return true; } // Execute communication $result = $this->trigger_engine->execute_communication( $schedule, $recipients ); // Update schedule run tracking $this->schedule_manager->update_schedule_run_tracking( $schedule_id ); // Log execution $this->logger->log_schedule_execution( $schedule_id, 'sent', array( 'recipient_count' => count( $recipients ), 'success' => $result ) ); return $result; } catch ( Exception $e ) { $this->logger->log_schedule_execution( $schedule_id, 'failed', array( 'error' => $e->getMessage() ) ); if ( class_exists( 'HVAC_Logger' ) ) { HVAC_Logger::error( "Schedule execution failed: " . $e->getMessage(), 'Scheduler' ); } return false; } } /** * Handle event saved/updated * * @param int $event_id Event ID */ public function on_event_saved( $event_id ) { $schedules = $this->schedule_manager->get_schedules_by_event( $event_id ); foreach ( $schedules as $schedule ) { // Recalculate next run time if event date changed $new_next_run = $this->calculate_next_run_time( $schedule ); if ( $new_next_run !== $schedule['next_run'] ) { $this->schedule_manager->update_schedule( $schedule['schedule_id'], array( 'next_run' => $new_next_run ) ); } } } /** * Handle attendee registration * * @param int $attendee_id Attendee ID * @param int $event_id Event ID */ public function on_attendee_registered( $attendee_id, $event_id ) { // Process immediate registration triggers $this->trigger_engine->process_registration_triggers( $attendee_id, $event_id ); } /** * Check for event date changes */ public function check_event_date_changes() { // This will be called on wp hook to check for any event date changes // and update corresponding schedules if ( ! is_admin() || ! current_user_can( 'edit_posts' ) ) { return; } // Process any date change updates $this->trigger_engine->process_event_date_changes(); } /** * AJAX: Create schedule */ public function ajax_create_schedule() { check_ajax_referer( 'hvac_scheduler_nonce', 'nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'message' => __( 'You must be logged in to create schedules.', 'hvac-community-events' ) ) ); } $schedule_data = $this->sanitize_schedule_data( $_POST ); $schedule_data['trainer_id'] = get_current_user_id(); $result = $this->create_schedule( $schedule_data ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'schedule_id' => $result, 'message' => __( 'Schedule created successfully.', 'hvac-community-events' ) ) ); } /** * AJAX: Update schedule */ public function ajax_update_schedule() { check_ajax_referer( 'hvac_scheduler_nonce', 'nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'message' => __( 'You must be logged in to update schedules.', 'hvac-community-events' ) ) ); } $schedule_id = intval( $_POST['schedule_id'] ); $schedule_data = $this->sanitize_schedule_data( $_POST ); $result = $this->update_schedule( $schedule_id, $schedule_data ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Schedule updated successfully.', 'hvac-community-events' ) ) ); } /** * AJAX: Delete schedule */ public function ajax_delete_schedule() { check_ajax_referer( 'hvac_scheduler_nonce', 'nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'message' => __( 'You must be logged in to delete schedules.', 'hvac-community-events' ) ) ); } $schedule_id = intval( $_POST['schedule_id'] ); $result = $this->delete_schedule( $schedule_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Schedule deleted successfully.', 'hvac-community-events' ) ) ); } /** * AJAX: Get schedules */ public function ajax_get_schedules() { check_ajax_referer( 'hvac_scheduler_nonce', 'nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'message' => __( 'You must be logged in to view schedules.', 'hvac-community-events' ) ) ); } $trainer_id = get_current_user_id(); $event_id = isset( $_POST['event_id'] ) ? intval( $_POST['event_id'] ) : null; $status_filter = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : null; $schedules = $this->get_trainer_schedules( $trainer_id, $event_id ); if ( $status_filter && $status_filter !== 'all' ) { $schedules = array_filter( $schedules, function( $schedule ) use ( $status_filter ) { return $schedule['status'] === $status_filter; } ); } wp_send_json_success( array( 'schedules' => array_values( $schedules ) ) ); } /** * AJAX: Toggle schedule status */ public function ajax_toggle_schedule() { check_ajax_referer( 'hvac_scheduler_nonce', 'nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'message' => __( 'You must be logged in to toggle schedules.', 'hvac-community-events' ) ) ); } $schedule_id = intval( $_POST['schedule_id'] ); $schedule = $this->schedule_manager->get_schedule( $schedule_id ); if ( ! $schedule ) { wp_send_json_error( array( 'message' => __( 'Schedule not found.', 'hvac-community-events' ) ) ); } $new_status = ( $schedule['status'] === 'active' ) ? 'paused' : 'active'; $result = $this->update_schedule( $schedule_id, array( 'status' => $new_status ) ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'status' => $new_status, 'message' => sprintf( __( 'Schedule %s.', 'hvac-community-events' ), $new_status ) ) ); } /** * AJAX: Preview recipients */ public function ajax_preview_recipients() { check_ajax_referer( 'hvac_scheduler_nonce', 'nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'message' => __( 'You must be logged in to preview recipients.', 'hvac-community-events' ) ) ); } $schedule_data = $this->sanitize_schedule_data( $_POST ); $schedule_data['trainer_id'] = get_current_user_id(); $recipients = $this->trigger_engine->get_schedule_recipients( $schedule_data ); wp_send_json_success( array( 'recipients' => $recipients, 'count' => count( $recipients ) ) ); } /** * Sanitize schedule data from form input * * @param array $raw_data Raw POST data * @return array Sanitized schedule data */ private function sanitize_schedule_data( $raw_data ) { return array( 'schedule_name' => isset( $raw_data['schedule_name'] ) ? sanitize_text_field( $raw_data['schedule_name'] ) : '', 'event_id' => isset( $raw_data['event_id'] ) ? intval( $raw_data['event_id'] ) : null, 'template_id' => isset( $raw_data['template_id'] ) ? intval( $raw_data['template_id'] ) : 0, 'trigger_type' => isset( $raw_data['trigger_type'] ) ? sanitize_text_field( $raw_data['trigger_type'] ) : '', 'trigger_value' => isset( $raw_data['trigger_value'] ) ? intval( $raw_data['trigger_value'] ) : 0, 'trigger_unit' => isset( $raw_data['trigger_unit'] ) ? sanitize_text_field( $raw_data['trigger_unit'] ) : 'days', 'target_audience' => isset( $raw_data['target_audience'] ) ? sanitize_text_field( $raw_data['target_audience'] ) : 'all_attendees', 'custom_recipient_list' => isset( $raw_data['custom_recipient_list'] ) ? sanitize_textarea_field( $raw_data['custom_recipient_list'] ) : '', 'is_recurring' => isset( $raw_data['is_recurring'] ) ? (bool) $raw_data['is_recurring'] : false, 'recurring_interval' => isset( $raw_data['recurring_interval'] ) ? intval( $raw_data['recurring_interval'] ) : null, 'recurring_unit' => isset( $raw_data['recurring_unit'] ) ? sanitize_text_field( $raw_data['recurring_unit'] ) : null, 'max_runs' => isset( $raw_data['max_runs'] ) ? intval( $raw_data['max_runs'] ) : null, 'status' => isset( $raw_data['status'] ) ? sanitize_text_field( $raw_data['status'] ) : 'active' ); } /** * Get default schedule templates * * @return array Default schedule configurations */ public function get_default_schedule_templates() { return array( 'event_reminder_24h' => array( 'name' => __( '24-Hour Event Reminder', 'hvac-community-events' ), 'trigger_type' => 'before_event', 'trigger_value' => 1, 'trigger_unit' => 'days', 'template_category' => 'event_reminder', 'target_audience' => 'confirmed_attendees', 'description' => __( 'Send reminder 24 hours before event starts', 'hvac-community-events' ) ), 'welcome_on_registration' => array( 'name' => __( 'Welcome Email on Registration', 'hvac-community-events' ), 'trigger_type' => 'on_registration', 'trigger_value' => 0, 'trigger_unit' => 'minutes', 'template_category' => 'pre_event', 'target_audience' => 'all_attendees', 'description' => __( 'Send welcome email immediately when someone registers', 'hvac-community-events' ) ), 'post_event_followup' => array( 'name' => __( 'Post-Event Follow-up', 'hvac-community-events' ), 'trigger_type' => 'after_event', 'trigger_value' => 2, 'trigger_unit' => 'days', 'template_category' => 'post_event', 'target_audience' => 'all_attendees', 'description' => __( 'Send follow-up email 2 days after event', 'hvac-community-events' ) ), 'certificate_notification' => array( 'name' => __( 'Certificate Ready Notification', 'hvac-community-events' ), 'trigger_type' => 'after_event', 'trigger_value' => 3, 'trigger_unit' => 'days', 'template_category' => 'certificate', 'target_audience' => 'confirmed_attendees', 'description' => __( 'Notify attendees when certificates are ready', 'hvac-community-events' ) ) ); } } // Initialize the scheduler function hvac_communication_scheduler() { return HVAC_Communication_Scheduler::instance(); } // Initialize after plugins loaded add_action( 'plugins_loaded', 'hvac_communication_scheduler' );