Fix extension transfer system and browser phone compatibility
Major Fixes: - Fixed extension transfers going directly to voicemail for available agents - Resolved browser phone call disconnections during transfers - Fixed voicemail transcription placeholder text issue - Added Firefox compatibility with automatic media permissions Extension Transfer Improvements: - Changed from active client dialing to proper queue-based system - Fixed client name generation consistency (user_login vs display_name) - Added 2-minute timeout with automatic voicemail fallback - Enhanced agent availability detection for browser phone users Browser Phone Enhancements: - Added automatic microphone/speaker permission requests - Improved Firefox compatibility with explicit getUserMedia calls - Fixed client naming consistency across capability tokens and call acceptance - Added comprehensive error handling for permission denials Database & System Updates: - Added auto_busy_at column for automatic agent status reversion - Implemented 1-minute auto-revert system for busy agents with cron job - Updated database version to 1.6.2 for automatic migration - Fixed voicemail user_id association for extension voicemails Call Statistics & Logging: - Fixed browser phone calls not appearing in agent statistics - Enhanced call logging with proper agent_id association in JSON format - Improved customer number detection for complex call topologies - Added comprehensive debugging for call leg detection Voicemail & Transcription: - Replaced placeholder transcription with real Twilio API integration - Added manual transcription request capability for existing voicemails - Enhanced voicemail callback handling with user_id support - Fixed transcription webhook processing for extension voicemails Technical Improvements: - Standardized client name generation across all components - Added ElevenLabs TTS integration to agent connection messages - Enhanced error handling and logging throughout transfer system - Fixed TwiML generation syntax errors in dial() methods 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,11 @@ class TWP_Activator {
|
||||
|
||||
// Create webhook endpoints
|
||||
flush_rewrite_rules();
|
||||
|
||||
// Initialize user queues for existing users with phone numbers
|
||||
if (class_exists('TWP_User_Queue_Manager')) {
|
||||
TWP_User_Queue_Manager::initialize_all_user_queues();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -428,6 +433,14 @@ class TWP_Activator {
|
||||
}
|
||||
}
|
||||
|
||||
// Add user_id column to voicemails table for extension voicemails
|
||||
$table_voicemails = $wpdb->prefix . 'twp_voicemails';
|
||||
$voicemail_user_id_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_voicemails LIKE 'user_id'");
|
||||
if (empty($voicemail_user_id_exists)) {
|
||||
$wpdb->query("ALTER TABLE $table_voicemails ADD COLUMN user_id int(11) DEFAULT NULL AFTER workflow_id");
|
||||
$wpdb->query("ALTER TABLE $table_voicemails ADD INDEX user_id (user_id)");
|
||||
}
|
||||
|
||||
// Add login tracking columns to agent_status table
|
||||
$table_agent_status = $wpdb->prefix . 'twp_agent_status';
|
||||
|
||||
@@ -441,6 +454,12 @@ class TWP_Activator {
|
||||
$wpdb->query("ALTER TABLE $table_agent_status ADD COLUMN logged_in_at datetime AFTER is_logged_in");
|
||||
}
|
||||
|
||||
// Add auto_busy_at column to track when agent was automatically set to busy
|
||||
$auto_busy_at_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_agent_status LIKE 'auto_busy_at'");
|
||||
if (empty($auto_busy_at_exists)) {
|
||||
$wpdb->query("ALTER TABLE $table_agent_status ADD COLUMN auto_busy_at datetime DEFAULT NULL AFTER logged_in_at");
|
||||
}
|
||||
|
||||
$table_schedules = $wpdb->prefix . 'twp_phone_schedules';
|
||||
|
||||
// Check if holiday_dates column exists
|
||||
|
@@ -95,7 +95,7 @@ class TWP_Agent_Manager {
|
||||
/**
|
||||
* Set agent status
|
||||
*/
|
||||
public static function set_agent_status($user_id, $status, $call_sid = null) {
|
||||
public static function set_agent_status($user_id, $status, $call_sid = null, $auto_set = false) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . 'twp_agent_status';
|
||||
|
||||
@@ -108,6 +108,18 @@ class TWP_Agent_Manager {
|
||||
$is_logged_in = $existing ? $existing->is_logged_in : 0;
|
||||
$logged_in_at = $existing ? $existing->logged_in_at : null;
|
||||
|
||||
// Set auto_busy_at timestamp if automatically setting to busy
|
||||
$auto_busy_at = null;
|
||||
if ($auto_set && $status === 'busy') {
|
||||
$auto_busy_at = current_time('mysql');
|
||||
} elseif ($status !== 'busy') {
|
||||
// Clear auto_busy_at when changing from busy to any other status
|
||||
$auto_busy_at = null;
|
||||
} else {
|
||||
// Preserve existing auto_busy_at if manually setting to busy or not changing
|
||||
$auto_busy_at = $existing ? $existing->auto_busy_at : null;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
return $wpdb->update(
|
||||
$table_name,
|
||||
@@ -116,10 +128,11 @@ class TWP_Agent_Manager {
|
||||
'current_call_sid' => $call_sid,
|
||||
'last_activity' => current_time('mysql'),
|
||||
'is_logged_in' => $is_logged_in,
|
||||
'logged_in_at' => $logged_in_at
|
||||
'logged_in_at' => $logged_in_at,
|
||||
'auto_busy_at' => $auto_busy_at
|
||||
),
|
||||
array('user_id' => $user_id),
|
||||
array('%s', '%s', '%s', '%d', '%s'),
|
||||
array('%s', '%s', '%s', '%d', '%s', '%s'),
|
||||
array('%d')
|
||||
);
|
||||
} else {
|
||||
@@ -131,9 +144,10 @@ class TWP_Agent_Manager {
|
||||
'current_call_sid' => $call_sid,
|
||||
'last_activity' => current_time('mysql'),
|
||||
'is_logged_in' => 0,
|
||||
'logged_in_at' => null
|
||||
'logged_in_at' => null,
|
||||
'auto_busy_at' => $auto_busy_at
|
||||
),
|
||||
array('%d', '%s', '%s', '%s', '%d', '%s')
|
||||
array('%d', '%s', '%s', '%s', '%d', '%s', '%s')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -172,10 +186,11 @@ class TWP_Agent_Manager {
|
||||
'is_logged_in' => $is_logged_in ? 1 : 0,
|
||||
'logged_in_at' => $logged_in_at,
|
||||
'last_activity' => current_time('mysql'),
|
||||
'status' => $is_logged_in ? 'available' : 'offline'
|
||||
'status' => $is_logged_in ? 'available' : 'offline',
|
||||
'auto_busy_at' => null // Clear auto_busy_at when changing login status
|
||||
),
|
||||
array('user_id' => $user_id),
|
||||
array('%d', '%s', '%s', '%s'),
|
||||
array('%d', '%s', '%s', '%s', '%s'),
|
||||
array('%d')
|
||||
);
|
||||
} else {
|
||||
@@ -186,9 +201,10 @@ class TWP_Agent_Manager {
|
||||
'status' => $is_logged_in ? 'available' : 'offline',
|
||||
'is_logged_in' => $is_logged_in ? 1 : 0,
|
||||
'logged_in_at' => $logged_in_at,
|
||||
'last_activity' => current_time('mysql')
|
||||
'last_activity' => current_time('mysql'),
|
||||
'auto_busy_at' => null
|
||||
),
|
||||
array('%d', '%s', '%d', '%s', '%s')
|
||||
array('%d', '%s', '%d', '%s', '%s', '%s')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -316,7 +332,7 @@ class TWP_Agent_Manager {
|
||||
);
|
||||
|
||||
// Set agent status to busy
|
||||
self::set_agent_status($user_id, 'busy', $call->call_sid);
|
||||
self::set_agent_status($user_id, 'busy', $call->call_sid, true);
|
||||
|
||||
// Make a new call to the agent with proper caller ID
|
||||
$twilio = new TWP_Twilio_API();
|
||||
@@ -362,15 +378,23 @@ class TWP_Agent_Manager {
|
||||
// For browser mode, redirect the existing call to the browser client
|
||||
$current_user = get_userdata($user_id);
|
||||
// Twilio requires alphanumeric characters only - must match generate_capability_token
|
||||
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $current_user->display_name);
|
||||
// Use user_login for consistency with capability token generation
|
||||
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $current_user->user_login);
|
||||
if (empty($clean_name)) {
|
||||
$clean_name = 'user';
|
||||
}
|
||||
$client_name = 'agent' . $user_id . $clean_name;
|
||||
|
||||
error_log("TWP Accept: Redirecting call {$call->call_sid} to browser client '{$client_name}' for user {$user_id}");
|
||||
|
||||
// Create TwiML to redirect call to browser client
|
||||
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||||
$twiml->say('Connecting you to an agent.', ['voice' => 'alice']);
|
||||
|
||||
// Use TTS helper for ElevenLabs integration
|
||||
require_once plugin_dir_path(__FILE__) . 'class-twp-tts-helper.php';
|
||||
$tts_helper = TWP_TTS_Helper::get_instance();
|
||||
$tts_helper->add_tts_to_twiml($twiml, 'Connecting you to an agent.');
|
||||
|
||||
$dial = $twiml->dial();
|
||||
$dial->setAttribute('timeout', 30);
|
||||
$dial->client($client_name);
|
||||
@@ -594,6 +618,57 @@ class TWP_Agent_Manager {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and revert agents from auto-busy to available after 1 minute
|
||||
*/
|
||||
public static function revert_auto_busy_agents() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . 'twp_agent_status';
|
||||
|
||||
// Find agents who have been auto-busy for more than 1 minute and are still logged in
|
||||
$cutoff_time = date('Y-m-d H:i:s', strtotime('-1 minute'));
|
||||
|
||||
$auto_busy_agents = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT user_id, current_call_sid FROM $table_name
|
||||
WHERE status = 'busy'
|
||||
AND auto_busy_at IS NOT NULL
|
||||
AND auto_busy_at < %s
|
||||
AND is_logged_in = 1",
|
||||
$cutoff_time
|
||||
));
|
||||
|
||||
foreach ($auto_busy_agents as $agent) {
|
||||
// Verify the call is actually finished before reverting
|
||||
$call_sid = $agent->current_call_sid;
|
||||
$call_active = false;
|
||||
|
||||
if ($call_sid) {
|
||||
// Check if call is still active using Twilio API
|
||||
try {
|
||||
$api = new TWP_Twilio_API();
|
||||
$call_status = $api->get_call_status($call_sid);
|
||||
|
||||
// If call is still in progress, don't revert yet
|
||||
if (in_array($call_status, ['queued', 'ringing', 'in-progress'])) {
|
||||
$call_active = true;
|
||||
error_log("TWP Auto-Revert: Call {$call_sid} still active for user {$agent->user_id}, keeping busy");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("TWP Auto-Revert: Could not check call status for {$call_sid}: " . $e->getMessage());
|
||||
// If we can't check call status, assume it's finished and proceed with revert
|
||||
}
|
||||
}
|
||||
|
||||
// Only revert if call is not active
|
||||
if (!$call_active) {
|
||||
error_log("TWP Auto-Revert: Reverting user {$agent->user_id} from auto-busy to available");
|
||||
self::set_agent_status($agent->user_id, 'available', null, false);
|
||||
}
|
||||
}
|
||||
|
||||
return count($auto_busy_agents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate phone number format
|
||||
*/
|
||||
|
@@ -111,7 +111,7 @@ class TWP_Callback_Manager {
|
||||
);
|
||||
|
||||
// Set agent to busy
|
||||
TWP_Agent_Manager::set_agent_status($agent->user_id, 'busy', $agent_call_result['call_sid']);
|
||||
TWP_Agent_Manager::set_agent_status($agent->user_id, 'busy', $agent_call_result['call_sid'], true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -206,7 +206,7 @@ class TWP_Callback_Manager {
|
||||
|
||||
if ($agent_call_result['success']) {
|
||||
// Set agent to busy
|
||||
TWP_Agent_Manager::set_agent_status($agent_user_id, 'busy', $agent_call_result['call_sid']);
|
||||
TWP_Agent_Manager::set_agent_status($agent_user_id, 'busy', $agent_call_result['call_sid'], true);
|
||||
|
||||
// Log the outbound call
|
||||
TWP_Call_Logger::log_call(array(
|
||||
|
@@ -157,6 +157,7 @@ class TWP_Core {
|
||||
$this->loader->add_action('wp_ajax_twp_send_to_voicemail', $plugin_admin, 'ajax_send_to_voicemail');
|
||||
$this->loader->add_action('wp_ajax_twp_disconnect_call', $plugin_admin, 'ajax_disconnect_call');
|
||||
$this->loader->add_action('wp_ajax_twp_get_transfer_targets', $plugin_admin, 'ajax_get_transfer_targets');
|
||||
$this->loader->add_action('wp_ajax_twp_initialize_user_queues', $plugin_admin, 'ajax_initialize_user_queues');
|
||||
|
||||
// Eleven Labs AJAX
|
||||
$this->loader->add_action('wp_ajax_twp_get_elevenlabs_voices', $plugin_admin, 'ajax_get_elevenlabs_voices');
|
||||
|
@@ -11,6 +11,7 @@ class TWP_Deactivator {
|
||||
// Clear scheduled events
|
||||
wp_clear_scheduled_hook('twp_check_schedules');
|
||||
wp_clear_scheduled_hook('twp_process_queue');
|
||||
wp_clear_scheduled_hook('twp_auto_revert_agents');
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
|
@@ -209,6 +209,19 @@ class TWP_Twilio_API {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get call status only
|
||||
*/
|
||||
public function get_call_status($call_sid) {
|
||||
$call_result = $this->get_call($call_sid);
|
||||
|
||||
if ($call_result['success']) {
|
||||
return $call_result['data']['status'];
|
||||
}
|
||||
|
||||
throw new Exception('Could not retrieve call status: ' . ($call_result['error'] ?? 'Unknown error'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create TwiML for queue
|
||||
*/
|
||||
@@ -220,12 +233,14 @@ class TWP_Twilio_API {
|
||||
$response->say($wait_message, ['voice' => 'alice']);
|
||||
}
|
||||
|
||||
$enqueue = $response->enqueue($queue_name);
|
||||
|
||||
// Pass waitUrl as an option to enqueue if provided
|
||||
$enqueue_options = [];
|
||||
if ($wait_url) {
|
||||
$enqueue->waitUrl($wait_url);
|
||||
$enqueue_options['waitUrl'] = $wait_url;
|
||||
}
|
||||
|
||||
$response->enqueue($queue_name, $enqueue_options);
|
||||
|
||||
return $response->asXML();
|
||||
} catch (Exception $e) {
|
||||
error_log('TWP Plugin: Failed to create queue TwiML: ' . $e->getMessage());
|
||||
@@ -682,7 +697,8 @@ class TWP_Twilio_API {
|
||||
if (!$client_name) {
|
||||
$current_user = wp_get_current_user();
|
||||
// Twilio requires alphanumeric characters only - remove all non-alphanumeric
|
||||
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $current_user->display_name);
|
||||
// Use user_login for consistency across all client name generation
|
||||
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $current_user->user_login);
|
||||
if (empty($clean_name)) {
|
||||
$clean_name = 'user';
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ class TWP_User_Queue_Manager {
|
||||
'user_id' => $user_id,
|
||||
'extension' => $extension,
|
||||
'max_size' => 10,
|
||||
'timeout_seconds' => 300, // 5 minutes for logged-in users
|
||||
'timeout_seconds' => 120, // 2 minutes timeout
|
||||
'voicemail_prompt' => sprintf('You have reached %s. Please leave a message after the tone.', $user->display_name),
|
||||
'is_hold_queue' => 0
|
||||
),
|
||||
@@ -288,23 +288,49 @@ class TWP_User_Queue_Manager {
|
||||
), ARRAY_A);
|
||||
|
||||
if (!$current_queue) {
|
||||
return array('success' => false, 'error' => 'Call not found in queue');
|
||||
}
|
||||
|
||||
// Move call to hold queue
|
||||
$result = $wpdb->update(
|
||||
$wpdb->prefix . 'twp_queued_calls',
|
||||
array(
|
||||
// Call not in queue yet (browser phone calls), create a new entry
|
||||
error_log("TWP: Call not in queue, creating hold queue entry for SID: {$call_sid}");
|
||||
|
||||
// Check if enqueued_at column exists
|
||||
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
||||
$columns = $wpdb->get_col("DESCRIBE $calls_table");
|
||||
|
||||
$insert_data = array(
|
||||
'queue_id' => $extension_data['hold_queue_id'],
|
||||
'position' => 1 // Reset position in hold queue
|
||||
),
|
||||
array('id' => $current_queue['id']),
|
||||
array('%d', '%d'),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
return array('success' => false, 'error' => 'Failed to transfer to hold queue');
|
||||
'call_sid' => $call_sid,
|
||||
'from_number' => '', // Will be populated by Twilio webhooks
|
||||
'to_number' => '', // Will be populated by Twilio webhooks
|
||||
'position' => 1,
|
||||
'status' => 'waiting'
|
||||
);
|
||||
|
||||
if (in_array('enqueued_at', $columns)) {
|
||||
$insert_data['enqueued_at'] = current_time('mysql');
|
||||
} else {
|
||||
$insert_data['joined_at'] = current_time('mysql');
|
||||
}
|
||||
|
||||
$result = $wpdb->insert($calls_table, $insert_data);
|
||||
|
||||
if ($result === false) {
|
||||
return array('success' => false, 'error' => 'Failed to create hold queue entry');
|
||||
}
|
||||
} else {
|
||||
// Move existing call to hold queue
|
||||
$result = $wpdb->update(
|
||||
$wpdb->prefix . 'twp_queued_calls',
|
||||
array(
|
||||
'queue_id' => $extension_data['hold_queue_id'],
|
||||
'position' => 1 // Reset position in hold queue
|
||||
),
|
||||
array('id' => $current_queue['id']),
|
||||
array('%d', '%d'),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
return array('success' => false, 'error' => 'Failed to transfer to hold queue');
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
@@ -340,7 +366,9 @@ class TWP_User_Queue_Manager {
|
||||
), ARRAY_A);
|
||||
|
||||
if (!$held_call) {
|
||||
return array('success' => false, 'error' => 'Call not found in hold queue');
|
||||
// Call might not be in database (browser phone), but we can still resume it
|
||||
error_log("TWP: Call not found in hold queue database, will resume anyway for SID: {$call_sid}");
|
||||
// Continue with resume process even without database entry
|
||||
}
|
||||
|
||||
// Determine target queue
|
||||
@@ -356,20 +384,22 @@ class TWP_User_Queue_Manager {
|
||||
$target_queue_id
|
||||
));
|
||||
|
||||
// Move call to target queue
|
||||
$result = $wpdb->update(
|
||||
$wpdb->prefix . 'twp_queued_calls',
|
||||
array(
|
||||
'queue_id' => $target_queue_id,
|
||||
'position' => $next_position
|
||||
),
|
||||
array('id' => $held_call['id']),
|
||||
array('%d', '%d'),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
return array('success' => false, 'error' => 'Failed to resume from hold');
|
||||
// Move call to target queue if it exists in database
|
||||
if ($held_call) {
|
||||
$result = $wpdb->update(
|
||||
$wpdb->prefix . 'twp_queued_calls',
|
||||
array(
|
||||
'queue_id' => $target_queue_id,
|
||||
'position' => $next_position
|
||||
),
|
||||
array('id' => $held_call['id']),
|
||||
array('%d', '%d'),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
return array('success' => false, 'error' => 'Failed to resume from hold');
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
|
@@ -79,6 +79,13 @@ class TWP_Webhooks {
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Extension voicemail webhook (when extension transfer times out)
|
||||
register_rest_route('twilio-webhook/v1', '/extension-voicemail', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'handle_extension_voicemail'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Agent call status webhook (detect voicemail/no-answer)
|
||||
register_rest_route('twilio-webhook/v1', '/agent-call-status', array(
|
||||
'methods' => 'POST',
|
||||
@@ -272,13 +279,32 @@ class TWP_Webhooks {
|
||||
'CallStatus' => isset($params['CallStatus']) ? $params['CallStatus'] : ''
|
||||
);
|
||||
|
||||
// Log the browser call
|
||||
// Log the browser call with agent information
|
||||
$agent_id = null;
|
||||
$agent_name = '';
|
||||
|
||||
// Extract agent from client identifier (client:agentname format)
|
||||
if (isset($call_data['From']) && strpos($call_data['From'], 'client:') === 0) {
|
||||
$agent_username = substr($call_data['From'], 7); // Remove 'client:' prefix
|
||||
$agent_user = get_user_by('login', $agent_username);
|
||||
if ($agent_user) {
|
||||
$agent_id = $agent_user->ID;
|
||||
$agent_name = $agent_user->display_name;
|
||||
}
|
||||
}
|
||||
|
||||
TWP_Call_Logger::log_call(array(
|
||||
'call_sid' => $call_data['CallSid'],
|
||||
'from_number' => $call_data['From'],
|
||||
'to_number' => $call_data['To'],
|
||||
'status' => 'browser_call_initiated',
|
||||
'actions_taken' => 'Browser phone call initiated'
|
||||
'workflow_name' => 'Browser Phone Call',
|
||||
'actions_taken' => json_encode(array(
|
||||
'agent_id' => $agent_id,
|
||||
'agent_name' => $agent_name,
|
||||
'type' => 'browser_phone_outbound',
|
||||
'from_client' => $call_data['From']
|
||||
))
|
||||
));
|
||||
|
||||
// For outbound calls from browser, handle caller ID properly
|
||||
@@ -1138,6 +1164,61 @@ class TWP_Webhooks {
|
||||
return $this->send_twiml_response($twiml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle extension voicemail when transfer times out or agent doesn't answer
|
||||
*/
|
||||
public function handle_extension_voicemail($request) {
|
||||
$params = $request->get_params();
|
||||
$user_id = isset($params['user_id']) ? intval($params['user_id']) : 0;
|
||||
$extension = isset($params['extension']) ? sanitize_text_field($params['extension']) : '';
|
||||
$dial_status = isset($params['DialCallStatus']) ? $params['DialCallStatus'] : '';
|
||||
|
||||
error_log("TWP Extension Voicemail: user_id=$user_id, extension=$extension, dial_status=$dial_status");
|
||||
|
||||
// Check if the call was answered
|
||||
if ($dial_status === 'completed' || $dial_status === 'answered') {
|
||||
// Call was answered, just hang up
|
||||
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
|
||||
$twiml .= '<Response><Hangup/></Response>';
|
||||
return $this->send_twiml_response($twiml);
|
||||
}
|
||||
|
||||
// Call was not answered - send to voicemail
|
||||
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||||
|
||||
// Get user details for voicemail prompt
|
||||
$user = get_user_by('id', $user_id);
|
||||
$display_name = $user ? $user->display_name : "Extension $extension";
|
||||
|
||||
// Get voicemail prompt from personal queue if available
|
||||
global $wpdb;
|
||||
$personal_queue = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT voicemail_prompt FROM {$wpdb->prefix}twp_call_queues
|
||||
WHERE user_id = %d AND queue_type = 'personal'",
|
||||
$user_id
|
||||
));
|
||||
|
||||
$voicemail_prompt = $personal_queue && $personal_queue->voicemail_prompt
|
||||
? $personal_queue->voicemail_prompt
|
||||
: sprintf('%s is not available. Please leave a message after the tone.', $display_name);
|
||||
|
||||
// Use TTS helper for ElevenLabs support
|
||||
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
|
||||
$tts_helper = TWP_TTS_Helper::get_instance();
|
||||
$tts_helper->add_tts_to_twiml($twiml, $voicemail_prompt);
|
||||
|
||||
// Record voicemail with proper callback to save to database
|
||||
$twiml->record([
|
||||
'action' => home_url('/wp-json/twilio-webhook/v1/voicemail-callback?user_id=' . $user_id),
|
||||
'maxLength' => 120, // 2 minutes max
|
||||
'playBeep' => true,
|
||||
'transcribe' => true,
|
||||
'transcribeCallback' => home_url('/wp-json/twilio-webhook/v1/transcription?user_id=' . $user_id)
|
||||
]);
|
||||
|
||||
return $this->send_twiml_response($twiml->asXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy voicemail audio through WordPress
|
||||
*/
|
||||
@@ -1296,6 +1377,7 @@ class TWP_Webhooks {
|
||||
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
||||
$from = isset($params['From']) ? $params['From'] : '';
|
||||
$workflow_id = isset($params['workflow_id']) ? intval($params['workflow_id']) : 0;
|
||||
$user_id = isset($params['user_id']) ? intval($params['user_id']) : 0; // For extension voicemails
|
||||
|
||||
// Enhanced customer number detection for voicemails
|
||||
$customer_number = $from;
|
||||
@@ -1388,22 +1470,32 @@ class TWP_Webhooks {
|
||||
error_log('TWP Voicemail Callback: recording_url=' . $recording_url . ', from=' . $from . ', workflow_id=' . $workflow_id . ', call_sid=' . $call_sid);
|
||||
|
||||
if ($recording_url) {
|
||||
// Ensure database schema is up to date for extension voicemails
|
||||
TWP_Activator::force_table_updates();
|
||||
|
||||
// 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')
|
||||
$insert_data = array(
|
||||
'workflow_id' => $workflow_id,
|
||||
'user_id' => $user_id, // For extension voicemails
|
||||
'from_number' => $from,
|
||||
'recording_url' => $recording_url,
|
||||
'duration' => $recording_duration,
|
||||
'created_at' => current_time('mysql')
|
||||
);
|
||||
|
||||
$format = array('%d', '%d', '%s', '%s', '%d', '%s');
|
||||
|
||||
// If user_id is 0 (not an extension voicemail), set to NULL
|
||||
if ($user_id === 0) {
|
||||
$insert_data['user_id'] = null;
|
||||
$format[1] = 'NULL';
|
||||
}
|
||||
|
||||
$voicemail_id = $wpdb->insert($table_name, $insert_data, $format);
|
||||
|
||||
// Log voicemail action
|
||||
if ($call_sid) {
|
||||
TWP_Call_Logger::log_action($call_sid, 'Voicemail recorded (' . $recording_duration . 's)');
|
||||
@@ -2171,7 +2263,7 @@ class TWP_Webhooks {
|
||||
);
|
||||
|
||||
// Set agent to busy
|
||||
TWP_Agent_Manager::set_agent_status($user_id, 'busy', $call_result['call_sid']);
|
||||
TWP_Agent_Manager::set_agent_status($user_id, 'busy', $call_result['call_sid'], true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
Reference in New Issue
Block a user