prefix . 'twp_queued_calls'; // Get current position in queue $max_position = $wpdb->get_var($wpdb->prepare( "SELECT MAX(position) FROM $table_name WHERE queue_id = %d AND status = 'waiting'", $queue_id )); $position = $max_position ? $max_position + 1 : 1; $result = $wpdb->insert( $table_name, array( 'queue_id' => $queue_id, 'call_sid' => sanitize_text_field($call_data['call_sid']), 'from_number' => sanitize_text_field($call_data['from_number']), 'to_number' => sanitize_text_field($call_data['to_number']), 'position' => $position, 'status' => 'waiting' ), array('%d', '%s', '%s', '%s', '%d', '%s') ); return $result !== false ? $position : false; } /** * Remove call from queue */ public static function remove_from_queue($call_sid) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; // Get call info before removing $call = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE call_sid = %s", $call_sid )); if ($call) { // Update status $wpdb->update( $table_name, array( 'status' => 'completed', 'ended_at' => current_time('mysql') ), array('call_sid' => $call_sid), array('%s', '%s'), array('%s') ); // Reorder queue positions self::reorder_queue($call->queue_id); return true; } return false; } /** * Get next call in queue */ public static function get_next_call($queue_id) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; return $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE queue_id = %d AND status = 'waiting' ORDER BY position ASC LIMIT 1", $queue_id )); } /** * Answer queued call */ public static function answer_call($call_sid, $agent_number) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; // Update call status $wpdb->update( $table_name, array( 'status' => 'answered', 'answered_at' => current_time('mysql') ), array('call_sid' => $call_sid), array('%s', '%s'), array('%s') ); // Connect call to agent $twilio = new TWP_Twilio_API(); $twilio->forward_call($call_sid, $agent_number); return true; } /** * Process waiting calls */ public function process_waiting_calls() { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; $queue_table = $wpdb->prefix . 'twp_call_queues'; // Get all active queues $queues = $wpdb->get_results("SELECT * FROM $queue_table"); foreach ($queues as $queue) { // Check for timed out calls $timeout_time = date('Y-m-d H:i:s', strtotime('-' . $queue->timeout_seconds . ' seconds')); $timed_out_calls = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE queue_id = %d AND status = 'waiting' AND joined_at <= %s", $queue->id, $timeout_time )); foreach ($timed_out_calls as $call) { // Handle timeout $this->handle_timeout($call, $queue); } // Update caller positions and play position messages $this->update_queue_positions($queue->id); } } /** * Handle call timeout */ private function handle_timeout($call, $queue) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; // Update status $wpdb->update( $table_name, array( 'status' => 'timeout', 'ended_at' => current_time('mysql') ), array('id' => $call->id), array('%s', '%s'), array('%d') ); // Offer callback instead of hanging up $callback_twiml = TWP_Callback_Manager::create_callback_twiml($queue->id, $call->from_number); $twilio = new TWP_Twilio_API(); $twilio->update_call($call->call_sid, array( 'Twiml' => $callback_twiml )); // Reorder queue self::reorder_queue($queue->id); } /** * Update queue positions */ private function update_queue_positions($queue_id) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; $waiting_calls = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE queue_id = %d AND status = 'waiting' ORDER BY position ASC", $queue_id )); foreach ($waiting_calls as $index => $call) { $position = $index + 1; // Update position if changed if ($call->position != $position) { $wpdb->update( $table_name, array('position' => $position), array('id' => $call->id), array('%d'), array('%d') ); } // Announce position every 30 seconds $last_announcement = get_transient('twp_queue_announce_' . $call->call_sid); if (!$last_announcement) { $this->announce_position($call, $position); set_transient('twp_queue_announce_' . $call->call_sid, true, 30); } } } /** * Announce queue position */ private function announce_position($call, $position) { $twilio = new TWP_Twilio_API(); $elevenlabs = new TWP_ElevenLabs_API(); $message = "You are currently number $position in the queue. Please hold and an agent will be with you shortly."; // Generate TTS audio $audio_result = $elevenlabs->text_to_speech($message); if ($audio_result['success']) { // Create TwiML with audio $twiml = new SimpleXMLElement(''); $play = $twiml->addChild('Play', $audio_result['file_url']); $play->addAttribute('loop', '0'); // Add wait music $queue = self::get_queue($call->queue_id); if ($queue && $queue->wait_music_url) { $play_music = $twiml->addChild('Play', $queue->wait_music_url); $play_music->addAttribute('loop', '0'); } $twilio->update_call($call->call_sid, array( 'Twiml' => $twiml->asXML() )); } } /** * Reorder queue positions */ private static function reorder_queue($queue_id) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; $waiting_calls = $wpdb->get_results($wpdb->prepare( "SELECT id FROM $table_name WHERE queue_id = %d AND status = 'waiting' ORDER BY position ASC", $queue_id )); foreach ($waiting_calls as $index => $call) { $wpdb->update( $table_name, array('position' => $index + 1), array('id' => $call->id), array('%d'), array('%d') ); } } /** * Create queue */ public static function create_queue($data) { global $wpdb; $table_name = $wpdb->prefix . 'twp_call_queues'; return $wpdb->insert( $table_name, array( 'queue_name' => sanitize_text_field($data['queue_name']), 'max_size' => intval($data['max_size']), 'wait_music_url' => esc_url_raw($data['wait_music_url']), 'tts_message' => sanitize_textarea_field($data['tts_message']), 'timeout_seconds' => intval($data['timeout_seconds']) ), array('%s', '%d', '%s', '%s', '%d') ); } /** * Get queue */ public static function get_queue($queue_id) { global $wpdb; $table_name = $wpdb->prefix . 'twp_call_queues'; return $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $queue_id )); } /** * Get all queues */ public static function get_all_queues() { global $wpdb; $table_name = $wpdb->prefix . 'twp_call_queues'; return $wpdb->get_results("SELECT * FROM $table_name ORDER BY queue_name ASC"); } /** * Delete queue */ public static function delete_queue($queue_id) { global $wpdb; $queue_table = $wpdb->prefix . 'twp_call_queues'; $calls_table = $wpdb->prefix . 'twp_queued_calls'; // First delete all queued calls for this queue $wpdb->delete($calls_table, array('queue_id' => $queue_id), array('%d')); // Then delete the queue itself return $wpdb->delete($queue_table, array('id' => $queue_id), array('%d')); } /** * Get queue status */ public static function get_queue_status() { global $wpdb; $queue_table = $wpdb->prefix . 'twp_call_queues'; $calls_table = $wpdb->prefix . 'twp_queued_calls'; $queues = $wpdb->get_results("SELECT * FROM $queue_table"); $status = array(); foreach ($queues as $queue) { $waiting_count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $calls_table WHERE queue_id = %d AND status = 'waiting'", $queue->id )); $status[] = array( 'queue_id' => $queue->id, 'queue_name' => $queue->queue_name, 'waiting_calls' => $waiting_count, 'max_size' => $queue->max_size, 'available_slots' => $queue->max_size - $waiting_count ); } return $status; } }