prefix . 'twp_callbacks'; $result = $wpdb->insert( $table_name, array( 'phone_number' => sanitize_text_field($phone_number), 'queue_id' => $queue_id ? intval($queue_id) : null, 'original_call_sid' => $call_sid, 'status' => 'pending' ), array('%s', '%d', '%s', '%s') ); if ($result !== false) { // Send confirmation SMS if configured $sms_number = get_option('twp_sms_notification_number'); if ($sms_number) { $message = "Callback requested for " . $phone_number . ". We'll call you back shortly."; self::send_sms($phone_number, $message); } return $wpdb->insert_id; } return false; } /** * Process pending callbacks */ public static function process_callbacks() { global $wpdb; $table_name = $wpdb->prefix . 'twp_callbacks'; // Get pending callbacks older than 2 minutes (to avoid immediate callback) $callbacks = $wpdb->get_results(" SELECT * FROM $table_name WHERE status = 'pending' AND requested_at <= DATE_SUB(NOW(), INTERVAL 2 MINUTE) AND attempts < 3 ORDER BY requested_at ASC LIMIT 10 "); foreach ($callbacks as $callback) { self::initiate_callback($callback); } } /** * Initiate a callback */ private static function initiate_callback($callback) { global $wpdb; $table_name = $wpdb->prefix . 'twp_callbacks'; // Find an available agent $available_agent = TWP_Agent_Manager::get_available_agents(); if (empty($available_agent)) { // No agents available, try again later $wpdb->update( $table_name, array('last_attempt' => current_time('mysql')), array('id' => $callback->id), array('%s'), array('%d') ); return false; } $agent = $available_agent[0]; // Get first available agent // Create a conference call $twilio = new TWP_Twilio_API(); // First call the agent $agent_call_result = $twilio->make_call( $agent->phone_number, home_url('/wp-json/twilio-webhook/v1/callback-agent'), array( 'callback_id' => $callback->id, 'customer_number' => $callback->phone_number ) ); if ($agent_call_result['success']) { // Update callback status $wpdb->update( $table_name, array( 'status' => 'calling', 'attempts' => $callback->attempts + 1, 'last_attempt' => current_time('mysql'), 'callback_call_sid' => $agent_call_result['call_sid'] ), array('id' => $callback->id), array('%s', '%d', '%s', '%s'), array('%d') ); // Set agent to busy TWP_Agent_Manager::set_agent_status($agent->user_id, 'busy', $agent_call_result['call_sid']); return true; } return false; } /** * Handle callback agent answered */ public static function handle_agent_answered($callback_id, $agent_call_sid) { global $wpdb; $callbacks_table = $wpdb->prefix . 'twp_callbacks'; $callback = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $callbacks_table WHERE id = %d", $callback_id )); if (!$callback) { return false; } // Now call the customer and conference them in $twilio = new TWP_Twilio_API(); $customer_call_result = $twilio->make_call( $callback->phone_number, home_url('/wp-json/twilio-webhook/v1/callback-customer'), array( 'agent_call_sid' => $agent_call_sid, 'callback_id' => $callback_id ) ); if ($customer_call_result['success']) { // Update callback status $wpdb->update( $callbacks_table, array('status' => 'connecting'), array('id' => $callback_id), array('%s'), array('%d') ); return true; } return false; } /** * Complete callback */ public static function complete_callback($callback_id) { global $wpdb; $table_name = $wpdb->prefix . 'twp_callbacks'; $wpdb->update( $table_name, array( 'status' => 'completed', 'completed_at' => current_time('mysql') ), array('id' => $callback_id), array('%s', '%s'), array('%d') ); } /** * Initiate outbound call (click-to-call) */ public static function initiate_outbound_call($to_number, $agent_user_id) { $agent_phone = get_user_meta($agent_user_id, 'twp_phone_number', true); if (!$agent_phone) { return array('success' => false, 'error' => 'No phone number configured'); } $twilio = new TWP_Twilio_API(); // First call the agent $agent_call_result = $twilio->make_call( $agent_phone, home_url('/wp-json/twilio-webhook/v1/outbound-agent'), array( 'target_number' => $to_number, 'agent_user_id' => $agent_user_id ) ); if ($agent_call_result['success']) { // Set agent to busy TWP_Agent_Manager::set_agent_status($agent_user_id, 'busy', $agent_call_result['call_sid']); // Log the outbound call TWP_Call_Logger::log_call(array( 'call_sid' => $agent_call_result['call_sid'], 'from_number' => $agent_phone, 'to_number' => $to_number, 'status' => 'outbound_initiated', 'workflow_name' => 'Outbound Call', 'actions_taken' => json_encode(array( 'agent_id' => $agent_user_id, 'agent_name' => get_userdata($agent_user_id)->display_name, 'type' => 'click_to_call' )) )); return array('success' => true, 'call_sid' => $agent_call_result['call_sid']); } return array('success' => false, 'error' => $agent_call_result['error']); } /** * Handle outbound agent answered */ public static function handle_outbound_agent_answered($target_number, $agent_call_sid) { $twilio = new TWP_Twilio_API(); // Create TwiML to call the target number $twiml = new \Twilio\TwiML\VoiceResponse(); $twiml->say('Connecting your call...', ['voice' => 'alice']); $dial = $twiml->dial([ 'callerId' => get_option('twp_caller_id_number', ''), // Use configured caller ID 'timeout' => 30 ]); $dial->number($target_number); // If no answer, leave a message $twiml->say('The number you called is not available. Please try again later.', ['voice' => 'alice']); return $twiml->asXML(); } /** * Create callback option TwiML for queue */ public static function create_callback_twiml($queue_id, $caller_number) { $twiml = new \Twilio\TwiML\VoiceResponse(); $gather = $twiml->gather([ 'numDigits' => 1, 'timeout' => 10, 'action' => home_url('/wp-json/twilio-webhook/v1/callback-choice'), 'method' => 'POST' ]); $gather->say( 'You are currently in the queue. Press 1 to wait on the line, or press 2 to request a callback.', ['voice' => 'alice'] ); // Default to callback if no input $twiml->say('No input received. Requesting callback for you.', ['voice' => 'alice']); $twiml->redirect(home_url('/wp-json/twilio-webhook/v1/request-callback?' . http_build_query([ 'queue_id' => $queue_id, 'phone_number' => $caller_number ]))); return $twiml->asXML(); } /** * Send SMS notification */ private static function send_sms($to_number, $message) { $twilio = new TWP_Twilio_API(); return $twilio->send_sms($to_number, $message); } /** * Get callback statistics */ public static function get_callback_stats($days = 7) { global $wpdb; $table_name = $wpdb->prefix . 'twp_callbacks'; $since_date = date('Y-m-d H:i:s', strtotime("-$days days")); $stats = array( 'total_requests' => $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE requested_at >= %s", $since_date )), 'completed' => $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE requested_at >= %s AND status = 'completed'", $since_date )), 'pending' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'pending'"), 'avg_completion_time' => $wpdb->get_var($wpdb->prepare( "SELECT AVG(TIMESTAMPDIFF(MINUTE, requested_at, completed_at)) FROM $table_name WHERE requested_at >= %s AND status = 'completed'", $since_date )) ); $stats['success_rate'] = $stats['total_requests'] > 0 ? round(($stats['completed'] / $stats['total_requests']) * 100, 1) : 0; return $stats; } /** * Get pending callbacks for admin */ public static function get_pending_callbacks() { global $wpdb; $table_name = $wpdb->prefix . 'twp_callbacks'; $queues_table = $wpdb->prefix . 'twp_call_queues'; return $wpdb->get_results(" SELECT c.*, q.queue_name, TIMESTAMPDIFF(MINUTE, c.requested_at, NOW()) as wait_minutes FROM $table_name c LEFT JOIN $queues_table q ON c.queue_id = q.id WHERE c.status IN ('pending', 'calling', 'connecting') ORDER BY c.requested_at ASC "); } }