1763 lines
75 KiB
PHP
1763 lines
75 KiB
PHP
<?php
|
|
/**
|
|
* Webhook handler class
|
|
*/
|
|
class TWP_Webhooks {
|
|
|
|
/**
|
|
* Constructor - ensure Twilio SDK is loaded
|
|
*/
|
|
public function __construct() {
|
|
// Load Twilio SDK if not already loaded
|
|
if (!class_exists('\Twilio\Rest\Client')) {
|
|
$autoloader_path = plugin_dir_path(dirname(__FILE__)) . 'vendor/autoload.php';
|
|
if (file_exists($autoloader_path)) {
|
|
require_once $autoloader_path;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register webhook endpoints
|
|
*/
|
|
public function register_endpoints() {
|
|
// Register REST API endpoints for webhooks
|
|
add_action('rest_api_init', function() {
|
|
// Voice webhook
|
|
register_rest_route('twilio-webhook/v1', '/voice', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_voice_webhook'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// SMS webhook
|
|
register_rest_route('twilio-webhook/v1', '/sms', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_sms_webhook'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Status webhook
|
|
register_rest_route('twilio-webhook/v1', '/status', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_status_webhook'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// IVR response webhook
|
|
register_rest_route('twilio-webhook/v1', '/ivr-response', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_ivr_response'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Queue wait webhook
|
|
register_rest_route('twilio-webhook/v1', '/queue-wait', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_queue_wait'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Queue action webhook (for enqueue/dequeue events)
|
|
register_rest_route('twilio-webhook/v1', '/queue-action', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_queue_action'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Voicemail callback webhook
|
|
register_rest_route('twilio-webhook/v1', '/voicemail-callback', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_voicemail_callback'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Voicemail complete webhook (after recording)
|
|
register_rest_route('twilio-webhook/v1', '/voicemail-complete', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_voicemail_complete'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Agent call status webhook (detect voicemail/no-answer)
|
|
register_rest_route('twilio-webhook/v1', '/agent-call-status', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_agent_call_status'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Agent screening webhook (screen agent before connecting)
|
|
register_rest_route('twilio-webhook/v1', '/agent-screen', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_agent_screen'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Agent confirmation webhook (after agent presses key)
|
|
register_rest_route('twilio-webhook/v1', '/agent-confirm', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_agent_confirm'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Voicemail audio proxy endpoint
|
|
register_rest_route('twilio-webhook/v1', '/voicemail-audio/(?P<id>\d+)', array(
|
|
'methods' => 'GET',
|
|
'callback' => array($this, 'proxy_voicemail_audio'),
|
|
'permission_callback' => function() {
|
|
// Check if user is logged in with proper permissions
|
|
return is_user_logged_in() && current_user_can('manage_options');
|
|
},
|
|
'args' => array(
|
|
'id' => array(
|
|
'validate_callback' => function($param, $request, $key) {
|
|
return is_numeric($param);
|
|
}
|
|
),
|
|
),
|
|
));
|
|
|
|
// Transcription webhook
|
|
register_rest_route('twilio-webhook/v1', '/transcription', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_transcription_webhook'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Callback choice webhook
|
|
register_rest_route('twilio-webhook/v1', '/callback-choice', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_callback_choice'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Request callback webhook
|
|
register_rest_route('twilio-webhook/v1', '/request-callback', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_request_callback'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Callback agent webhook
|
|
register_rest_route('twilio-webhook/v1', '/callback-agent', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_callback_agent'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Callback customer webhook
|
|
register_rest_route('twilio-webhook/v1', '/callback-customer', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_callback_customer'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Outbound agent webhook
|
|
register_rest_route('twilio-webhook/v1', '/outbound-agent', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_outbound_agent'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Ring group result webhook
|
|
register_rest_route('twilio-webhook/v1', '/ring-group-result', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_ring_group_result'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Agent connect webhook
|
|
register_rest_route('twilio-webhook/v1', '/agent-connect', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_agent_connect'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
|
|
// Outbound agent with from number webhook
|
|
register_rest_route('twilio-webhook/v1', '/outbound-agent-with-from', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'handle_outbound_agent_with_from'),
|
|
'permission_callback' => '__return_true'
|
|
));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle webhook requests (deprecated - using REST API now)
|
|
*/
|
|
public function handle_webhook() {
|
|
// This method is deprecated and no longer used
|
|
// Webhooks are now handled via REST API endpoints
|
|
}
|
|
|
|
/**
|
|
* Send TwiML response
|
|
*/
|
|
private function send_twiml_response($twiml) {
|
|
// Send raw XML response for Twilio
|
|
header('Content-Type: text/xml; charset=utf-8');
|
|
echo $twiml;
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Verify Twilio signature
|
|
*/
|
|
private function verify_twilio_signature() {
|
|
// Get signature header
|
|
$signature = isset($_SERVER['HTTP_X_TWILIO_SIGNATURE']) ? $_SERVER['HTTP_X_TWILIO_SIGNATURE'] : '';
|
|
|
|
if (!$signature) {
|
|
return false;
|
|
}
|
|
|
|
// Get current URL
|
|
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
|
$url = $protocol . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
|
|
|
// Get POST data
|
|
$postData = file_get_contents('php://input');
|
|
parse_str($postData, $params);
|
|
|
|
// Verify signature
|
|
$twilio = new TWP_Twilio_API();
|
|
return $twilio->validate_webhook_signature($url, $params, $signature);
|
|
}
|
|
|
|
/**
|
|
* Handle voice webhook
|
|
*/
|
|
public function handle_voice_webhook($request) {
|
|
// Verify Twilio signature
|
|
if (!$this->verify_twilio_signature()) {
|
|
return new WP_Error('unauthorized', 'Unauthorized', array('status' => 401));
|
|
}
|
|
|
|
$params = $request->get_params();
|
|
|
|
$call_data = array(
|
|
'CallSid' => isset($params['CallSid']) ? $params['CallSid'] : '',
|
|
'From' => isset($params['From']) ? $params['From'] : '',
|
|
'To' => isset($params['To']) ? $params['To'] : '',
|
|
'CallStatus' => isset($params['CallStatus']) ? $params['CallStatus'] : ''
|
|
);
|
|
|
|
// Log the incoming call
|
|
TWP_Call_Logger::log_call(array(
|
|
'call_sid' => $call_data['CallSid'],
|
|
'from_number' => $call_data['From'],
|
|
'to_number' => $call_data['To'],
|
|
'status' => 'initiated',
|
|
'actions_taken' => 'Incoming call received'
|
|
));
|
|
|
|
// Check for schedule
|
|
$schedule_id = isset($params['schedule_id']) ? intval($params['schedule_id']) : 0;
|
|
|
|
if ($schedule_id) {
|
|
$schedule = TWP_Scheduler::get_schedule($schedule_id);
|
|
|
|
if ($schedule && $schedule->workflow_id) {
|
|
// Execute workflow
|
|
TWP_Call_Logger::log_action($call_data['CallSid'], 'Executing workflow: ' . $schedule->schedule_name);
|
|
$twiml = TWP_Workflow::execute_workflow($schedule->workflow_id, $call_data);
|
|
|
|
TWP_Call_Logger::update_call($call_data['CallSid'], array(
|
|
'workflow_id' => $schedule->workflow_id,
|
|
'workflow_name' => $schedule->schedule_name
|
|
));
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
} elseif ($schedule && $schedule->forward_number) {
|
|
// Forward call
|
|
TWP_Call_Logger::log_action($call_data['CallSid'], 'Forwarding to: ' . $schedule->forward_number);
|
|
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$dial = $twiml->addChild('Dial');
|
|
$dial->addChild('Number', $schedule->forward_number);
|
|
return $this->send_twiml_response($twiml->asXML());
|
|
}
|
|
}
|
|
|
|
// Check for workflow associated with phone number
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_workflows';
|
|
|
|
$workflow = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE phone_number = %s AND is_active = 1 LIMIT 1",
|
|
$call_data['To']
|
|
));
|
|
|
|
if ($workflow) {
|
|
TWP_Call_Logger::log_action($call_data['CallSid'], 'Executing workflow: ' . $workflow->workflow_name);
|
|
$twiml = TWP_Workflow::execute_workflow($workflow->id, $call_data);
|
|
|
|
TWP_Call_Logger::update_call($call_data['CallSid'], array(
|
|
'workflow_id' => $workflow->id,
|
|
'workflow_name' => $workflow->workflow_name
|
|
));
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
// Default response
|
|
TWP_Call_Logger::log_action($call_data['CallSid'], 'No workflow found, using default response');
|
|
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$say = $twiml->addChild('Say', 'Thank you for calling. Please hold while we connect you.');
|
|
$say->addAttribute('voice', 'alice');
|
|
|
|
// Add to default queue
|
|
$enqueue = $twiml->addChild('Enqueue', 'default');
|
|
|
|
return $this->send_twiml_response($twiml->asXML());
|
|
}
|
|
|
|
/**
|
|
* Handle SMS webhook
|
|
*/
|
|
public function handle_sms_webhook($request) {
|
|
error_log('TWP SMS Webhook: ===== WEBHOOK TRIGGERED =====');
|
|
error_log('TWP SMS Webhook: Request method: ' . $_SERVER['REQUEST_METHOD']);
|
|
error_log('TWP SMS Webhook: Request URI: ' . $_SERVER['REQUEST_URI']);
|
|
|
|
$params = $request->get_params();
|
|
$sms_data = array(
|
|
'MessageSid' => isset($params['MessageSid']) ? $params['MessageSid'] : '',
|
|
'From' => isset($params['From']) ? $params['From'] : '',
|
|
'To' => isset($params['To']) ? $params['To'] : '',
|
|
'Body' => isset($params['Body']) ? $params['Body'] : ''
|
|
);
|
|
|
|
error_log('TWP SMS Webhook: Raw POST data: ' . print_r($_POST, true));
|
|
error_log('TWP SMS Webhook: Parsed params: ' . print_r($params, true));
|
|
error_log('TWP SMS Webhook: Received SMS - From: ' . $sms_data['From'] . ', To: ' . $sms_data['To'] . ', Body: ' . $sms_data['Body']);
|
|
|
|
// Process SMS commands
|
|
$command = strtolower(trim($sms_data['Body']));
|
|
|
|
switch ($command) {
|
|
case '1':
|
|
error_log('TWP SMS Webhook: Agent texted "1" - calling handle_agent_ready_sms');
|
|
$this->handle_agent_ready_sms($sms_data['From']);
|
|
break;
|
|
|
|
case 'status':
|
|
$this->send_status_sms($sms_data['From']);
|
|
break;
|
|
|
|
case 'help':
|
|
$this->send_help_sms($sms_data['From']);
|
|
break;
|
|
|
|
default:
|
|
// Log SMS for later processing
|
|
$this->log_sms($sms_data);
|
|
break;
|
|
}
|
|
|
|
// Empty response
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
echo $twiml->asXML();
|
|
}
|
|
|
|
/**
|
|
* Handle status webhook
|
|
*/
|
|
public function handle_status_webhook($request) {
|
|
// Verify Twilio signature
|
|
if (!$this->verify_twilio_signature()) {
|
|
return new WP_Error('unauthorized', 'Unauthorized', array('status' => 401));
|
|
}
|
|
|
|
$params = $request->get_params();
|
|
|
|
$status_data = array(
|
|
'CallSid' => isset($params['CallSid']) ? $params['CallSid'] : '',
|
|
'CallStatus' => isset($params['CallStatus']) ? $params['CallStatus'] : '',
|
|
'CallDuration' => isset($params['CallDuration']) ? intval($params['CallDuration']) : 0
|
|
);
|
|
|
|
// Update call log with status and duration
|
|
TWP_Call_Logger::update_call($status_data['CallSid'], array(
|
|
'status' => $status_data['CallStatus'],
|
|
'duration' => $status_data['CallDuration'],
|
|
'actions_taken' => 'Call status changed to: ' . $status_data['CallStatus']
|
|
));
|
|
|
|
// Update call status in queue if applicable
|
|
// Remove from queue for any terminal call state
|
|
if (in_array($status_data['CallStatus'], ['completed', 'busy', 'failed', 'canceled', 'no-answer'])) {
|
|
$queue_removed = TWP_Call_Queue::remove_from_queue($status_data['CallSid']);
|
|
if ($queue_removed) {
|
|
TWP_Call_Logger::log_action($status_data['CallSid'], 'Call removed from queue due to status: ' . $status_data['CallStatus']);
|
|
error_log('TWP Status Webhook: Removed call ' . $status_data['CallSid'] . ' from queue (status: ' . $status_data['CallStatus'] . ')');
|
|
}
|
|
}
|
|
|
|
// Empty response
|
|
return new WP_REST_Response('<?xml version="1.0" encoding="UTF-8"?><Response></Response>', 200, array(
|
|
'Content-Type' => 'text/xml; charset=utf-8'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Handle IVR response
|
|
*/
|
|
private function handle_ivr_response() {
|
|
$digits = isset($_POST['Digits']) ? $_POST['Digits'] : '';
|
|
$workflow_id = isset($_GET['workflow_id']) ? intval($_GET['workflow_id']) : 0;
|
|
$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 0;
|
|
|
|
if (!$workflow_id || !$step_id) {
|
|
$this->send_default_response();
|
|
return;
|
|
}
|
|
|
|
$workflow = TWP_Workflow::get_workflow($workflow_id);
|
|
|
|
if (!$workflow) {
|
|
$this->send_default_response();
|
|
return;
|
|
}
|
|
|
|
$workflow_data = json_decode($workflow->workflow_data, true);
|
|
|
|
// Find the step and its options
|
|
foreach ($workflow_data['steps'] as $step) {
|
|
if ($step['id'] == $step_id && isset($step['options'][$digits])) {
|
|
$option = $step['options'][$digits];
|
|
|
|
switch ($option['action']) {
|
|
case 'forward':
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$dial = $twiml->addChild('Dial');
|
|
$dial->addChild('Number', $option['number']);
|
|
echo $twiml->asXML();
|
|
return;
|
|
|
|
case 'queue':
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$enqueue = $twiml->addChild('Enqueue', $option['queue_name']);
|
|
echo $twiml->asXML();
|
|
return;
|
|
|
|
case 'voicemail':
|
|
$elevenlabs = new TWP_ElevenLabs_API();
|
|
$twiml = TWP_Workflow::create_voicemail_twiml($option, $elevenlabs);
|
|
echo $twiml;
|
|
return;
|
|
|
|
case 'message':
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$say = $twiml->addChild('Say', $option['message']);
|
|
$say->addAttribute('voice', 'alice');
|
|
$twiml->addChild('Hangup');
|
|
echo $twiml->asXML();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invalid option - replay menu
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$say = $twiml->addChild('Say', 'Invalid option. Please try again.');
|
|
$say->addAttribute('voice', 'alice');
|
|
$twiml->addChild('Redirect');
|
|
echo $twiml->asXML();
|
|
}
|
|
|
|
/**
|
|
* Handle queue wait
|
|
*/
|
|
public function handle_queue_wait($request = null) {
|
|
// Get parameters from request
|
|
$params = $request ? $request->get_params() : $_REQUEST;
|
|
$queue_id = isset($params['queue_id']) ? intval($params['queue_id']) : 0;
|
|
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
|
|
error_log('TWP Queue Wait: queue_id=' . $queue_id . ', call_sid=' . $call_sid);
|
|
|
|
// Get caller's position in queue
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
$call = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE call_sid = %s",
|
|
$call_sid
|
|
));
|
|
|
|
if ($call) {
|
|
$position = $call->position;
|
|
$status = $call->status;
|
|
error_log('TWP Queue Wait: Found call in position ' . $position . ' with status ' . $status);
|
|
|
|
// If call is being connected to an agent, provide different response
|
|
if ($status === 'connecting' || $status === 'answered') {
|
|
error_log('TWP Queue Wait: Call is being connected to agent, providing hold message');
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$say = $twiml->addChild('Say', 'We found an available agent. Please hold while we connect you.');
|
|
$say->addAttribute('voice', 'alice');
|
|
|
|
// Add music or pause while connecting
|
|
$queue = TWP_Call_Queue::get_queue($queue_id);
|
|
if ($queue && !empty($queue->wait_music_url)) {
|
|
$play = $twiml->addChild('Play', $queue->wait_music_url);
|
|
} else {
|
|
$pause = $twiml->addChild('Pause');
|
|
$pause->addAttribute('length', '30');
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml->asXML());
|
|
}
|
|
|
|
// For waiting calls, continue with normal queue behavior
|
|
// Create basic TwiML response
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
|
|
// Simple position announcement
|
|
if ($position > 1) {
|
|
$message = "You are currently number $position in the queue. Please continue to hold.";
|
|
$say = $twiml->addChild('Say', $message);
|
|
$say->addAttribute('voice', 'alice');
|
|
}
|
|
|
|
// Add wait music or pause, then redirect back to continue the loop
|
|
$queue = TWP_Call_Queue::get_queue($queue_id);
|
|
if ($queue && !empty($queue->wait_music_url)) {
|
|
$play = $twiml->addChild('Play', $queue->wait_music_url);
|
|
} else {
|
|
// Add a pause to prevent rapid loops
|
|
$pause = $twiml->addChild('Pause');
|
|
$pause->addAttribute('length', '15'); // 15 second pause
|
|
}
|
|
|
|
// Redirect back to this same endpoint to create continuous loop
|
|
$redirect_url = home_url('/wp-json/twilio-webhook/v1/queue-wait');
|
|
$redirect_url = add_query_arg(array(
|
|
'queue_id' => $queue_id,
|
|
'call_sid' => urlencode($call_sid) // URL encode to handle special characters
|
|
), $redirect_url);
|
|
|
|
// Set the text content of Redirect element properly
|
|
$redirect = $twiml->addChild('Redirect');
|
|
$redirect[0] = $redirect_url; // Set the URL as the text content
|
|
$redirect->addAttribute('method', 'POST');
|
|
|
|
|
|
$response = $twiml->asXML();
|
|
error_log('TWP Queue Wait: Returning continuous TwiML: ' . $response);
|
|
|
|
return $this->send_twiml_response($response);
|
|
} else {
|
|
error_log('TWP Queue Wait: Call not found in queue - providing basic hold');
|
|
|
|
// Call not in queue yet (maybe still being processed) - provide basic hold with redirect
|
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
|
$say = $twiml->addChild('Say', 'Please hold while we process your call.');
|
|
$say->addAttribute('voice', 'alice');
|
|
|
|
// Add a pause then redirect to check again
|
|
$pause = $twiml->addChild('Pause');
|
|
$pause->addAttribute('length', '10'); // 10 second pause
|
|
|
|
// Redirect back to check if call has been added to queue
|
|
$redirect_url = home_url('/wp-json/twilio-webhook/v1/queue-wait');
|
|
$redirect_url = add_query_arg(array(
|
|
'queue_id' => $queue_id,
|
|
'call_sid' => urlencode($call_sid) // URL encode to handle special characters
|
|
), $redirect_url);
|
|
|
|
// Set the text content of Redirect element properly
|
|
$redirect = $twiml->addChild('Redirect');
|
|
$redirect[0] = $redirect_url; // Set the URL as the text content
|
|
$redirect->addAttribute('method', 'POST');
|
|
|
|
|
|
return $this->send_twiml_response($twiml->asXML());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle queue action (enqueue/dequeue events)
|
|
*/
|
|
public function handle_queue_action($request = null) {
|
|
$params = $request ? $request->get_params() : $_REQUEST;
|
|
$queue_id = isset($params['queue_id']) ? intval($params['queue_id']) : 0;
|
|
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
$queue_result = isset($params['QueueResult']) ? $params['QueueResult'] : '';
|
|
$from_number = isset($params['From']) ? $params['From'] : '';
|
|
$to_number = isset($params['To']) ? $params['To'] : '';
|
|
|
|
error_log('TWP Queue Action: queue_id=' . $queue_id . ', call_sid=' . $call_sid . ', result=' . $queue_result);
|
|
|
|
// Call left queue (answered, timeout, hangup, etc.) - update status
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
$status = 'completed';
|
|
if ($queue_result === 'timeout') {
|
|
$status = 'timeout';
|
|
} elseif ($queue_result === 'hangup') {
|
|
$status = 'hangup';
|
|
} elseif ($queue_result === 'bridged') {
|
|
$status = 'answered';
|
|
} elseif ($queue_result === 'leave') {
|
|
$status = 'transferred';
|
|
}
|
|
|
|
$updated = $wpdb->update(
|
|
$table_name,
|
|
array(
|
|
'status' => $status,
|
|
'ended_at' => current_time('mysql')
|
|
),
|
|
array('call_sid' => $call_sid),
|
|
array('%s', '%s'),
|
|
array('%s')
|
|
);
|
|
|
|
if ($updated) {
|
|
error_log('TWP Queue Action: Updated call status to ' . $status);
|
|
} else {
|
|
error_log('TWP Queue Action: No call found to update with SID ' . $call_sid);
|
|
}
|
|
|
|
// Return empty response - this is just for tracking
|
|
return $this->send_twiml_response('<Response></Response>');
|
|
}
|
|
|
|
/**
|
|
* Handle voicemail complete (after recording)
|
|
*/
|
|
public function handle_voicemail_complete($request) {
|
|
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
$twiml .= '<Response>';
|
|
$twiml .= '<Say voice="alice">Thank you for your message. Goodbye.</Say>';
|
|
$twiml .= '<Hangup/>';
|
|
$twiml .= '</Response>';
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Proxy voicemail audio through WordPress
|
|
*/
|
|
public function proxy_voicemail_audio($request) {
|
|
// Permission already checked by REST API permission_callback
|
|
$voicemail_id = intval($request->get_param('id'));
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$voicemail = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT recording_url FROM $table_name WHERE id = %d",
|
|
$voicemail_id
|
|
));
|
|
|
|
if (!$voicemail || !$voicemail->recording_url) {
|
|
header('HTTP/1.0 404 Not Found');
|
|
exit('Voicemail not found');
|
|
}
|
|
|
|
// Fetch the audio from Twilio using authenticated request
|
|
$twilio = new TWP_Twilio_API();
|
|
$account_sid = get_option('twp_twilio_account_sid');
|
|
$auth_token = get_option('twp_twilio_auth_token');
|
|
|
|
// Add .mp3 to the URL if not present
|
|
$audio_url = $voicemail->recording_url;
|
|
if (strpos($audio_url, '.mp3') === false && strpos($audio_url, '.wav') === false) {
|
|
$audio_url .= '.mp3';
|
|
}
|
|
|
|
// Fetch audio with authentication
|
|
$response = wp_remote_get($audio_url, array(
|
|
'headers' => array(
|
|
'Authorization' => 'Basic ' . base64_encode($account_sid . ':' . $auth_token)
|
|
),
|
|
'timeout' => 30
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
return new WP_Error('fetch_error', 'Unable to fetch audio', array('status' => 500));
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$content_type = wp_remote_retrieve_header($response, 'content-type') ?: 'audio/mpeg';
|
|
|
|
// Return audio with proper headers
|
|
header('Content-Type: ' . $content_type);
|
|
header('Content-Length: ' . strlen($body));
|
|
header('Cache-Control: private, max-age=3600');
|
|
echo $body;
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Handle voicemail callback
|
|
*/
|
|
public function handle_voicemail_callback($request) {
|
|
// Verify Twilio signature
|
|
if (!$this->verify_twilio_signature()) {
|
|
return new WP_Error('unauthorized', 'Unauthorized', array('status' => 401));
|
|
}
|
|
|
|
$params = $request->get_params();
|
|
|
|
// Debug logging
|
|
error_log('TWP Voicemail Callback Params: ' . json_encode($params));
|
|
|
|
$recording_url = isset($params['RecordingUrl']) ? $params['RecordingUrl'] : '';
|
|
$recording_duration = isset($params['RecordingDuration']) ? intval($params['RecordingDuration']) : 0;
|
|
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
$from = isset($params['From']) ? $params['From'] : '';
|
|
$workflow_id = isset($params['workflow_id']) ? intval($params['workflow_id']) : 0;
|
|
|
|
// If From is not provided in the callback, try to get it from the call log
|
|
if (empty($from) && !empty($call_sid)) {
|
|
global $wpdb;
|
|
$call_log_table = $wpdb->prefix . 'twp_call_log';
|
|
$call_record = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT from_number FROM $call_log_table WHERE call_sid = %s LIMIT 1",
|
|
$call_sid
|
|
));
|
|
if ($call_record && $call_record->from_number) {
|
|
$from = $call_record->from_number;
|
|
error_log('TWP Voicemail Callback: Retrieved from_number from call log: ' . $from);
|
|
}
|
|
}
|
|
|
|
// Debug what we extracted
|
|
error_log('TWP Voicemail Callback: recording_url=' . $recording_url . ', from=' . $from . ', workflow_id=' . $workflow_id . ', call_sid=' . $call_sid);
|
|
|
|
if ($recording_url) {
|
|
// Save voicemail record
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$voicemail_id = $wpdb->insert(
|
|
$table_name,
|
|
array(
|
|
'workflow_id' => $workflow_id,
|
|
'from_number' => $from,
|
|
'recording_url' => $recording_url,
|
|
'duration' => $recording_duration,
|
|
'created_at' => current_time('mysql')
|
|
),
|
|
array('%d', '%s', '%s', '%d', '%s')
|
|
);
|
|
|
|
// Log voicemail action
|
|
if ($call_sid) {
|
|
TWP_Call_Logger::log_action($call_sid, 'Voicemail recorded (' . $recording_duration . 's)');
|
|
}
|
|
|
|
// Send notification email
|
|
$this->send_voicemail_notification($from, $recording_url, $recording_duration, $voicemail_id);
|
|
|
|
// Schedule transcription if enabled
|
|
$this->schedule_voicemail_transcription($voicemail_id, $recording_url);
|
|
}
|
|
|
|
return new WP_REST_Response('<?xml version="1.0" encoding="UTF-8"?><Response></Response>', 200, array(
|
|
'Content-Type' => 'text/xml; charset=utf-8'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Send voicemail notification
|
|
*/
|
|
private function send_voicemail_notification($from_number, $recording_url, $duration, $voicemail_id) {
|
|
$admin_email = get_option('admin_email');
|
|
$site_name = get_bloginfo('name');
|
|
|
|
$subject = '[' . $site_name . '] New Voicemail from ' . $from_number;
|
|
|
|
$message = "You have received a new voicemail:\n\n";
|
|
$message .= "From: " . $from_number . "\n";
|
|
$message .= "Duration: " . $duration . " seconds\n";
|
|
$message .= "Received: " . current_time('F j, Y g:i A') . "\n\n";
|
|
$message .= "Listen to the voicemail in your admin panel:\n";
|
|
$message .= admin_url('admin.php?page=twilio-wp-voicemails') . "\n\n";
|
|
$message .= "Direct link to recording:\n";
|
|
$message .= $recording_url;
|
|
|
|
$headers = array('Content-Type: text/plain; charset=UTF-8');
|
|
|
|
wp_mail($admin_email, $subject, $message, $headers);
|
|
|
|
// Also send SMS notification if configured
|
|
$notification_number = get_option('twp_sms_notification_number');
|
|
if ($notification_number) {
|
|
$twilio = new TWP_Twilio_API();
|
|
$sms_message = "New voicemail from {$from_number} ({$duration}s). Check admin panel to listen.";
|
|
$twilio->send_sms($notification_number, $sms_message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule voicemail transcription
|
|
*/
|
|
private function schedule_voicemail_transcription($voicemail_id, $recording_url) {
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
// Mark transcription as pending - Twilio will call our transcription webhook when ready
|
|
$wpdb->update(
|
|
$table_name,
|
|
array('transcription' => 'Transcription pending...'),
|
|
array('id' => $voicemail_id),
|
|
array('%s'),
|
|
array('%d')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle transcription webhook
|
|
*/
|
|
public function handle_transcription_webhook($request) {
|
|
// Verify Twilio signature
|
|
if (!$this->verify_twilio_signature()) {
|
|
return new WP_Error('unauthorized', 'Unauthorized', array('status' => 401));
|
|
}
|
|
|
|
$params = $request->get_params();
|
|
|
|
$transcription_text = isset($params['TranscriptionText']) ? $params['TranscriptionText'] : '';
|
|
$recording_sid = isset($params['RecordingSid']) ? $params['RecordingSid'] : '';
|
|
$transcription_status = isset($params['TranscriptionStatus']) ? $params['TranscriptionStatus'] : '';
|
|
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
|
|
if ($transcription_status === 'completed' && $transcription_text) {
|
|
// Find voicemail by recording URL (we need to match by call_sid or recording URL)
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
// First try to find by recording URL containing the RecordingSid
|
|
$voicemail = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE recording_url LIKE %s ORDER BY created_at DESC LIMIT 1",
|
|
'%' . $recording_sid . '%'
|
|
));
|
|
|
|
if ($voicemail) {
|
|
// Update transcription
|
|
$wpdb->update(
|
|
$table_name,
|
|
array('transcription' => $transcription_text),
|
|
array('id' => $voicemail->id),
|
|
array('%s'),
|
|
array('%d')
|
|
);
|
|
|
|
// Log transcription completion
|
|
if ($call_sid) {
|
|
TWP_Call_Logger::log_action($call_sid, 'Voicemail transcription completed');
|
|
}
|
|
|
|
// Send notification if transcription contains keywords
|
|
$this->check_transcription_keywords($voicemail->id, $transcription_text, $voicemail->from_number);
|
|
}
|
|
} elseif ($transcription_status === 'failed') {
|
|
// Handle failed transcription
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$voicemail = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE recording_url LIKE %s ORDER BY created_at DESC LIMIT 1",
|
|
'%' . $recording_sid . '%'
|
|
));
|
|
|
|
if ($voicemail) {
|
|
$wpdb->update(
|
|
$table_name,
|
|
array('transcription' => 'Transcription failed'),
|
|
array('id' => $voicemail->id),
|
|
array('%s'),
|
|
array('%d')
|
|
);
|
|
}
|
|
|
|
if ($call_sid) {
|
|
TWP_Call_Logger::log_action($call_sid, 'Voicemail transcription failed');
|
|
}
|
|
}
|
|
|
|
return new WP_REST_Response('<?xml version="1.0" encoding="UTF-8"?><Response></Response>', 200, array(
|
|
'Content-Type' => 'text/xml; charset=utf-8'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Check transcription for important keywords
|
|
*/
|
|
private function check_transcription_keywords($voicemail_id, $transcription_text, $from_number) {
|
|
$urgent_keywords = get_option('twp_urgent_keywords', 'urgent,emergency,important,asap,help');
|
|
$keywords = array_map('trim', explode(',', strtolower($urgent_keywords)));
|
|
|
|
$transcription_lower = strtolower($transcription_text);
|
|
|
|
foreach ($keywords as $keyword) {
|
|
if (!empty($keyword) && strpos($transcription_lower, $keyword) !== false) {
|
|
// Send urgent notification
|
|
$this->send_urgent_voicemail_notification($voicemail_id, $transcription_text, $from_number, $keyword);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send urgent voicemail notification
|
|
*/
|
|
private function send_urgent_voicemail_notification($voicemail_id, $transcription_text, $from_number, $keyword) {
|
|
$admin_email = get_option('admin_email');
|
|
$site_name = get_bloginfo('name');
|
|
|
|
$subject = '[URGENT] ' . $site_name . ' - Voicemail from ' . $from_number;
|
|
|
|
$message = "URGENT VOICEMAIL DETECTED:\\n\\n";
|
|
$message .= "From: " . $from_number . "\\n";
|
|
$message .= "Keyword detected: " . $keyword . "\\n";
|
|
$message .= "Received: " . current_time('F j, Y g:i A') . "\\n\\n";
|
|
$message .= "Transcription:\\n" . $transcription_text . "\\n\\n";
|
|
$message .= "Listen to voicemail: " . admin_url('admin.php?page=twilio-wp-voicemails');
|
|
|
|
$headers = array('Content-Type: text/plain; charset=UTF-8');
|
|
|
|
wp_mail($admin_email, $subject, $message, $headers);
|
|
|
|
// Also send urgent SMS notification
|
|
$notification_number = get_option('twp_sms_notification_number');
|
|
if ($notification_number) {
|
|
$twilio = new TWP_Twilio_API();
|
|
$sms_message = "URGENT voicemail from {$from_number}. Keyword: {$keyword}. Check admin panel immediately.";
|
|
$twilio->send_sms($notification_number, $sms_message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send default response
|
|
*/
|
|
private function send_default_response() {
|
|
$response = new \Twilio\TwiML\VoiceResponse();
|
|
$response->say('Thank you for calling. Goodbye.', ['voice' => 'alice']);
|
|
$response->hangup();
|
|
echo $response->asXML();
|
|
}
|
|
|
|
/**
|
|
* Send status SMS
|
|
*/
|
|
private function send_status_sms($to_number) {
|
|
$twilio = new TWP_Twilio_API();
|
|
$queue_status = TWP_Call_Queue::get_queue_status();
|
|
|
|
$message = "Queue Status:\n";
|
|
foreach ($queue_status as $queue) {
|
|
$message .= $queue['queue_name'] . ': ' . $queue['waiting_calls'] . " waiting\n";
|
|
}
|
|
|
|
$twilio->send_sms($to_number, $message);
|
|
}
|
|
|
|
/**
|
|
* Send help SMS
|
|
*/
|
|
private function send_help_sms($to_number) {
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
$message = "Available commands:\n";
|
|
$message .= "STATUS - Get queue status\n";
|
|
$message .= "HELP - Show this message";
|
|
|
|
$twilio->send_sms($to_number, $message);
|
|
}
|
|
|
|
/**
|
|
* Log SMS
|
|
*/
|
|
private function log_sms($sms_data) {
|
|
// Store SMS in database for later processing
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_sms_log';
|
|
|
|
$wpdb->insert(
|
|
$table_name,
|
|
array(
|
|
'message_sid' => $sms_data['MessageSid'],
|
|
'from_number' => $sms_data['From'],
|
|
'to_number' => $sms_data['To'],
|
|
'body' => $sms_data['Body'],
|
|
'received_at' => current_time('mysql')
|
|
),
|
|
array('%s', '%s', '%s', '%s', '%s')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Log call status
|
|
*/
|
|
private function log_call_status($status_data) {
|
|
// Store call status in database
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_call_log';
|
|
|
|
$wpdb->insert(
|
|
$table_name,
|
|
array(
|
|
'call_sid' => $status_data['CallSid'],
|
|
'status' => $status_data['CallStatus'],
|
|
'duration' => $status_data['CallDuration'],
|
|
'updated_at' => current_time('mysql')
|
|
),
|
|
array('%s', '%s', '%d', '%s')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle callback choice webhook
|
|
*/
|
|
public function handle_callback_choice($request) {
|
|
$params = $request->get_params();
|
|
$digits = isset($params['Digits']) ? $params['Digits'] : '';
|
|
$phone_number = isset($params['Caller']) ? $params['Caller'] : '';
|
|
$queue_id = isset($params['queue_id']) ? intval($params['queue_id']) : null;
|
|
|
|
if ($digits === '1') {
|
|
// User chose to wait - redirect back to queue
|
|
$twiml = '<Response><Say voice="alice">Returning you to the queue.</Say><Redirect>' . home_url('/wp-json/twilio-webhook/v1/queue-wait?queue_id=' . $queue_id) . '</Redirect></Response>';
|
|
} else {
|
|
// Default to callback (digits === '2' or no input)
|
|
TWP_Callback_Manager::request_callback($phone_number, $queue_id);
|
|
$twiml = '<Response><Say voice="alice">Your callback has been requested. We will call you back shortly. Thank you!</Say><Hangup/></Response>';
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Handle request callback webhook
|
|
*/
|
|
public function handle_request_callback($request) {
|
|
$params = $request->get_params();
|
|
$phone_number = isset($params['phone_number']) ? $params['phone_number'] : '';
|
|
$queue_id = isset($params['queue_id']) ? intval($params['queue_id']) : null;
|
|
|
|
if ($phone_number) {
|
|
TWP_Callback_Manager::request_callback($phone_number, $queue_id);
|
|
$twiml = '<Response><Say voice="alice">Your callback has been requested. We will call you back shortly. Thank you!</Say><Hangup/></Response>';
|
|
} else {
|
|
$twiml = '<Response><Say voice="alice">Unable to process your callback request. Please try again.</Say><Hangup/></Response>';
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Handle callback agent webhook
|
|
*/
|
|
public function handle_callback_agent($request) {
|
|
$params = $request->get_params();
|
|
$callback_id = isset($params['callback_id']) ? intval($params['callback_id']) : 0;
|
|
$customer_number = isset($params['customer_number']) ? $params['customer_number'] : '';
|
|
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
|
|
if ($callback_id && $customer_number) {
|
|
TWP_Callback_Manager::handle_agent_answered($callback_id, $call_sid);
|
|
$twiml = '<Response><Say voice="alice">Please hold while we connect the customer.</Say><Play>http://com.twilio.music.classical.s3.amazonaws.com/BusyStrings.wav</Play></Response>';
|
|
} else {
|
|
$twiml = '<Response><Say voice="alice">Unable to process callback. Hanging up.</Say><Hangup/></Response>';
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Handle callback customer webhook
|
|
*/
|
|
public function handle_callback_customer($request) {
|
|
$params = $request->get_params();
|
|
$agent_call_sid = isset($params['agent_call_sid']) ? $params['agent_call_sid'] : '';
|
|
$callback_id = isset($params['callback_id']) ? intval($params['callback_id']) : 0;
|
|
|
|
if ($agent_call_sid) {
|
|
// Conference both calls together
|
|
$conference_name = 'callback-' . $callback_id . '-' . time();
|
|
$twiml = '<Response><Say voice="alice">You are being connected to an agent.</Say><Dial><Conference>' . $conference_name . '</Conference></Dial></Response>';
|
|
|
|
// Update the agent call to join the same conference
|
|
$twilio = new TWP_Twilio_API();
|
|
$agent_twiml = '<Response><Dial><Conference>' . $conference_name . '</Conference></Dial></Response>';
|
|
$twilio->update_call($agent_call_sid, array('twiml' => $agent_twiml));
|
|
|
|
// Mark callback as completed
|
|
TWP_Callback_Manager::complete_callback($callback_id);
|
|
} else {
|
|
$twiml = '<Response><Say voice="alice">Unable to connect your call. Please try again later.</Say><Hangup/></Response>';
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Handle outbound agent webhook
|
|
*/
|
|
public function handle_outbound_agent($request) {
|
|
$params = $request->get_params();
|
|
$target_number = isset($params['target_number']) ? $params['target_number'] : '';
|
|
$agent_call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
|
|
if ($target_number) {
|
|
$twiml = TWP_Callback_Manager::handle_outbound_agent_answered($target_number, $agent_call_sid);
|
|
} else {
|
|
$twiml = '<Response><Say voice="alice">Unable to process outbound call.</Say><Hangup/></Response>';
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Handle ring group result webhook
|
|
*/
|
|
public function handle_ring_group_result($request) {
|
|
$params = $request->get_params();
|
|
$dial_call_status = isset($params['DialCallStatus']) ? $params['DialCallStatus'] : '';
|
|
$group_id = isset($params['group_id']) ? intval($params['group_id']) : 0;
|
|
$queue_name = isset($params['queue_name']) ? $params['queue_name'] : '';
|
|
$fallback_action = isset($params['fallback_action']) ? $params['fallback_action'] : 'queue';
|
|
$caller_number = isset($params['From']) ? $params['From'] : '';
|
|
|
|
// If the call was answered, just hang up (call is connected)
|
|
if ($dial_call_status === 'completed') {
|
|
$twiml = '<Response></Response>';
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
// If no one answered, handle based on fallback action
|
|
if ($dial_call_status === 'no-answer' || $dial_call_status === 'busy' || $dial_call_status === 'failed') {
|
|
|
|
if ($fallback_action === 'queue' && !empty($queue_name)) {
|
|
// Put caller back in queue
|
|
$twiml = '<Response>';
|
|
$twiml .= '<Say voice="alice">No agents are currently available. Adding you to the queue.</Say>';
|
|
$twiml .= '<Enqueue waitUrl="' . home_url('/wp-json/twilio-webhook/v1/queue-wait?queue_name=' . urlencode($queue_name)) . '">' . $queue_name . '</Enqueue>';
|
|
$twiml .= '</Response>';
|
|
|
|
// Notify group members via SMS if no agents are available
|
|
$this->notify_group_members_sms($group_id, $caller_number, $queue_name);
|
|
|
|
} else if ($fallback_action === 'voicemail') {
|
|
// Go to voicemail
|
|
$twiml = '<Response>';
|
|
$twiml .= '<Say voice="alice">No one is available to take your call. Please leave a message after the beep.</Say>';
|
|
$twiml .= '<Record action="' . home_url('/wp-json/twilio-webhook/v1/voicemail-callback') . '" maxLength="300" playBeep="true" finishOnKey="#"/>';
|
|
$twiml .= '</Response>';
|
|
|
|
} else {
|
|
// Default message and callback option
|
|
$callback_twiml = TWP_Callback_Manager::create_callback_twiml(null, $caller_number);
|
|
return $this->send_twiml_response($callback_twiml);
|
|
}
|
|
} else {
|
|
// Unknown status, provide callback option
|
|
$callback_twiml = TWP_Callback_Manager::create_callback_twiml(null, $caller_number);
|
|
return $this->send_twiml_response($callback_twiml);
|
|
}
|
|
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Notify group members via SMS when no agents are available
|
|
*/
|
|
private function notify_group_members_sms($group_id, $caller_number, $queue_name) {
|
|
$members = TWP_Agent_Groups::get_group_members($group_id);
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
// Get SMS from number with proper priority (no workflow context here)
|
|
$from_number = TWP_Twilio_API::get_sms_from_number();
|
|
|
|
if (empty($from_number) || empty($members)) {
|
|
return;
|
|
}
|
|
|
|
$message = "Call waiting in queue '{$queue_name}' from {$caller_number}. Text '1' to this number to receive the next available call.";
|
|
|
|
foreach ($members as $member) {
|
|
$agent_phone = get_user_meta($member->user_id, 'twp_phone_number', true);
|
|
|
|
if (!empty($agent_phone)) {
|
|
// Send SMS notification with proper from number
|
|
$twilio->send_sms($agent_phone, $message, $from_number);
|
|
|
|
// Log the notification
|
|
error_log("TWP: SMS notification sent to agent {$member->user_id} at {$agent_phone} from {$from_number}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle agent ready SMS (when agent texts "1")
|
|
*/
|
|
private function handle_agent_ready_sms($incoming_number) {
|
|
error_log('TWP Agent Ready: Processing agent ready SMS from incoming_number: ' . $incoming_number);
|
|
|
|
// Standardized naming: incoming_number = phone number that sent the SMS to us
|
|
|
|
// Normalize phone number - add + prefix if missing
|
|
$agent_number = $incoming_number;
|
|
if (!empty($incoming_number) && substr($incoming_number, 0, 1) !== '+') {
|
|
$agent_number = '+' . $incoming_number;
|
|
error_log('TWP Agent Ready: Normalized agent_number to ' . $agent_number);
|
|
}
|
|
|
|
// Validate that this looks like a real phone number before proceeding
|
|
if (!preg_match('/^\+1[0-9]{10}$/', $agent_number)) {
|
|
error_log('TWP Agent Ready: Invalid phone number format: ' . $agent_number . ' - skipping error message send');
|
|
return; // Don't send error messages to invalid numbers
|
|
}
|
|
|
|
// Find user by phone number - try both original and normalized versions
|
|
$users = get_users(array(
|
|
'meta_key' => 'twp_phone_number',
|
|
'meta_value' => $agent_number,
|
|
'meta_compare' => '='
|
|
));
|
|
|
|
// If not found with normalized number, try original
|
|
if (empty($users)) {
|
|
$users = get_users(array(
|
|
'meta_key' => 'twp_phone_number',
|
|
'meta_value' => $incoming_number,
|
|
'meta_compare' => '='
|
|
));
|
|
error_log('TWP Agent Ready: Tried original phone format: ' . $incoming_number);
|
|
}
|
|
|
|
if (empty($users)) {
|
|
error_log('TWP Agent Ready: No user found for agent_number ' . $agent_number);
|
|
|
|
// Only send error message to valid real phone numbers (not test numbers)
|
|
if (preg_match('/^\+1[0-9]{10}$/', $agent_number) && !preg_match('/^\+1951234567[0-9]$/', $agent_number)) {
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
// Get default Twilio number for sending error messages
|
|
$default_number = TWP_Twilio_API::get_sms_from_number();
|
|
|
|
$twilio->send_sms($agent_number, "Phone number not found in system. Please contact administrator.", $default_number);
|
|
}
|
|
return;
|
|
}
|
|
|
|
$user = $users[0];
|
|
$user_id = $user->ID;
|
|
|
|
error_log('TWP Agent Ready: Found user ID ' . $user_id . ' for agent_number ' . $agent_number);
|
|
|
|
// Set agent status to available
|
|
TWP_Agent_Manager::set_agent_status($user_id, 'available');
|
|
|
|
// Check for waiting calls and assign one if available
|
|
error_log('TWP Agent Ready: Calling try_assign_call_to_agent for user ' . $user_id);
|
|
$assigned_call = $this->try_assign_call_to_agent($user_id, $agent_number);
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
// Get default Twilio number for sending confirmation messages
|
|
$default_number = TWP_Twilio_API::get_sms_from_number();
|
|
|
|
if ($assigned_call) {
|
|
$twilio->send_sms($agent_number, "Call assigned! You should receive the call shortly.", $default_number);
|
|
} else {
|
|
// No waiting calls, just confirm availability
|
|
$twilio->send_sms($agent_number, "Status updated to available. You'll receive the next waiting call.", $default_number);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle agent connect webhook (when agent answers SMS-triggered call)
|
|
*/
|
|
public function handle_agent_connect($request) {
|
|
$params = $request->get_params();
|
|
$queued_call_id = isset($params['queued_call_id']) ? intval($params['queued_call_id']) : 0;
|
|
$customer_call_sid = isset($params['customer_call_sid']) ? $params['customer_call_sid'] : '';
|
|
$customer_number = isset($params['customer_number']) ? $params['customer_number'] : '';
|
|
$agent_call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
$agent_phone = isset($params['agent_phone']) ? $params['agent_phone'] : '';
|
|
|
|
error_log('TWP Agent Connect: Handling agent connect - queued_call_id=' . $queued_call_id . ', customer_call_sid=' . $customer_call_sid . ', agent_call_sid=' . $agent_call_sid);
|
|
|
|
if (!$queued_call_id && !$customer_call_sid) {
|
|
error_log('TWP Agent Connect: Missing required parameters');
|
|
$twiml = '<Response><Say voice="alice">Unable to connect call.</Say><Hangup/></Response>';
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
// Get the queued call from database
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
if ($queued_call_id) {
|
|
$queued_call = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $calls_table WHERE id = %d",
|
|
$queued_call_id
|
|
));
|
|
} else {
|
|
$queued_call = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $calls_table WHERE call_sid = %s AND status IN ('waiting', 'connecting')",
|
|
$customer_call_sid
|
|
));
|
|
}
|
|
|
|
if (!$queued_call) {
|
|
error_log('TWP Agent Connect: Queued call not found');
|
|
$twiml = '<Response><Say voice="alice">The customer call is no longer available.</Say><Hangup/></Response>';
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
// Create conference to connect agent and customer
|
|
$conference_name = 'queue-connect-' . $queued_call->id . '-' . time();
|
|
|
|
error_log('TWP Agent Connect: Creating conference ' . $conference_name);
|
|
|
|
// Agent TwiML - connect agent to conference
|
|
$twiml = '<Response>';
|
|
$twiml .= '<Say voice="alice">Connecting you to the customer now.</Say>';
|
|
$twiml .= '<Dial timeout="30" action="' . home_url('/wp-json/twilio-webhook/v1/agent-call-status') . '">';
|
|
$twiml .= '<Conference startConferenceOnEnter="true" endConferenceOnExit="true">' . $conference_name . '</Conference>';
|
|
$twiml .= '</Dial>';
|
|
$twiml .= '</Response>';
|
|
|
|
// Connect customer to the same conference
|
|
$customer_twiml = '<Response>';
|
|
$customer_twiml .= '<Say voice="alice">An agent is now available. Connecting you now.</Say>';
|
|
$customer_twiml .= '<Dial timeout="300">';
|
|
$customer_twiml .= '<Conference startConferenceOnEnter="false" endConferenceOnExit="false">' . $conference_name . '</Conference>';
|
|
$customer_twiml .= '</Dial>';
|
|
$customer_twiml .= '<Say voice="alice">The call has ended. Thank you.</Say>';
|
|
$customer_twiml .= '</Response>';
|
|
|
|
try {
|
|
$twilio = new TWP_Twilio_API();
|
|
$update_result = $twilio->update_call($queued_call->call_sid, array('twiml' => $customer_twiml));
|
|
|
|
if ($update_result['success']) {
|
|
error_log('TWP Agent Connect: Successfully updated customer call with conference TwiML');
|
|
|
|
// Update call status to connected/answered
|
|
$updated = $wpdb->update(
|
|
$calls_table,
|
|
array(
|
|
'status' => 'answered',
|
|
'agent_phone' => $agent_phone,
|
|
'agent_call_sid' => $agent_call_sid,
|
|
'answered_at' => current_time('mysql')
|
|
),
|
|
array('id' => $queued_call->id),
|
|
array('%s', '%s', '%s', '%s'),
|
|
array('%d')
|
|
);
|
|
|
|
if ($updated) {
|
|
error_log('TWP Agent Connect: Updated call status to answered');
|
|
} else {
|
|
error_log('TWP Agent Connect: Failed to update call status');
|
|
}
|
|
|
|
// Reorder queue positions after removing this call
|
|
TWP_Call_Queue::reorder_queue($queued_call->queue_id);
|
|
|
|
} else {
|
|
error_log('TWP Agent Connect: Failed to update customer call: ' . ($update_result['error'] ?? 'Unknown error'));
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TWP Agent Connect: Exception updating customer call: ' . $e->getMessage());
|
|
}
|
|
|
|
error_log('TWP Agent Connect: Returning agent TwiML: ' . $twiml);
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
/**
|
|
* Try to assign a waiting call to the agent
|
|
*/
|
|
private function try_assign_call_to_agent($user_id, $agent_number) {
|
|
error_log('TWP Call Assignment: Starting try_assign_call_to_agent for user ' . $user_id . ' agent_number ' . $agent_number);
|
|
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
// Find the longest waiting call that this agent can handle
|
|
// Get agent's groups first
|
|
$groups_table = $wpdb->prefix . 'twp_group_members';
|
|
$agent_groups = $wpdb->get_col($wpdb->prepare("
|
|
SELECT group_id FROM $groups_table WHERE user_id = %d
|
|
", $user_id));
|
|
|
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
|
|
|
if (!empty($agent_groups)) {
|
|
// Find waiting calls from queues assigned to this agent's groups
|
|
$placeholders = implode(',', array_fill(0, count($agent_groups), '%d'));
|
|
$waiting_call = $wpdb->get_row($wpdb->prepare("
|
|
SELECT qc.*, q.phone_number as queue_phone_number, q.agent_group_id
|
|
FROM $calls_table qc
|
|
LEFT JOIN $queues_table q ON qc.queue_id = q.id
|
|
WHERE qc.status = 'waiting'
|
|
AND (q.agent_group_id IN ($placeholders) OR q.agent_group_id IS NULL)
|
|
ORDER BY qc.joined_at ASC
|
|
LIMIT 1
|
|
", ...$agent_groups));
|
|
} else {
|
|
// Agent not in any group - can only handle calls from queues with no assigned group
|
|
$waiting_call = $wpdb->get_row($wpdb->prepare("
|
|
SELECT qc.*, q.phone_number as queue_phone_number, q.agent_group_id
|
|
FROM $calls_table qc
|
|
LEFT JOIN $queues_table q ON qc.queue_id = q.id
|
|
WHERE qc.status = %s
|
|
AND q.agent_group_id IS NULL
|
|
ORDER BY qc.joined_at ASC
|
|
LIMIT 1
|
|
", 'waiting'));
|
|
}
|
|
|
|
if (!$waiting_call) {
|
|
return false;
|
|
}
|
|
|
|
// Determine which Twilio number to use as caller ID when calling the agent
|
|
// Priority: 1) Queue's workflow_number, 2) Original workflow_number, 3) default_number
|
|
$workflow_number = null;
|
|
|
|
error_log('TWP Debug: Waiting call data: ' . print_r($waiting_call, true));
|
|
|
|
// Detailed debugging of phone number selection
|
|
error_log('TWP Debug: Queue workflow_number field: ' . (empty($waiting_call->queue_phone_number) ? 'EMPTY' : $waiting_call->queue_phone_number));
|
|
error_log('TWP Debug: Original workflow_number field: ' . (empty($waiting_call->to_number) ? 'EMPTY' : $waiting_call->to_number));
|
|
|
|
if (!empty($waiting_call->queue_phone_number)) {
|
|
$workflow_number = $waiting_call->queue_phone_number;
|
|
error_log('TWP Debug: SELECTED queue workflow_number: ' . $workflow_number);
|
|
} elseif (!empty($waiting_call->to_number)) {
|
|
$workflow_number = $waiting_call->to_number;
|
|
error_log('TWP Debug: SELECTED original workflow_number: ' . $workflow_number);
|
|
} else {
|
|
$workflow_number = TWP_Twilio_API::get_sms_from_number();
|
|
error_log('TWP Debug: SELECTED default_number: ' . $workflow_number);
|
|
}
|
|
|
|
error_log('TWP Debug: Final workflow_number for agent call: ' . $workflow_number);
|
|
|
|
// Make call to agent using the determined workflow number as caller ID
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
// Build agent connect URL with parameters
|
|
$agent_connect_url = home_url('/wp-json/twilio-webhook/v1/agent-connect') . '?' . http_build_query(array(
|
|
'queued_call_id' => $waiting_call->id,
|
|
'customer_number' => $waiting_call->from_number
|
|
));
|
|
|
|
$call_result = $twilio->make_call(
|
|
$agent_number, // To: agent's phone number
|
|
$agent_connect_url, // TwiML URL with parameters
|
|
null, // No status callback needed
|
|
$workflow_number // From: workflow number as caller ID
|
|
);
|
|
|
|
if ($call_result['success']) {
|
|
// Update queued call status
|
|
$wpdb->update(
|
|
$calls_table,
|
|
array(
|
|
'status' => 'connecting',
|
|
'answered_at' => current_time('mysql')
|
|
),
|
|
array('id' => $waiting_call->id),
|
|
array('%s', '%s'),
|
|
array('%d')
|
|
);
|
|
|
|
// Set agent to busy
|
|
TWP_Agent_Manager::set_agent_status($user_id, 'busy', $call_result['call_sid']);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handle agent screening - called when agent answers, before connecting to customer
|
|
*/
|
|
public function handle_agent_screen($request) {
|
|
$params = $request->get_params();
|
|
$queued_call_id = isset($params['queued_call_id']) ? intval($params['queued_call_id']) : 0;
|
|
$customer_number = isset($params['customer_number']) ? $params['customer_number'] : '';
|
|
$customer_call_sid = isset($params['customer_call_sid']) ? $params['customer_call_sid'] : '';
|
|
$agent_call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
|
|
error_log("TWP Agent Screen: QueuedCallId={$queued_call_id}, CustomerCallSid={$customer_call_sid}, AgentCallSid={$agent_call_sid}");
|
|
|
|
if (!$queued_call_id || !$customer_call_sid) {
|
|
$twiml = '<Response><Say voice="alice">Unable to connect call.</Say><Hangup/></Response>';
|
|
return $this->send_twiml_response($twiml);
|
|
}
|
|
|
|
// Screen the agent - ask them to press a key to confirm they're human
|
|
$screen_url = home_url('/wp-json/twilio-webhook/v1/agent-confirm');
|
|
$screen_url = add_query_arg(array(
|
|
'queued_call_id' => $queued_call_id,
|
|
'customer_call_sid' => $customer_call_sid,
|
|
'agent_call_sid' => $agent_call_sid
|
|
), $screen_url);
|
|
|
|
// Use proper TwiML generation
|
|
$response = new \Twilio\TwiML\VoiceResponse();
|
|
|
|
$gather = $response->gather([
|
|
'timeout' => 10,
|
|
'numDigits' => 1,
|
|
'action' => $screen_url,
|
|
'method' => 'POST'
|
|
]);
|
|
$gather->say('You have an incoming call. Press any key to accept and connect to the caller.', ['voice' => 'alice']);
|
|
|
|
$response->say('No response received. Call cancelled.', ['voice' => 'alice']);
|
|
$response->hangup();
|
|
|
|
return $this->send_twiml_response($response->asXML());
|
|
}
|
|
|
|
/**
|
|
* Handle agent confirmation - called when agent presses key to confirm
|
|
*/
|
|
public function handle_agent_confirm($request) {
|
|
$params = $request->get_params();
|
|
$queued_call_id = isset($params['queued_call_id']) ? intval($params['queued_call_id']) : 0;
|
|
$customer_call_sid = isset($params['customer_call_sid']) ? $params['customer_call_sid'] : '';
|
|
$agent_call_sid = isset($params['agent_call_sid']) ? $params['agent_call_sid'] : '';
|
|
$digits = isset($params['Digits']) ? $params['Digits'] : '';
|
|
|
|
error_log("TWP Agent Confirm: Digits={$digits}, QueuedCallId={$queued_call_id}, CustomerCallSid={$customer_call_sid}, AgentCallSid={$agent_call_sid}");
|
|
|
|
if (!$digits) {
|
|
// No key pressed - agent didn't confirm
|
|
error_log("TWP Agent Confirm: No key pressed, cancelling call");
|
|
|
|
// Requeue the call for another agent
|
|
$this->handle_agent_no_answer($queued_call_id, 0, 'no_response');
|
|
|
|
$response = new \Twilio\TwiML\VoiceResponse();
|
|
$response->say('Call cancelled.', ['voice' => 'alice']);
|
|
$response->hangup();
|
|
return $this->send_twiml_response($response->asXML());
|
|
}
|
|
|
|
// Agent confirmed - now connect both calls to a conference
|
|
$conference_name = 'queue-connect-' . $queued_call_id . '-' . time();
|
|
|
|
error_log("TWP Agent Confirm: Creating conference {$conference_name} to connect customer and agent");
|
|
|
|
// Connect agent to conference using proper TwiML
|
|
$response = new \Twilio\TwiML\VoiceResponse();
|
|
$response->say('Connecting you now.', ['voice' => 'alice']);
|
|
$dial = $response->dial();
|
|
$dial->conference($conference_name);
|
|
|
|
// Connect customer to the same conference
|
|
$this->connect_customer_to_conference($customer_call_sid, $conference_name, $queued_call_id);
|
|
|
|
return $this->send_twiml_response($response->asXML());
|
|
}
|
|
|
|
/**
|
|
* Connect customer to conference
|
|
*/
|
|
private function connect_customer_to_conference($customer_call_sid, $conference_name, $queued_call_id) {
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
// Create TwiML to connect customer to conference using proper SDK
|
|
$customer_response = new \Twilio\TwiML\VoiceResponse();
|
|
$customer_response->say('Connecting you to an agent.', ['voice' => 'alice']);
|
|
$customer_dial = $customer_response->dial();
|
|
$customer_dial->conference($conference_name);
|
|
|
|
$customer_twiml = $customer_response->asXML();
|
|
|
|
// Update the customer call to join the conference
|
|
$result = $twilio->update_call($customer_call_sid, array(
|
|
'twiml' => $customer_twiml
|
|
));
|
|
|
|
if ($result['success']) {
|
|
// Update queued call status to connected
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
$wpdb->update(
|
|
$calls_table,
|
|
array(
|
|
'status' => 'connected',
|
|
'answered_at' => current_time('mysql')
|
|
),
|
|
array('id' => $queued_call_id),
|
|
array('%s', '%s'),
|
|
array('%d')
|
|
);
|
|
|
|
error_log("TWP Agent Confirm: Successfully connected customer {$customer_call_sid} to conference {$conference_name}");
|
|
} else {
|
|
error_log("TWP Agent Confirm: Failed to connect customer to conference: " . print_r($result, true));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle outbound agent with from number webhook
|
|
*/
|
|
public function handle_outbound_agent_with_from($request) {
|
|
try {
|
|
$params = $request->get_params();
|
|
|
|
// Get parameters from query string or POST body
|
|
$target_number = $request->get_param('target_number') ?: '';
|
|
$from_number = $request->get_param('from_number') ?: '';
|
|
$agent_call_sid = $request->get_param('CallSid') ?: '';
|
|
|
|
// Log parameters for debugging
|
|
error_log('TWP Outbound Webhook - Target: ' . $target_number . ', From: ' . $from_number . ', CallSid: ' . $agent_call_sid);
|
|
error_log('TWP Outbound Webhook - All params: ' . print_r($params, true));
|
|
|
|
if ($target_number && $from_number) {
|
|
// Create TwiML using SDK directly
|
|
$response = new \Twilio\TwiML\VoiceResponse();
|
|
$response->say('Connecting your outbound call...', ['voice' => 'alice']);
|
|
$response->dial($target_number, ['callerId' => $from_number, 'timeout' => 30]);
|
|
|
|
// If call isn't answered, the TwiML will handle the fallback
|
|
return $this->send_twiml_response($response->asXML());
|
|
} else {
|
|
// Enhanced error message with debugging info
|
|
$error_msg = 'Unable to process outbound call.';
|
|
if (empty($target_number)) {
|
|
$error_msg .= ' Missing target number.';
|
|
}
|
|
if (empty($from_number)) {
|
|
$error_msg .= ' Missing from number.';
|
|
}
|
|
|
|
error_log('TWP Outbound Error: ' . $error_msg . ' Params: ' . json_encode($params));
|
|
|
|
$error_response = new \Twilio\TwiML\VoiceResponse();
|
|
$error_response->say($error_msg, ['voice' => 'alice']);
|
|
$error_response->hangup();
|
|
return $this->send_twiml_response($error_response->asXML());
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TWP Outbound Webhook Exception: ' . $e->getMessage());
|
|
$exception_response = new \Twilio\TwiML\VoiceResponse();
|
|
$exception_response->say('Technical error occurred. Please try again.', ['voice' => 'alice']);
|
|
$exception_response->hangup();
|
|
return $this->send_twiml_response($exception_response->asXML());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle agent call status to detect voicemail/no-answer
|
|
*/
|
|
public function handle_agent_call_status($request) {
|
|
$params = $request->get_params();
|
|
|
|
$call_status = isset($params['CallStatus']) ? $params['CallStatus'] : '';
|
|
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
|
$queued_call_id = isset($params['queued_call_id']) ? intval($params['queued_call_id']) : 0;
|
|
$user_id = isset($params['user_id']) ? intval($params['user_id']) : 0;
|
|
$original_call_sid = isset($params['original_call_sid']) ? $params['original_call_sid'] : '';
|
|
|
|
// Check for machine detection
|
|
$answered_by = isset($params['AnsweredBy']) ? $params['AnsweredBy'] : '';
|
|
$machine_detection_duration = isset($params['MachineDetectionDuration']) ? $params['MachineDetectionDuration'] : '';
|
|
|
|
error_log("TWP Agent Call Status: CallSid={$call_sid}, Status={$call_status}, AnsweredBy={$answered_by}, QueuedCallId={$queued_call_id}");
|
|
|
|
// Handle different call statuses
|
|
switch ($call_status) {
|
|
case 'no-answer':
|
|
case 'busy':
|
|
case 'failed':
|
|
// Agent didn't answer or was busy - requeue the call or try next agent
|
|
$this->handle_agent_no_answer($queued_call_id, $user_id, $call_status);
|
|
break;
|
|
|
|
case 'answered':
|
|
// Check if it was answered by a machine (voicemail) or human
|
|
if ($answered_by === 'machine') {
|
|
// Call went to voicemail - treat as no-answer
|
|
error_log("TWP Agent Call Status: Agent {$user_id} call went to voicemail - requeing");
|
|
$this->handle_agent_no_answer($queued_call_id, $user_id, 'voicemail');
|
|
} else {
|
|
// Agent actually answered - they're already set to busy
|
|
error_log("TWP Agent Call Status: Agent {$user_id} answered call {$call_sid}");
|
|
}
|
|
break;
|
|
|
|
case 'completed':
|
|
// Check if call was completed because it went to voicemail
|
|
if ($answered_by === 'machine_start' || $answered_by === 'machine_end_beep' || $answered_by === 'machine_end_silence') {
|
|
// Call went to voicemail - treat as no-answer and requeue
|
|
error_log("TWP Agent Call Status: Agent {$user_id} call completed via voicemail ({$answered_by}) - requeuing");
|
|
$this->handle_agent_no_answer($queued_call_id, $user_id, 'voicemail');
|
|
} else {
|
|
// Call completed normally - set agent back to available
|
|
TWP_Agent_Manager::set_agent_status($user_id, 'available');
|
|
error_log("TWP Agent Call Status: Agent {$user_id} call completed normally, set to available");
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Return empty response
|
|
return $this->send_twiml_response('<Response></Response>');
|
|
}
|
|
|
|
/**
|
|
* Handle when agent doesn't answer (voicemail, busy, no-answer)
|
|
*/
|
|
private function handle_agent_no_answer($queued_call_id, $user_id, $status) {
|
|
error_log("TWP Agent No Answer: QueuedCallId={$queued_call_id}, UserId={$user_id}, Status={$status}");
|
|
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
// Get the queued call info
|
|
$queued_call = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $calls_table WHERE id = %d",
|
|
$queued_call_id
|
|
));
|
|
|
|
if (!$queued_call) {
|
|
error_log("TWP Agent No Answer: Queued call not found for ID {$queued_call_id}");
|
|
return;
|
|
}
|
|
|
|
// Set agent back to available
|
|
TWP_Agent_Manager::set_agent_status($user_id, 'available');
|
|
|
|
// Put the call back in waiting status for other agents to pick up
|
|
$wpdb->update(
|
|
$calls_table,
|
|
array(
|
|
'status' => 'waiting',
|
|
'answered_at' => null
|
|
),
|
|
array('id' => $queued_call_id),
|
|
array('%s', '%s'),
|
|
array('%d')
|
|
);
|
|
|
|
error_log("TWP Agent No Answer: Call {$queued_call_id} returned to queue, agent {$user_id} set to available");
|
|
|
|
// Optionally: Try to assign to another available agent
|
|
// $this->try_assign_to_next_agent($queued_call->queue_id, $queued_call_id);
|
|
}
|
|
} |