Files
twilio-wp-plugin/includes/class-twp-callback-manager.php

341 lines
11 KiB
PHP
Raw Normal View History

2025-08-06 15:25:47 -07:00
<?php
/**
* Callback management class for queue callbacks and outbound calling
*/
class TWP_Callback_Manager {
/**
* Request a callback from queue
*/
public static function request_callback($phone_number, $queue_id = null, $call_sid = null) {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_callbacks';
$result = $wpdb->insert(
$table_name,
array(
'phone_number' => sanitize_text_field($phone_number),
'queue_id' => $queue_id ? intval($queue_id) : null,
'original_call_sid' => $call_sid,
'status' => 'pending'
),
array('%s', '%d', '%s', '%s')
);
if ($result !== false) {
// Send confirmation SMS if configured
$sms_number = get_option('twp_sms_notification_number');
if ($sms_number) {
$message = "Callback requested for " . $phone_number . ". We'll call you back shortly.";
self::send_sms($phone_number, $message);
}
return $wpdb->insert_id;
}
return false;
}
/**
* Process pending callbacks
*/
public static function process_callbacks() {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_callbacks';
// Get pending callbacks older than 2 minutes (to avoid immediate callback)
$callbacks = $wpdb->get_results("
SELECT * FROM $table_name
WHERE status = 'pending'
AND requested_at <= DATE_SUB(NOW(), INTERVAL 2 MINUTE)
AND attempts < 3
ORDER BY requested_at ASC
LIMIT 10
");
foreach ($callbacks as $callback) {
self::initiate_callback($callback);
}
}
/**
* Initiate a callback
*/
private static function initiate_callback($callback) {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_callbacks';
// Find an available agent
$available_agent = TWP_Agent_Manager::get_available_agents();
if (empty($available_agent)) {
// No agents available, try again later
$wpdb->update(
$table_name,
array('last_attempt' => current_time('mysql')),
array('id' => $callback->id),
array('%s'),
array('%d')
);
return false;
}
$agent = $available_agent[0]; // Get first available agent
// Create a conference call
$twilio = new TWP_Twilio_API();
// First call the agent
$agent_call_result = $twilio->make_call(
$agent->phone_number,
home_url('/wp-json/twilio-webhook/v1/callback-agent'),
array(
'callback_id' => $callback->id,
'customer_number' => $callback->phone_number
)
);
if ($agent_call_result['success']) {
// Update callback status
$wpdb->update(
$table_name,
array(
'status' => 'calling',
'attempts' => $callback->attempts + 1,
'last_attempt' => current_time('mysql'),
'callback_call_sid' => $agent_call_result['call_sid']
),
array('id' => $callback->id),
array('%s', '%d', '%s', '%s'),
array('%d')
);
// Set agent to busy
TWP_Agent_Manager::set_agent_status($agent->user_id, 'busy', $agent_call_result['call_sid']);
return true;
}
return false;
}
/**
* Handle callback agent answered
*/
public static function handle_agent_answered($callback_id, $agent_call_sid) {
global $wpdb;
$callbacks_table = $wpdb->prefix . 'twp_callbacks';
$callback = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $callbacks_table WHERE id = %d",
$callback_id
));
if (!$callback) {
return false;
}
// Now call the customer and conference them in
$twilio = new TWP_Twilio_API();
$customer_call_result = $twilio->make_call(
$callback->phone_number,
home_url('/wp-json/twilio-webhook/v1/callback-customer'),
array(
'agent_call_sid' => $agent_call_sid,
'callback_id' => $callback_id
)
);
if ($customer_call_result['success']) {
// Update callback status
$wpdb->update(
$callbacks_table,
array('status' => 'connecting'),
array('id' => $callback_id),
array('%s'),
array('%d')
);
return true;
}
return false;
}
/**
* Complete callback
*/
public static function complete_callback($callback_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_callbacks';
$wpdb->update(
$table_name,
array(
'status' => 'completed',
'completed_at' => current_time('mysql')
),
array('id' => $callback_id),
array('%s', '%s'),
array('%d')
);
}
/**
* Initiate outbound call (click-to-call)
*/
public static function initiate_outbound_call($to_number, $agent_user_id) {
$agent_phone = get_user_meta($agent_user_id, 'twp_phone_number', true);
if (!$agent_phone) {
return array('success' => false, 'error' => 'No phone number configured');
}
$twilio = new TWP_Twilio_API();
// First call the agent
$agent_call_result = $twilio->make_call(
$agent_phone,
home_url('/wp-json/twilio-webhook/v1/outbound-agent'),
array(
'target_number' => $to_number,
'agent_user_id' => $agent_user_id
)
);
if ($agent_call_result['success']) {
// Set agent to busy
TWP_Agent_Manager::set_agent_status($agent_user_id, 'busy', $agent_call_result['call_sid']);
// Log the outbound call
TWP_Call_Logger::log_call(array(
'call_sid' => $agent_call_result['call_sid'],
'from_number' => $agent_phone,
'to_number' => $to_number,
'status' => 'outbound_initiated',
'workflow_name' => 'Outbound Call',
'actions_taken' => json_encode(array(
'agent_id' => $agent_user_id,
'agent_name' => get_userdata($agent_user_id)->display_name,
'type' => 'click_to_call'
))
));
return array('success' => true, 'call_sid' => $agent_call_result['call_sid']);
}
return array('success' => false, 'error' => $agent_call_result['error']);
}
/**
* Handle outbound agent answered
*/
public static function handle_outbound_agent_answered($target_number, $agent_call_sid) {
$twilio = new TWP_Twilio_API();
// Create TwiML to call the target number
$twiml = new \Twilio\TwiML\VoiceResponse();
$twiml->say('Connecting your call...', ['voice' => 'alice']);
$dial = $twiml->dial([
'callerId' => get_option('twp_caller_id_number', ''), // Use configured caller ID
'timeout' => 30
]);
$dial->number($target_number);
// If no answer, leave a message
$twiml->say('The number you called is not available. Please try again later.', ['voice' => 'alice']);
return $twiml->asXML();
}
/**
* Create callback option TwiML for queue
*/
public static function create_callback_twiml($queue_id, $caller_number) {
$twiml = new \Twilio\TwiML\VoiceResponse();
$gather = $twiml->gather([
'numDigits' => 1,
'timeout' => 10,
'action' => home_url('/wp-json/twilio-webhook/v1/callback-choice'),
'method' => 'POST'
]);
$gather->say(
'You are currently in the queue. Press 1 to wait on the line, or press 2 to request a callback.',
['voice' => 'alice']
);
// Default to callback if no input
$twiml->say('No input received. Requesting callback for you.', ['voice' => 'alice']);
$twiml->redirect(home_url('/wp-json/twilio-webhook/v1/request-callback?' . http_build_query([
'queue_id' => $queue_id,
'phone_number' => $caller_number
])));
return $twiml->asXML();
}
/**
* Send SMS notification
*/
private static function send_sms($to_number, $message) {
$twilio = new TWP_Twilio_API();
return $twilio->send_sms($to_number, $message);
}
/**
* Get callback statistics
*/
public static function get_callback_stats($days = 7) {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_callbacks';
$since_date = date('Y-m-d H:i:s', strtotime("-$days days"));
$stats = array(
'total_requests' => $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE requested_at >= %s",
$since_date
)),
'completed' => $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE requested_at >= %s AND status = 'completed'",
$since_date
)),
'pending' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'pending'"),
'avg_completion_time' => $wpdb->get_var($wpdb->prepare(
"SELECT AVG(TIMESTAMPDIFF(MINUTE, requested_at, completed_at))
FROM $table_name
WHERE requested_at >= %s AND status = 'completed'",
$since_date
))
);
$stats['success_rate'] = $stats['total_requests'] > 0 ?
round(($stats['completed'] / $stats['total_requests']) * 100, 1) : 0;
return $stats;
}
/**
* Get pending callbacks for admin
*/
public static function get_pending_callbacks() {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_callbacks';
$queues_table = $wpdb->prefix . 'twp_call_queues';
return $wpdb->get_results("
SELECT
c.*,
q.queue_name,
TIMESTAMPDIFF(MINUTE, c.requested_at, NOW()) as wait_minutes
FROM $table_name c
LEFT JOIN $queues_table q ON c.queue_id = q.id
WHERE c.status IN ('pending', 'calling', 'connecting')
ORDER BY c.requested_at ASC
");
}
}