Add comprehensive call control features and web phone transfer capabilities
## New Call Control Features - Call hold/unhold with music playback - Call transfer with agent selection dialog - Call requeue to different queues - Call recording with start/stop controls - Real-time recording status tracking ## Enhanced Transfer System - Transfer to agents with cell phones (direct) - Transfer to web phone agents via personal queues - Automatic queue creation for each user - Real-time agent availability status - Visual agent selection with status indicators (📱 phone, 💻 web) ## Call Recordings Management - New database table for call recordings - Recordings tab in voicemail interface - Play/download recordings functionality - Admin-only delete capability - Integration with Twilio recording webhooks ## Agent Queue System - Personal queues (agent_[user_id]) for web phone transfers - Automatic polling for incoming transfers - Transfer notifications with browser alerts - Agent status tracking (available/busy/offline) ## Technical Enhancements - 8 new AJAX endpoints for call controls - Recording status webhooks - Enhanced transfer dialogs with agent selection - Improved error handling and user feedback - Mobile-responsive call control interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,8 @@ class TWP_Activator {
|
||||
'twp_agent_groups',
|
||||
'twp_group_members',
|
||||
'twp_agent_status',
|
||||
'twp_callbacks'
|
||||
'twp_callbacks',
|
||||
'twp_call_recordings'
|
||||
);
|
||||
|
||||
$missing_tables = array();
|
||||
@@ -284,6 +285,30 @@ class TWP_Activator {
|
||||
KEY queue_id (queue_id)
|
||||
) $charset_collate;";
|
||||
|
||||
// Call recordings table
|
||||
$table_recordings = $wpdb->prefix . 'twp_call_recordings';
|
||||
$sql_recordings = "CREATE TABLE $table_recordings (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
call_sid varchar(100) NOT NULL,
|
||||
recording_sid varchar(100),
|
||||
recording_url varchar(500),
|
||||
duration int(11) DEFAULT 0,
|
||||
from_number varchar(20),
|
||||
to_number varchar(20),
|
||||
agent_id bigint(20),
|
||||
status varchar(20) DEFAULT 'recording',
|
||||
started_at datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
ended_at datetime,
|
||||
file_size int(11),
|
||||
transcription text,
|
||||
notes text,
|
||||
PRIMARY KEY (id),
|
||||
KEY call_sid (call_sid),
|
||||
KEY recording_sid (recording_sid),
|
||||
KEY agent_id (agent_id),
|
||||
KEY started_at (started_at)
|
||||
) $charset_collate;";
|
||||
|
||||
dbDelta($sql_schedules);
|
||||
dbDelta($sql_queues);
|
||||
dbDelta($sql_queued_calls);
|
||||
@@ -296,6 +321,7 @@ class TWP_Activator {
|
||||
dbDelta($sql_group_members);
|
||||
dbDelta($sql_agent_status);
|
||||
dbDelta($sql_callbacks);
|
||||
dbDelta($sql_recordings);
|
||||
|
||||
// Add missing columns for existing installations
|
||||
self::add_missing_columns();
|
||||
|
@@ -201,6 +201,19 @@ class TWP_Core {
|
||||
$this->loader->add_action('wp_ajax_twp_get_conversation', $plugin_admin, 'ajax_get_conversation');
|
||||
$this->loader->add_action('wp_ajax_twp_send_sms_reply', $plugin_admin, 'ajax_send_sms_reply');
|
||||
|
||||
// Call control actions
|
||||
$this->loader->add_action('wp_ajax_twp_toggle_hold', $plugin_admin, 'ajax_toggle_hold');
|
||||
$this->loader->add_action('wp_ajax_twp_transfer_call', $plugin_admin, 'ajax_transfer_call');
|
||||
$this->loader->add_action('wp_ajax_twp_requeue_call', $plugin_admin, 'ajax_requeue_call');
|
||||
$this->loader->add_action('wp_ajax_twp_start_recording', $plugin_admin, 'ajax_start_recording');
|
||||
$this->loader->add_action('wp_ajax_twp_stop_recording', $plugin_admin, 'ajax_stop_recording');
|
||||
$this->loader->add_action('wp_ajax_twp_get_call_recordings', $plugin_admin, 'ajax_get_call_recordings');
|
||||
$this->loader->add_action('wp_ajax_twp_delete_recording', $plugin_admin, 'ajax_delete_recording');
|
||||
$this->loader->add_action('wp_ajax_twp_get_online_agents', $plugin_admin, 'ajax_get_online_agents');
|
||||
$this->loader->add_action('wp_ajax_twp_transfer_to_agent_queue', $plugin_admin, 'ajax_transfer_to_agent_queue');
|
||||
$this->loader->add_action('wp_ajax_twp_check_personal_queue', $plugin_admin, 'ajax_check_personal_queue');
|
||||
$this->loader->add_action('wp_ajax_twp_accept_transfer_call', $plugin_admin, 'ajax_accept_transfer_call');
|
||||
|
||||
// Frontend browser phone AJAX handlers are already covered by the admin handlers above
|
||||
// since they check permissions internally
|
||||
}
|
||||
|
@@ -171,6 +171,24 @@ class TWP_Shortcodes {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Call Control Panel (shown during active calls) -->
|
||||
<div class="twp-call-controls-panel" id="twp-call-controls-panel" style="display: none;">
|
||||
<div class="call-control-buttons">
|
||||
<button id="twp-hold-btn" class="twp-btn twp-btn-control" title="Put call on hold">
|
||||
Hold
|
||||
</button>
|
||||
<button id="twp-transfer-btn" class="twp-btn twp-btn-control" title="Transfer to another agent">
|
||||
Transfer
|
||||
</button>
|
||||
<button id="twp-requeue-btn" class="twp-btn twp-btn-control" title="Put call back in queue">
|
||||
Requeue
|
||||
</button>
|
||||
<button id="twp-record-btn" class="twp-btn twp-btn-control" title="Start/stop recording">
|
||||
Record
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Queue Management Section -->
|
||||
<div class="twp-queue-section" id="twp-queue-section">
|
||||
<h4>Your Queues</h4>
|
||||
|
@@ -100,6 +100,20 @@ class TWP_Webhooks {
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Recording status webhook
|
||||
register_rest_route('twilio-webhook/v1', '/recording-status', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'handle_recording_status'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Resume call webhook (for unhold)
|
||||
register_rest_route('twilio-webhook/v1', '/resume-call', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'handle_resume_call'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Smart routing webhook (checks user preference)
|
||||
register_rest_route('twilio-webhook/v1', '/smart-routing', array(
|
||||
'methods' => 'POST',
|
||||
@@ -2251,4 +2265,78 @@ class TWP_Webhooks {
|
||||
// Optionally: Try to assign to another available agent
|
||||
// $this->try_assign_to_next_agent($queued_call->queue_id, $queued_call_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle recording status callback
|
||||
*/
|
||||
public function handle_recording_status($request) {
|
||||
$params = $request->get_params();
|
||||
|
||||
error_log('TWP Recording Status: ' . print_r($params, true));
|
||||
|
||||
$recording_sid = isset($params['RecordingSid']) ? $params['RecordingSid'] : '';
|
||||
$recording_url = isset($params['RecordingUrl']) ? $params['RecordingUrl'] : '';
|
||||
$recording_status = isset($params['RecordingStatus']) ? $params['RecordingStatus'] : '';
|
||||
$recording_duration = isset($params['RecordingDuration']) ? intval($params['RecordingDuration']) : 0;
|
||||
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
||||
|
||||
if ($recording_sid && $recording_status === 'completed') {
|
||||
global $wpdb;
|
||||
$recordings_table = $wpdb->prefix . 'twp_call_recordings';
|
||||
|
||||
// Update recording with URL and duration
|
||||
$wpdb->update(
|
||||
$recordings_table,
|
||||
[
|
||||
'recording_url' => $recording_url,
|
||||
'duration' => $recording_duration,
|
||||
'status' => 'completed',
|
||||
'ended_at' => current_time('mysql')
|
||||
],
|
||||
['recording_sid' => $recording_sid]
|
||||
);
|
||||
|
||||
error_log("TWP: Recording completed - SID: $recording_sid, Duration: $recording_duration seconds");
|
||||
}
|
||||
|
||||
// Return empty response
|
||||
$response = new \Twilio\TwiML\VoiceResponse();
|
||||
return new WP_REST_Response($response->asXML(), 200, array('Content-Type' => 'text/xml'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resume call (unhold)
|
||||
*/
|
||||
public function handle_resume_call($request) {
|
||||
$params = $request->get_params();
|
||||
|
||||
error_log('TWP Resume Call: ' . print_r($params, true));
|
||||
|
||||
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
||||
|
||||
// Return empty TwiML to continue the call
|
||||
$response = new \Twilio\TwiML\VoiceResponse();
|
||||
|
||||
// Check if this is a conference call
|
||||
global $wpdb;
|
||||
$call_log_table = $wpdb->prefix . 'twp_call_log';
|
||||
$call_info = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM $call_log_table WHERE call_sid = %s",
|
||||
$call_sid
|
||||
));
|
||||
|
||||
if ($call_info && strpos($call_info->call_type, 'conference') !== false) {
|
||||
// Rejoin conference
|
||||
$dial = $response->dial();
|
||||
$dial->conference('Room_' . $call_sid, [
|
||||
'startConferenceOnEnter' => true,
|
||||
'endConferenceOnExit' => true
|
||||
]);
|
||||
} else {
|
||||
// Just continue the call
|
||||
$response->say('Call resumed');
|
||||
}
|
||||
|
||||
return new WP_REST_Response($response->asXML(), 200, array('Content-Type' => 'text/xml'));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user