361 lines
11 KiB
PHP
361 lines
11 KiB
PHP
<?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;
|
|
}
|
|
} |