code revision

This commit is contained in:
2025-08-06 15:25:47 -07:00
parent fc220beeac
commit c6edbbeba7
19 changed files with 10039 additions and 0 deletions

View File

@@ -0,0 +1,361 @@
<?php
/**
* Call queue management class
*/
class TWP_Call_Queue {
/**
* Add call to queue
*/
public static function add_to_queue($queue_id, $call_data) {
global $wpdb;
$table_name = $wpdb->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('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
$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;
}
}