685 lines
24 KiB
PHP
685 lines
24 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Mobile App REST API Endpoints
|
||
|
|
*
|
||
|
|
* Provides REST API endpoints for mobile app functionality
|
||
|
|
*/
|
||
|
|
class TWP_Mobile_API {
|
||
|
|
|
||
|
|
private $auth;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructor
|
||
|
|
*/
|
||
|
|
public function __construct() {
|
||
|
|
// Initialize auth handler
|
||
|
|
require_once plugin_dir_path(__FILE__) . 'class-twp-mobile-auth.php';
|
||
|
|
$this->auth = new TWP_Mobile_Auth();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register REST API endpoints
|
||
|
|
*/
|
||
|
|
public function register_endpoints() {
|
||
|
|
add_action('rest_api_init', function() {
|
||
|
|
// Agent status endpoints
|
||
|
|
register_rest_route('twilio-mobile/v1', '/agent/status', array(
|
||
|
|
'methods' => 'GET',
|
||
|
|
'callback' => array($this, 'get_agent_status'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
register_rest_route('twilio-mobile/v1', '/agent/status', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'update_agent_status'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
// Queue state endpoint
|
||
|
|
register_rest_route('twilio-mobile/v1', '/queues/state', array(
|
||
|
|
'methods' => 'GET',
|
||
|
|
'callback' => array($this, 'get_queue_state'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
// Queue calls (specific queue)
|
||
|
|
register_rest_route('twilio-mobile/v1', '/queues/(?P<id>\d+)/calls', array(
|
||
|
|
'methods' => 'GET',
|
||
|
|
'callback' => array($this, 'get_queue_calls'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
// Call control endpoints
|
||
|
|
register_rest_route('twilio-mobile/v1', '/calls/(?P<call_sid>[^/]+)/accept', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'accept_call'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
register_rest_route('twilio-mobile/v1', '/calls/(?P<call_sid>[^/]+)/reject', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'reject_call'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
register_rest_route('twilio-mobile/v1', '/calls/(?P<call_sid>[^/]+)/hold', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'hold_call'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
register_rest_route('twilio-mobile/v1', '/calls/(?P<call_sid>[^/]+)/unhold', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'unhold_call'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
register_rest_route('twilio-mobile/v1', '/calls/(?P<call_sid>[^/]+)/transfer', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'transfer_call'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
// FCM token registration
|
||
|
|
register_rest_route('twilio-mobile/v1', '/fcm/register', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'register_fcm_token'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
// Agent phone number
|
||
|
|
register_rest_route('twilio-mobile/v1', '/agent/phone', array(
|
||
|
|
'methods' => 'GET',
|
||
|
|
'callback' => array($this, 'get_agent_phone'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
|
||
|
|
register_rest_route('twilio-mobile/v1', '/agent/phone', array(
|
||
|
|
'methods' => 'POST',
|
||
|
|
'callback' => array($this, 'update_agent_phone'),
|
||
|
|
'permission_callback' => array($this->auth, 'verify_token')
|
||
|
|
));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get agent status
|
||
|
|
*/
|
||
|
|
public function get_agent_status($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
|
||
|
|
global $wpdb;
|
||
|
|
$table = $wpdb->prefix . 'twp_agent_status';
|
||
|
|
|
||
|
|
$status = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT status, is_logged_in, current_call_sid, last_activity, available_for_queues FROM $table WHERE user_id = %d",
|
||
|
|
$user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
if (!$status) {
|
||
|
|
// Create default status
|
||
|
|
$wpdb->insert(
|
||
|
|
$table,
|
||
|
|
array('user_id' => $user_id, 'status' => 'offline', 'is_logged_in' => 0),
|
||
|
|
array('%d', '%s', '%d')
|
||
|
|
);
|
||
|
|
|
||
|
|
$status = (object) array(
|
||
|
|
'status' => 'offline',
|
||
|
|
'is_logged_in' => 0,
|
||
|
|
'current_call_sid' => null,
|
||
|
|
'last_activity' => current_time('mysql'),
|
||
|
|
'available_for_queues' => 1
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'status' => $status->status,
|
||
|
|
'is_logged_in' => (bool)$status->is_logged_in,
|
||
|
|
'current_call_sid' => $status->current_call_sid,
|
||
|
|
'last_activity' => $status->last_activity,
|
||
|
|
'available_for_queues' => (bool)$status->available_for_queues
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update agent status
|
||
|
|
*/
|
||
|
|
public function update_agent_status($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$new_status = $request->get_param('status');
|
||
|
|
$is_logged_in = $request->get_param('is_logged_in');
|
||
|
|
|
||
|
|
if (!in_array($new_status, array('available', 'busy', 'offline'))) {
|
||
|
|
return new WP_Error('invalid_status', 'Status must be available, busy, or offline', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
global $wpdb;
|
||
|
|
$table = $wpdb->prefix . 'twp_agent_status';
|
||
|
|
|
||
|
|
// Check if status exists
|
||
|
|
$exists = $wpdb->get_var($wpdb->prepare(
|
||
|
|
"SELECT COUNT(*) FROM $table WHERE user_id = %d",
|
||
|
|
$user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
$data = array(
|
||
|
|
'status' => $new_status,
|
||
|
|
'last_activity' => current_time('mysql')
|
||
|
|
);
|
||
|
|
|
||
|
|
if ($is_logged_in !== null) {
|
||
|
|
$data['is_logged_in'] = $is_logged_in ? 1 : 0;
|
||
|
|
if ($is_logged_in) {
|
||
|
|
$data['logged_in_at'] = current_time('mysql');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($exists) {
|
||
|
|
$wpdb->update(
|
||
|
|
$table,
|
||
|
|
$data,
|
||
|
|
array('user_id' => $user_id),
|
||
|
|
array('%s', '%s'),
|
||
|
|
array('%d')
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
$data['user_id'] = $user_id;
|
||
|
|
$wpdb->insert($table, $data);
|
||
|
|
}
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Status updated successfully'
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get queue state (all queues user has access to)
|
||
|
|
*/
|
||
|
|
public function get_queue_state($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
|
||
|
|
global $wpdb;
|
||
|
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
||
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
||
|
|
$assignments_table = $wpdb->prefix . 'twp_queue_assignments';
|
||
|
|
|
||
|
|
// Get queues assigned to this user
|
||
|
|
$queue_ids = $wpdb->get_col($wpdb->prepare(
|
||
|
|
"SELECT queue_id FROM $assignments_table WHERE user_id = %d",
|
||
|
|
$user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
// Also include personal queues
|
||
|
|
$personal_queue_ids = $wpdb->get_col($wpdb->prepare(
|
||
|
|
"SELECT id FROM $queues_table WHERE user_id = %d",
|
||
|
|
$user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
$all_queue_ids = array_unique(array_merge($queue_ids, $personal_queue_ids));
|
||
|
|
|
||
|
|
if (empty($all_queue_ids)) {
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'queues' => array()
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
$queue_ids_str = implode(',', array_map('intval', $all_queue_ids));
|
||
|
|
|
||
|
|
// Get queue information with call counts
|
||
|
|
$queues = $wpdb->get_results("
|
||
|
|
SELECT
|
||
|
|
q.id,
|
||
|
|
q.queue_name,
|
||
|
|
q.queue_type,
|
||
|
|
q.extension,
|
||
|
|
COUNT(c.id) as waiting_count
|
||
|
|
FROM $queues_table q
|
||
|
|
LEFT JOIN $calls_table c ON q.id = c.queue_id AND c.status = 'waiting'
|
||
|
|
WHERE q.id IN ($queue_ids_str)
|
||
|
|
GROUP BY q.id
|
||
|
|
");
|
||
|
|
|
||
|
|
$result = array();
|
||
|
|
foreach ($queues as $queue) {
|
||
|
|
$result[] = array(
|
||
|
|
'id' => (int)$queue->id,
|
||
|
|
'name' => $queue->queue_name,
|
||
|
|
'type' => $queue->queue_type,
|
||
|
|
'extension' => $queue->extension,
|
||
|
|
'waiting_count' => (int)$queue->waiting_count
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'queues' => $result
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get calls in a specific queue
|
||
|
|
*/
|
||
|
|
public function get_queue_calls($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$queue_id = (int)$request['id'];
|
||
|
|
|
||
|
|
// Verify user has access to this queue
|
||
|
|
if (!$this->user_has_queue_access($user_id, $queue_id)) {
|
||
|
|
return new WP_Error('forbidden', 'You do not have access to this queue', array('status' => 403));
|
||
|
|
}
|
||
|
|
|
||
|
|
global $wpdb;
|
||
|
|
$table = $wpdb->prefix . 'twp_queued_calls';
|
||
|
|
|
||
|
|
$calls = $wpdb->get_results($wpdb->prepare(
|
||
|
|
"SELECT call_sid, from_number, to_number, position, status, joined_at, enqueued_at
|
||
|
|
FROM $table
|
||
|
|
WHERE queue_id = %d AND status = 'waiting'
|
||
|
|
ORDER BY position ASC",
|
||
|
|
$queue_id
|
||
|
|
));
|
||
|
|
|
||
|
|
$result = array();
|
||
|
|
foreach ($calls as $call) {
|
||
|
|
$result[] = array(
|
||
|
|
'call_sid' => $call->call_sid,
|
||
|
|
'from_number' => $call->from_number,
|
||
|
|
'to_number' => $call->to_number,
|
||
|
|
'position' => (int)$call->position,
|
||
|
|
'status' => $call->status,
|
||
|
|
'wait_time' => $this->calculate_wait_time($call->enqueued_at ?: $call->joined_at)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'calls' => $result
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Accept a call (dequeue and connect to agent)
|
||
|
|
*/
|
||
|
|
public function accept_call($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$call_sid = $request['call_sid'];
|
||
|
|
|
||
|
|
// Get agent phone number
|
||
|
|
$agent_number = get_user_meta($user_id, 'twp_agent_phone', true);
|
||
|
|
|
||
|
|
if (empty($agent_number)) {
|
||
|
|
return new WP_Error('no_phone', 'No phone number configured for agent', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize Twilio API
|
||
|
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-twilio-api.php';
|
||
|
|
$twilio = new TWP_Twilio_API();
|
||
|
|
|
||
|
|
// Get call info from queue
|
||
|
|
global $wpdb;
|
||
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
||
|
|
|
||
|
|
$call = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT * FROM $calls_table WHERE call_sid = %s AND status = 'waiting'",
|
||
|
|
$call_sid
|
||
|
|
));
|
||
|
|
|
||
|
|
if (!$call) {
|
||
|
|
return new WP_Error('call_not_found', 'Call not found or no longer waiting', array('status' => 404));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify user has access to this queue
|
||
|
|
if (!$this->user_has_queue_access($user_id, $call->queue_id)) {
|
||
|
|
return new WP_Error('forbidden', 'You do not have access to this queue', array('status' => 403));
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Connect agent to call
|
||
|
|
$agent_call = $twilio->create_call(
|
||
|
|
$agent_number,
|
||
|
|
$call->to_number,
|
||
|
|
array(
|
||
|
|
'url' => site_url('/wp-json/twilio-webhook/v1/connect-agent'),
|
||
|
|
'statusCallback' => site_url('/wp-json/twilio-webhook/v1/agent-call-status'),
|
||
|
|
'statusCallbackEvent' => array('completed', 'no-answer', 'busy', 'failed'),
|
||
|
|
'timeout' => 30
|
||
|
|
)
|
||
|
|
);
|
||
|
|
|
||
|
|
// Update call record
|
||
|
|
$wpdb->update(
|
||
|
|
$calls_table,
|
||
|
|
array(
|
||
|
|
'status' => 'connecting',
|
||
|
|
'agent_phone' => $agent_number,
|
||
|
|
'agent_call_sid' => $agent_call->sid
|
||
|
|
),
|
||
|
|
array('call_sid' => $call_sid),
|
||
|
|
array('%s', '%s', '%s'),
|
||
|
|
array('%s')
|
||
|
|
);
|
||
|
|
|
||
|
|
// Update agent status
|
||
|
|
$status_table = $wpdb->prefix . 'twp_agent_status';
|
||
|
|
$wpdb->update(
|
||
|
|
$status_table,
|
||
|
|
array('status' => 'busy', 'current_call_sid' => $call_sid),
|
||
|
|
array('user_id' => $user_id),
|
||
|
|
array('%s', '%s'),
|
||
|
|
array('%d')
|
||
|
|
);
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Call accepted, connecting to agent',
|
||
|
|
'agent_call_sid' => $agent_call->sid
|
||
|
|
), 200);
|
||
|
|
|
||
|
|
} catch (Exception $e) {
|
||
|
|
return new WP_Error('twilio_error', $e->getMessage(), array('status' => 500));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reject a call (send to voicemail)
|
||
|
|
*/
|
||
|
|
public function reject_call($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$call_sid = $request['call_sid'];
|
||
|
|
|
||
|
|
global $wpdb;
|
||
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
||
|
|
|
||
|
|
$call = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT * FROM $calls_table WHERE call_sid = %s AND status = 'waiting'",
|
||
|
|
$call_sid
|
||
|
|
));
|
||
|
|
|
||
|
|
if (!$call) {
|
||
|
|
return new WP_Error('call_not_found', 'Call not found or no longer waiting', array('status' => 404));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify user has access to this queue
|
||
|
|
if (!$this->user_has_queue_access($user_id, $call->queue_id)) {
|
||
|
|
return new WP_Error('forbidden', 'You do not have access to this queue', array('status' => 403));
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Initialize Twilio API
|
||
|
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-twilio-api.php';
|
||
|
|
$twilio = new TWP_Twilio_API();
|
||
|
|
|
||
|
|
// Redirect call to voicemail
|
||
|
|
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||
|
|
$twiml->say('The agent is unavailable. Please leave a message after the tone.');
|
||
|
|
$twiml->record(array(
|
||
|
|
'action' => site_url('/wp-json/twilio-webhook/v1/voicemail-complete'),
|
||
|
|
'maxLength' => 120,
|
||
|
|
'transcribe' => true
|
||
|
|
));
|
||
|
|
$twiml->say('We did not receive a recording. Goodbye.');
|
||
|
|
|
||
|
|
$twilio->update_call($call_sid, array('twiml' => $twiml->asXML()));
|
||
|
|
|
||
|
|
// Update call status
|
||
|
|
$wpdb->update(
|
||
|
|
$calls_table,
|
||
|
|
array('status' => 'voicemail', 'ended_at' => current_time('mysql')),
|
||
|
|
array('call_sid' => $call_sid),
|
||
|
|
array('%s', '%s'),
|
||
|
|
array('%s')
|
||
|
|
);
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Call sent to voicemail'
|
||
|
|
), 200);
|
||
|
|
|
||
|
|
} catch (Exception $e) {
|
||
|
|
return new WP_Error('twilio_error', $e->getMessage(), array('status' => 500));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hold a call
|
||
|
|
*/
|
||
|
|
public function hold_call($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$call_sid = $request['call_sid'];
|
||
|
|
|
||
|
|
try {
|
||
|
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-admin.php';
|
||
|
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-twilio-api.php';
|
||
|
|
|
||
|
|
$admin = new TWP_Admin('twilio-wp-plugin', TWP_VERSION);
|
||
|
|
$twilio = new TWP_Twilio_API();
|
||
|
|
|
||
|
|
// Find customer call leg
|
||
|
|
$customer_call_sid = $admin->find_customer_call_leg($call_sid, $twilio);
|
||
|
|
|
||
|
|
if (!$customer_call_sid) {
|
||
|
|
return new WP_Error('call_not_found', 'Could not find customer call leg', array('status' => 404));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get user's hold queue
|
||
|
|
global $wpdb;
|
||
|
|
$ext_table = $wpdb->prefix . 'twp_user_extensions';
|
||
|
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
||
|
|
|
||
|
|
$extension = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT hold_queue_id FROM $ext_table WHERE user_id = %d",
|
||
|
|
$user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
if (!$extension || !$extension->hold_queue_id) {
|
||
|
|
return new WP_Error('no_hold_queue', 'No hold queue configured', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
$hold_queue = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT queue_name, wait_music_url FROM $queues_table WHERE id = %d",
|
||
|
|
$extension->hold_queue_id
|
||
|
|
));
|
||
|
|
|
||
|
|
// Put call on hold
|
||
|
|
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||
|
|
$twiml->say('Please hold while we transfer your call.');
|
||
|
|
$enqueue = $twiml->enqueue($hold_queue->queue_name, array(
|
||
|
|
'waitUrl' => $hold_queue->wait_music_url ?: site_url('/wp-json/twilio-webhook/v1/queue-wait')
|
||
|
|
));
|
||
|
|
|
||
|
|
$twilio->update_call($customer_call_sid, array('twiml' => $twiml->asXML()));
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Call placed on hold'
|
||
|
|
), 200);
|
||
|
|
|
||
|
|
} catch (Exception $e) {
|
||
|
|
return new WP_Error('hold_error', $e->getMessage(), array('status' => 500));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Unhold a call (resume from hold queue)
|
||
|
|
*/
|
||
|
|
public function unhold_call($request) {
|
||
|
|
// Implementation would retrieve from hold queue and reconnect
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Unhold functionality - to be implemented with queue retrieval'
|
||
|
|
), 501);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Transfer a call to another extension/queue
|
||
|
|
*/
|
||
|
|
public function transfer_call($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$call_sid = $request['call_sid'];
|
||
|
|
$target = $request->get_param('target'); // Extension number or queue ID
|
||
|
|
|
||
|
|
if (empty($target)) {
|
||
|
|
return new WP_Error('missing_target', 'Transfer target is required', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-admin.php';
|
||
|
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-twilio-api.php';
|
||
|
|
|
||
|
|
$admin = new TWP_Admin('twilio-wp-plugin', TWP_VERSION);
|
||
|
|
$twilio = new TWP_Twilio_API();
|
||
|
|
|
||
|
|
// Find customer call leg
|
||
|
|
$customer_call_sid = $admin->find_customer_call_leg($call_sid, $twilio);
|
||
|
|
|
||
|
|
if (!$customer_call_sid) {
|
||
|
|
return new WP_Error('call_not_found', 'Could not find customer call leg', array('status' => 404));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Look up target (extension or queue)
|
||
|
|
global $wpdb;
|
||
|
|
$ext_table = $wpdb->prefix . 'twp_user_extensions';
|
||
|
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
||
|
|
|
||
|
|
// Try as extension first
|
||
|
|
$target_queue = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT q.* FROM $queues_table q
|
||
|
|
JOIN $ext_table e ON q.id = e.personal_queue_id
|
||
|
|
WHERE e.extension = %s",
|
||
|
|
$target
|
||
|
|
));
|
||
|
|
|
||
|
|
// If not extension, try as queue ID
|
||
|
|
if (!$target_queue && is_numeric($target)) {
|
||
|
|
$target_queue = $wpdb->get_row($wpdb->prepare(
|
||
|
|
"SELECT * FROM $queues_table WHERE id = %d",
|
||
|
|
$target
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!$target_queue) {
|
||
|
|
return new WP_Error('invalid_target', 'Transfer target not found', array('status' => 404));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Transfer to queue
|
||
|
|
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||
|
|
$twiml->say('Transferring your call.');
|
||
|
|
$twiml->enqueue($target_queue->queue_name, array(
|
||
|
|
'waitUrl' => $target_queue->wait_music_url ?: site_url('/wp-json/twilio-webhook/v1/queue-wait')
|
||
|
|
));
|
||
|
|
|
||
|
|
$twilio->update_call($customer_call_sid, array('twiml' => $twiml->asXML()));
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Call transferred successfully'
|
||
|
|
), 200);
|
||
|
|
|
||
|
|
} catch (Exception $e) {
|
||
|
|
return new WP_Error('transfer_error', $e->getMessage(), array('status' => 500));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register FCM token for push notifications
|
||
|
|
*/
|
||
|
|
public function register_fcm_token($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$fcm_token = $request->get_param('fcm_token');
|
||
|
|
$refresh_token = $request->get_param('refresh_token');
|
||
|
|
|
||
|
|
if (empty($fcm_token)) {
|
||
|
|
return new WP_Error('missing_token', 'FCM token is required', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->auth->update_fcm_token($user_id, $refresh_token, $fcm_token);
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'FCM token registered successfully'
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get agent phone number
|
||
|
|
*/
|
||
|
|
public function get_agent_phone($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$agent_number = get_user_meta($user_id, 'twp_agent_phone', true);
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'phone_number' => $agent_number ?: null
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update agent phone number
|
||
|
|
*/
|
||
|
|
public function update_agent_phone($request) {
|
||
|
|
$user_id = $this->auth->get_current_user_id();
|
||
|
|
$phone_number = $request->get_param('phone_number');
|
||
|
|
|
||
|
|
if (empty($phone_number)) {
|
||
|
|
return new WP_Error('missing_phone', 'Phone number is required', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate E.164 format
|
||
|
|
if (!preg_match('/^\+[1-9]\d{1,14}$/', $phone_number)) {
|
||
|
|
return new WP_Error('invalid_phone', 'Phone number must be in E.164 format (+1XXXXXXXXXX)', array('status' => 400));
|
||
|
|
}
|
||
|
|
|
||
|
|
update_user_meta($user_id, 'twp_agent_phone', $phone_number);
|
||
|
|
|
||
|
|
return new WP_REST_Response(array(
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Phone number updated successfully'
|
||
|
|
), 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if user has access to a queue
|
||
|
|
*/
|
||
|
|
private function user_has_queue_access($user_id, $queue_id) {
|
||
|
|
global $wpdb;
|
||
|
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
||
|
|
$assignments_table = $wpdb->prefix . 'twp_queue_assignments';
|
||
|
|
|
||
|
|
// Check if it's user's personal queue
|
||
|
|
$is_personal = $wpdb->get_var($wpdb->prepare(
|
||
|
|
"SELECT COUNT(*) FROM $queues_table WHERE id = %d AND user_id = %d",
|
||
|
|
$queue_id, $user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
if ($is_personal) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if user is assigned to this queue
|
||
|
|
$is_assigned = $wpdb->get_var($wpdb->prepare(
|
||
|
|
"SELECT COUNT(*) FROM $assignments_table WHERE queue_id = %d AND user_id = %d",
|
||
|
|
$queue_id, $user_id
|
||
|
|
));
|
||
|
|
|
||
|
|
return (bool)$is_assigned;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate wait time in seconds
|
||
|
|
*/
|
||
|
|
private function calculate_wait_time($start_time) {
|
||
|
|
if (!$start_time) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$start = strtotime($start_time);
|
||
|
|
$now = current_time('timestamp');
|
||
|
|
|
||
|
|
return max(0, $now - $start);
|
||
|
|
}
|
||
|
|
}
|