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:
@@ -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