Initiate outbound calls to connect customers with your phone. Click-to-call functionality allows you to dial any number.
Make an Outbound Call
Select the Twilio number to call from
Enter the number you want to call (include country code)
The number where you'll receive the call first
Recent Outbound Calls
Date/Time
From
To
Agent
Status
Duration
prefix . 'twp_call_log';
$recent_calls = $wpdb->get_results($wpdb->prepare("
SELECT cl.*, u.display_name as agent_name
FROM $log_table cl
LEFT JOIN {$wpdb->users} u ON JSON_EXTRACT(cl.actions_taken, '$.agent_id') = u.ID
WHERE cl.workflow_name = 'Outbound Call'
OR cl.status = 'outbound_initiated'
ORDER BY cl.created_at DESC
LIMIT 20
"));
if (empty($recent_calls)) {
echo '
prefix . 'twp_voicemails';
$workflows_table = $wpdb->prefix . 'twp_workflows';
$voicemails = $wpdb->get_results("
SELECT v.*, w.workflow_name
FROM $voicemails_table v
LEFT JOIN $workflows_table w ON v.workflow_id = w.id
ORDER BY v.created_at DESC
LIMIT 50
");
foreach ($voicemails as $voicemail) {
?>
';
}
}
/**
* Show admin notices
*/
public function show_admin_notices() {
// Check if we're on a plugin page
$screen = get_current_screen();
if (!$screen || strpos($screen->id, 'twilio-wp') === false) {
return;
}
// Check if database tables exist
require_once TWP_PLUGIN_DIR . 'includes/class-twp-activator.php';
$tables_exist = TWP_Activator::ensure_tables_exist();
if (!$tables_exist) {
?>
Twilio WP Plugin: Database tables were missing and have been created automatically.
If you continue to experience issues, please deactivate and reactivate the plugin.
Twilio WP Plugin: To use text-to-speech features, please configure your
ElevenLabs API key.
Twilio WP Plugin: Please configure your
Twilio credentials
to start using the plugin.
plugin_name,
TWP_PLUGIN_URL . 'assets/css/admin.css',
array('thickbox'),
$this->version,
'all'
);
}
/**
* Enqueue scripts
*/
public function enqueue_scripts() {
// Enqueue ThickBox for WordPress native modals
wp_enqueue_script('thickbox');
wp_enqueue_script(
$this->plugin_name,
TWP_PLUGIN_URL . 'assets/js/admin.js',
array('jquery', 'thickbox'),
$this->version,
false
);
wp_localize_script(
$this->plugin_name,
'twp_ajax',
array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('twp_ajax_nonce'),
'rest_url' => rest_url(),
'has_elevenlabs_key' => !empty(get_option('twp_elevenlabs_api_key')),
'timezone' => wp_timezone_string()
)
);
}
/**
* AJAX handler for saving schedule
*/
public function ajax_save_schedule() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Debug logging - log incoming POST data
error_log('TWP Schedule Save: POST data: ' . print_r($_POST, true));
$schedule_id = isset($_POST['schedule_id']) ? intval($_POST['schedule_id']) : 0;
// Remove duplicate days and sanitize
$days_of_week = isset($_POST['days_of_week']) ? $_POST['days_of_week'] : array();
$unique_days = array_unique(array_map('sanitize_text_field', $days_of_week));
$data = array(
'schedule_name' => sanitize_text_field($_POST['schedule_name']),
'days_of_week' => implode(',', $unique_days),
'start_time' => sanitize_text_field($_POST['start_time']),
'end_time' => sanitize_text_field($_POST['end_time']),
'workflow_id' => isset($_POST['workflow_id']) && !empty($_POST['workflow_id']) ? intval($_POST['workflow_id']) : null,
'holiday_dates' => isset($_POST['holiday_dates']) ? sanitize_textarea_field($_POST['holiday_dates']) : '',
'is_active' => isset($_POST['is_active']) ? 1 : 0
);
// Add optional fields if provided
if (!empty($_POST['phone_number'])) {
$data['phone_number'] = sanitize_text_field($_POST['phone_number']);
}
if (!empty($_POST['forward_number'])) {
$data['forward_number'] = sanitize_text_field($_POST['forward_number']);
}
if (!empty($_POST['after_hours_action'])) {
$data['after_hours_action'] = sanitize_text_field($_POST['after_hours_action']);
}
if (!empty($_POST['after_hours_workflow_id'])) {
$data['after_hours_workflow_id'] = intval($_POST['after_hours_workflow_id']);
}
if (!empty($_POST['after_hours_forward_number'])) {
$data['after_hours_forward_number'] = sanitize_text_field($_POST['after_hours_forward_number']);
}
// Debug logging - log processed data
error_log('TWP Schedule Save: Processed data: ' . print_r($data, true));
error_log('TWP Schedule Save: Schedule ID: ' . $schedule_id);
if ($schedule_id) {
error_log('TWP Schedule Save: Updating existing schedule');
$result = TWP_Scheduler::update_schedule($schedule_id, $data);
} else {
error_log('TWP Schedule Save: Creating new schedule');
$result = TWP_Scheduler::create_schedule($data);
}
error_log('TWP Schedule Save: Result: ' . ($result ? 'true' : 'false'));
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for deleting schedule
*/
public function ajax_delete_schedule() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$schedule_id = intval($_POST['schedule_id']);
$result = TWP_Scheduler::delete_schedule($schedule_id);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for getting all schedules
*/
public function ajax_get_schedules() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$schedules = TWP_Scheduler::get_schedules();
wp_send_json_success($schedules);
}
/**
* AJAX handler for getting a single schedule
*/
public function ajax_get_schedule() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$schedule_id = intval($_POST['schedule_id']);
$schedule = TWP_Scheduler::get_schedule($schedule_id);
if ($schedule) {
wp_send_json_success($schedule);
} else {
wp_send_json_error('Schedule not found');
}
}
/**
* AJAX handler for saving workflow
*/
public function ajax_save_workflow() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$workflow_id = isset($_POST['workflow_id']) ? intval($_POST['workflow_id']) : 0;
// Parse the workflow data JSON
$workflow_data_json = isset($_POST['workflow_data']) ? stripslashes($_POST['workflow_data']) : '{}';
// Log for debugging
error_log('TWP Workflow Save - Raw data: ' . $workflow_data_json);
// Handle empty workflow data
if (empty($workflow_data_json) || $workflow_data_json === '{}') {
$workflow_data_parsed = array(
'steps' => array(),
'conditions' => array(),
'actions' => array()
);
} else {
$workflow_data_parsed = json_decode($workflow_data_json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('TWP Workflow Save - JSON Error: ' . json_last_error_msg());
wp_send_json_error('Invalid workflow data format: ' . json_last_error_msg());
return;
}
}
$data = array(
'workflow_name' => sanitize_text_field($_POST['workflow_name']),
'phone_number' => sanitize_text_field($_POST['phone_number']),
'steps' => isset($workflow_data_parsed['steps']) ? $workflow_data_parsed['steps'] : array(),
'conditions' => isset($workflow_data_parsed['conditions']) ? $workflow_data_parsed['conditions'] : array(),
'actions' => isset($workflow_data_parsed['actions']) ? $workflow_data_parsed['actions'] : array(),
'is_active' => isset($_POST['is_active']) ? intval($_POST['is_active']) : 0,
'workflow_data' => $workflow_data_json // Keep the raw JSON for update_workflow
);
if ($workflow_id) {
$result = TWP_Workflow::update_workflow($workflow_id, $data);
} else {
$result = TWP_Workflow::create_workflow($data);
}
if ($result === false) {
wp_send_json_error('Failed to save workflow to database');
} else {
global $wpdb;
wp_send_json_success(array('success' => true, 'workflow_id' => $workflow_id ?: $wpdb->insert_id));
}
}
/**
* AJAX handler for getting workflow
*/
public function ajax_get_workflow() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$workflow_id = intval($_POST['workflow_id']);
$workflow = TWP_Workflow::get_workflow($workflow_id);
wp_send_json_success($workflow);
}
/**
* AJAX handler for deleting workflow
*/
public function ajax_delete_workflow() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$workflow_id = intval($_POST['workflow_id']);
$result = TWP_Workflow::delete_workflow($workflow_id);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for test call
*/
public function ajax_test_call() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$to_number = sanitize_text_field($_POST['to_number']);
$workflow_id = intval($_POST['workflow_id']);
$twilio = new TWP_Twilio_API();
$twiml_url = home_url('/wp-json/twilio-webhook/v1/voice');
$twiml_url = add_query_arg('workflow_id', $workflow_id, $twiml_url);
$result = $twilio->make_call($to_number, $twiml_url);
wp_send_json_success($result);
}
/**
* AJAX handler for getting phone numbers
*/
public function ajax_get_phone_numbers() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$twilio = new TWP_Twilio_API();
$result = $twilio->get_phone_numbers();
if ($result['success']) {
wp_send_json_success($result['data']['incoming_phone_numbers']);
} else {
wp_send_json_error($result['error']);
}
}
/**
* AJAX handler for searching available phone numbers
*/
public function ajax_search_available_numbers() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$country_code = sanitize_text_field($_POST['country_code']);
$area_code = sanitize_text_field($_POST['area_code']);
$contains = sanitize_text_field($_POST['contains']);
$twilio = new TWP_Twilio_API();
$result = $twilio->search_available_numbers($country_code, $area_code, $contains);
if ($result['success']) {
wp_send_json_success($result['data']['available_phone_numbers']);
} else {
wp_send_json_error($result['error']);
}
}
/**
* AJAX handler for purchasing a phone number
*/
public function ajax_purchase_number() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$phone_number = sanitize_text_field($_POST['phone_number']);
$voice_url = isset($_POST['voice_url']) ? esc_url_raw($_POST['voice_url']) : null;
$sms_url = isset($_POST['sms_url']) ? esc_url_raw($_POST['sms_url']) : null;
$twilio = new TWP_Twilio_API();
$result = $twilio->purchase_phone_number($phone_number, $voice_url, $sms_url);
wp_send_json($result);
}
/**
* AJAX handler for configuring a phone number
*/
public function ajax_configure_number() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$number_sid = sanitize_text_field($_POST['number_sid']);
$voice_url = esc_url_raw($_POST['voice_url']);
$sms_url = esc_url_raw($_POST['sms_url']);
$twilio = new TWP_Twilio_API();
$result = $twilio->configure_phone_number($number_sid, $voice_url, $sms_url);
wp_send_json($result);
}
/**
* AJAX handler for releasing a phone number
*/
public function ajax_release_number() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$number_sid = sanitize_text_field($_POST['number_sid']);
$twilio = new TWP_Twilio_API();
$result = $twilio->release_phone_number($number_sid);
wp_send_json($result);
}
/**
* AJAX handler for getting queue details
*/
public function ajax_get_queue() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$queue_id = intval($_POST['queue_id']);
$queue = TWP_Call_Queue::get_queue($queue_id);
wp_send_json_success($queue);
}
/**
* AJAX handler for saving queue
*/
public function ajax_save_queue() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$queue_id = isset($_POST['queue_id']) ? intval($_POST['queue_id']) : 0;
$data = array(
'queue_name' => sanitize_text_field($_POST['queue_name']),
'phone_number' => sanitize_text_field($_POST['phone_number']),
'agent_group_id' => !empty($_POST['agent_group_id']) ? intval($_POST['agent_group_id']) : null,
'max_size' => intval($_POST['max_size']),
'wait_music_url' => esc_url_raw($_POST['wait_music_url']),
'tts_message' => sanitize_textarea_field($_POST['tts_message']),
'timeout_seconds' => intval($_POST['timeout_seconds'])
);
if ($queue_id) {
// Update existing queue
$result = TWP_Call_Queue::update_queue($queue_id, $data);
} else {
// Create new queue
$result = TWP_Call_Queue::create_queue($data);
}
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for getting queue details with call info
*/
public function ajax_get_queue_details() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$queue_id = intval($_POST['queue_id']);
$queue = TWP_Call_Queue::get_queue($queue_id);
if (!$queue) {
wp_send_json_error('Queue not found');
}
global $wpdb;
$calls_table = $wpdb->prefix . 'twp_queued_calls';
// Get current waiting calls
$waiting_calls = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $calls_table WHERE queue_id = %d AND status = 'waiting' ORDER BY position ASC",
$queue_id
));
// Calculate average wait time
$avg_wait = $wpdb->get_var($wpdb->prepare(
"SELECT AVG(TIMESTAMPDIFF(SECOND, joined_at, answered_at))
FROM $calls_table
WHERE queue_id = %d AND status = 'answered'
AND joined_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)",
$queue_id
));
$queue_status = TWP_Call_Queue::get_queue_status();
$waiting_count = 0;
foreach ($queue_status as $status) {
if ($status['queue_id'] == $queue_id) {
$waiting_count = $status['waiting_calls'];
break;
}
}
wp_send_json_success(array(
'queue' => $queue,
'waiting_calls' => $waiting_count,
'avg_wait_time' => $avg_wait ? round($avg_wait) . ' seconds' : 'N/A',
'calls' => $waiting_calls
));
}
/**
* AJAX handler for getting all queues
*/
public function ajax_get_all_queues() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$queues = TWP_Call_Queue::get_all_queues();
wp_send_json_success($queues);
}
/**
* AJAX handler for deleting queue
*/
public function ajax_delete_queue() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$queue_id = intval($_POST['queue_id']);
$result = TWP_Call_Queue::delete_queue($queue_id);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for dashboard stats
*/
public function ajax_get_dashboard_stats() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Ensure database tables exist
require_once TWP_PLUGIN_DIR . 'includes/class-twp-activator.php';
$tables_exist = TWP_Activator::ensure_tables_exist();
global $wpdb;
$calls_table = $wpdb->prefix . 'twp_queued_calls';
$log_table = $wpdb->prefix . 'twp_call_log';
$active_calls = 0;
$queued_calls = 0;
$recent_calls = array();
try {
// Check if tables exist before querying
$calls_table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $calls_table));
$log_table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $log_table));
if ($calls_table_exists) {
// First, clean up old answered calls that might be stuck (older than 2 hours)
$wpdb->query(
"UPDATE $calls_table
SET status = 'completed', ended_at = NOW()
WHERE status = 'answered'
AND joined_at < DATE_SUB(NOW(), INTERVAL 2 HOUR)"
);
// Get active calls - only recent ones to avoid counting stuck records
$active_calls = $wpdb->get_var(
"SELECT COUNT(*) FROM $calls_table
WHERE status IN ('waiting', 'answered')
AND joined_at >= DATE_SUB(NOW(), INTERVAL 4 HOUR)"
);
// Get queued calls
$queued_calls = $wpdb->get_var(
"SELECT COUNT(*) FROM $calls_table WHERE status = 'waiting'"
);
}
if ($log_table_exists) {
// Get recent calls from last 24 hours
$recent_calls = $wpdb->get_results(
"SELECT call_sid, status, duration, updated_at
FROM $log_table
WHERE updated_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY updated_at DESC
LIMIT 10"
);
}
} catch (Exception $e) {
error_log('TWP Plugin Dashboard Stats Error: ' . $e->getMessage());
// Continue with default values
}
$formatted_calls = array();
foreach ($recent_calls as $call) {
$formatted_calls[] = array(
'time' => date('H:i', strtotime($call->updated_at)),
'from' => substr($call->call_sid, 0, 10) . '...',
'to' => 'System',
'status' => ucfirst($call->status),
'duration' => $call->duration ? $call->duration . 's' : '-'
);
}
wp_send_json_success(array(
'active_calls' => $active_calls ?: 0,
'queued_calls' => $queued_calls ?: 0,
'recent_calls' => $formatted_calls
));
}
/**
* AJAX handler for getting Eleven Labs voices
*/
public function ajax_get_elevenlabs_voices() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$elevenlabs = new TWP_ElevenLabs_API();
$result = $elevenlabs->get_cached_voices();
if ($result['success']) {
wp_send_json_success($result['data']['voices']);
} else {
$error_message = 'Failed to load voices';
if (is_string($result['error'])) {
$error_message = $result['error'];
} elseif (is_array($result['error']) && isset($result['error']['detail'])) {
$error_message = $result['error']['detail'];
} elseif (is_array($result['error']) && isset($result['error']['error'])) {
$error_message = $result['error']['error'];
}
// Check if it's an API key issue and provide better error messages
if (empty(get_option('twp_elevenlabs_api_key'))) {
$error_message = 'Please configure your ElevenLabs API key in the settings first.';
} elseif (strpos(strtolower($error_message), 'unauthorized') !== false ||
strpos(strtolower($error_message), 'invalid') !== false ||
strpos(strtolower($error_message), '401') !== false) {
$error_message = 'Invalid API key. Please check your ElevenLabs API key in the settings.';
} elseif (strpos(strtolower($error_message), 'quota') !== false ||
strpos(strtolower($error_message), 'limit') !== false) {
$error_message = 'API quota exceeded. Please check your ElevenLabs subscription limits.';
} elseif (strpos(strtolower($error_message), 'network') !== false ||
strpos(strtolower($error_message), 'timeout') !== false ||
strpos(strtolower($error_message), 'connection') !== false) {
$error_message = 'Network error connecting to ElevenLabs. Please try again later.';
} elseif ($error_message === 'Failed to load voices') {
// Generic error - provide more helpful message
$api_key = get_option('twp_elevenlabs_api_key');
if (empty($api_key)) {
$error_message = 'No ElevenLabs API key configured. Please add your API key in the settings.';
} else {
$error_message = 'Unable to connect to ElevenLabs API. Please check your API key and internet connection.';
}
}
wp_send_json_error($error_message);
}
}
/**
* AJAX handler for getting ElevenLabs models
*/
public function ajax_get_elevenlabs_models() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$elevenlabs = new TWP_ElevenLabs_API();
$result = $elevenlabs->get_cached_models();
if ($result['success']) {
wp_send_json_success($result['data']);
} else {
$error_message = 'Failed to load models';
if (is_string($result['error'])) {
$error_message = $result['error'];
} elseif (is_array($result['error']) && isset($result['error']['detail'])) {
$error_message = $result['error']['detail'];
} elseif (is_array($result['error']) && isset($result['error']['error'])) {
$error_message = $result['error']['error'];
}
// Check if it's an API key issue and provide better error messages
if (empty(get_option('twp_elevenlabs_api_key'))) {
$error_message = 'Please configure your ElevenLabs API key in the settings first.';
} elseif (strpos(strtolower($error_message), 'unauthorized') !== false ||
strpos(strtolower($error_message), 'invalid') !== false ||
strpos(strtolower($error_message), '401') !== false) {
$error_message = 'Invalid API key. Please check your ElevenLabs API key in the settings.';
} elseif (strpos(strtolower($error_message), 'quota') !== false ||
strpos(strtolower($error_message), 'limit') !== false) {
$error_message = 'API quota exceeded. Please check your ElevenLabs subscription limits.';
} elseif (strpos(strtolower($error_message), 'network') !== false ||
strpos(strtolower($error_message), 'timeout') !== false ||
strpos(strtolower($error_message), 'connection') !== false) {
$error_message = 'Network error connecting to ElevenLabs. Please try again later.';
} elseif ($error_message === 'Failed to load models') {
// Generic error - provide more helpful message
$api_key = get_option('twp_elevenlabs_api_key');
if (empty($api_key)) {
$error_message = 'No ElevenLabs API key configured. Please add your API key in the settings.';
} else {
$error_message = 'Unable to connect to ElevenLabs API. Please check your API key and internet connection.';
}
}
wp_send_json_error($error_message);
}
}
/**
* AJAX handler for previewing a voice
*/
public function ajax_preview_voice() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$voice_id = sanitize_text_field($_POST['voice_id']);
$text = sanitize_text_field($_POST['text']) ?: 'Hello, this is a preview of this voice.';
$elevenlabs = new TWP_ElevenLabs_API();
$result = $elevenlabs->text_to_speech($text, $voice_id);
if ($result['success']) {
wp_send_json_success(array(
'audio_url' => $result['file_url']
));
} else {
$error_message = 'Failed to generate voice preview';
if (is_string($result['error'])) {
$error_message = $result['error'];
} elseif (is_array($result['error']) && isset($result['error']['detail'])) {
$error_message = $result['error']['detail'];
} elseif (is_array($result['error']) && isset($result['error']['error'])) {
$error_message = $result['error']['error'];
}
wp_send_json_error($error_message);
}
}
/**
* AJAX handler to get voicemail details
*/
public function ajax_get_voicemail() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$voicemail_id = intval($_POST['voicemail_id']);
if (!$voicemail_id) {
wp_send_json_error('Invalid voicemail ID');
}
global $wpdb;
$table_name = $wpdb->prefix . 'twp_voicemails';
$voicemail = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$voicemail_id
));
if ($voicemail) {
wp_send_json_success($voicemail);
} else {
wp_send_json_error('Voicemail not found');
}
}
/**
* AJAX handler to delete voicemail
*/
public function ajax_delete_voicemail() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$voicemail_id = intval($_POST['voicemail_id']);
if (!$voicemail_id) {
wp_send_json_error('Invalid voicemail ID');
}
global $wpdb;
$table_name = $wpdb->prefix . 'twp_voicemails';
$result = $wpdb->delete(
$table_name,
array('id' => $voicemail_id),
array('%d')
);
if ($result !== false) {
wp_send_json_success('Voicemail deleted successfully');
} else {
wp_send_json_error('Error deleting voicemail');
}
}
/**
* AJAX handler to get voicemail audio URL
*/
public function ajax_get_voicemail_audio() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Unauthorized');
return;
}
$voicemail_id = isset($_POST['voicemail_id']) ? intval($_POST['voicemail_id']) : 0;
if (!$voicemail_id) {
wp_send_json_error('Invalid voicemail ID');
return;
}
global $wpdb;
$table_name = $wpdb->prefix . 'twp_voicemails';
$voicemail = $wpdb->get_row($wpdb->prepare(
"SELECT recording_url FROM $table_name WHERE id = %d",
$voicemail_id
));
if (!$voicemail || !$voicemail->recording_url) {
wp_send_json_error('Voicemail not found');
return;
}
// Fetch the audio from Twilio using authenticated request
$account_sid = get_option('twp_twilio_account_sid');
$auth_token = get_option('twp_twilio_auth_token');
// Add .mp3 to the URL if not present
$audio_url = $voicemail->recording_url;
if (strpos($audio_url, '.mp3') === false && strpos($audio_url, '.wav') === false) {
$audio_url .= '.mp3';
}
// Log for debugging
error_log('TWP Voicemail Audio - Fetching from: ' . $audio_url);
// Fetch audio with authentication
$response = wp_remote_get($audio_url, array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode($account_sid . ':' . $auth_token)
),
'timeout' => 30
));
if (is_wp_error($response)) {
error_log('TWP Voicemail Audio - Error: ' . $response->get_error_message());
wp_send_json_error('Unable to fetch audio: ' . $response->get_error_message());
return;
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
error_log('TWP Voicemail Audio - HTTP Error: ' . $response_code);
wp_send_json_error('Audio fetch failed with code: ' . $response_code);
return;
}
$body = wp_remote_retrieve_body($response);
$content_type = wp_remote_retrieve_header($response, 'content-type') ?: 'audio/mpeg';
// Return audio as base64 data URL
$base64_audio = base64_encode($body);
$data_url = 'data:' . $content_type . ';base64,' . $base64_audio;
wp_send_json_success(array(
'audio_url' => $data_url,
'content_type' => $content_type,
'size' => strlen($body)
));
}
/**
* AJAX handler to manually transcribe voicemail
*/
public function ajax_transcribe_voicemail() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$voicemail_id = intval($_POST['voicemail_id']);
if (!$voicemail_id) {
wp_send_json_error('Invalid voicemail ID');
}
global $wpdb;
$table_name = $wpdb->prefix . 'twp_voicemails';
$voicemail = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$voicemail_id
));
if (!$voicemail) {
wp_send_json_error('Voicemail not found');
}
// For now, we'll use a placeholder transcription since we'd need a speech-to-text service
// In a real implementation, you'd send the recording URL to a transcription service
$placeholder_transcription = "This is a placeholder transcription. In a production environment, this would be generated using a speech-to-text service like Google Cloud Speech-to-Text, Amazon Transcribe, or Twilio's built-in transcription service.";
$result = $wpdb->update(
$table_name,
array('transcription' => $placeholder_transcription),
array('id' => $voicemail_id),
array('%s'),
array('%d')
);
if ($result !== false) {
wp_send_json_success(array('transcription' => $placeholder_transcription));
} else {
wp_send_json_error('Error generating transcription');
}
}
/**
* AJAX handler for getting all groups
*/
public function ajax_get_all_groups() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$groups = TWP_Agent_Groups::get_all_groups();
wp_send_json_success($groups);
}
/**
* AJAX handler for getting a group
*/
public function ajax_get_group() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$group_id = intval($_POST['group_id']);
$group = TWP_Agent_Groups::get_group($group_id);
wp_send_json_success($group);
}
/**
* AJAX handler for saving a group
*/
public function ajax_save_group() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$group_id = isset($_POST['group_id']) ? intval($_POST['group_id']) : 0;
$data = array(
'group_name' => sanitize_text_field($_POST['group_name']),
'description' => sanitize_textarea_field($_POST['description']),
'ring_strategy' => sanitize_text_field($_POST['ring_strategy'] ?? 'simultaneous'),
'timeout_seconds' => intval($_POST['timeout_seconds'] ?? 30)
);
if ($group_id) {
$result = TWP_Agent_Groups::update_group($group_id, $data);
} else {
$result = TWP_Agent_Groups::create_group($data);
}
wp_send_json_success(array('success' => $result !== false, 'group_id' => $result));
}
/**
* AJAX handler for deleting a group
*/
public function ajax_delete_group() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$group_id = intval($_POST['group_id']);
$result = TWP_Agent_Groups::delete_group($group_id);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for getting group members
*/
public function ajax_get_group_members() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$group_id = intval($_POST['group_id']);
$members = TWP_Agent_Groups::get_group_members($group_id);
wp_send_json_success($members);
}
/**
* AJAX handler for adding a group member
*/
public function ajax_add_group_member() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$group_id = intval($_POST['group_id']);
$user_id = intval($_POST['user_id']);
$priority = intval($_POST['priority'] ?? 0);
$result = TWP_Agent_Groups::add_member($group_id, $user_id, $priority);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for removing a group member
*/
public function ajax_remove_group_member() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$group_id = intval($_POST['group_id']);
$user_id = intval($_POST['user_id']);
$result = TWP_Agent_Groups::remove_member($group_id, $user_id);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for accepting a call
*/
public function ajax_accept_call() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$call_id = intval($_POST['call_id']);
$user_id = get_current_user_id();
$result = TWP_Agent_Manager::accept_queued_call($call_id, $user_id);
if ($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result['error']);
}
}
/**
* AJAX handler for getting waiting calls
*/
public function ajax_get_waiting_calls() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
global $wpdb;
$calls_table = $wpdb->prefix . 'twp_queued_calls';
$queues_table = $wpdb->prefix . 'twp_call_queues';
$waiting_calls = $wpdb->get_results("
SELECT
c.*,
q.queue_name,
TIMESTAMPDIFF(SECOND, c.joined_at, NOW()) as wait_seconds
FROM $calls_table c
JOIN $queues_table q ON c.queue_id = q.id
WHERE c.status = 'waiting'
ORDER BY c.position ASC
");
wp_send_json_success($waiting_calls);
}
/**
* AJAX handler for setting agent status
*/
public function ajax_set_agent_status() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$user_id = get_current_user_id();
$status = sanitize_text_field($_POST['status']);
$result = TWP_Agent_Manager::set_agent_status($user_id, $status);
wp_send_json_success(array('success' => $result));
}
/**
* AJAX handler for getting call details
*/
public function ajax_get_call_details() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
if (!isset($_POST['call_sid'])) {
wp_send_json_error('Call SID is required');
}
$call_sid = sanitize_text_field($_POST['call_sid']);
global $wpdb;
$table_name = $wpdb->prefix . 'twp_call_log';
$call = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE call_sid = %s",
$call_sid
));
if ($call) {
// Parse actions_taken if it's JSON
if ($call->actions_taken && is_string($call->actions_taken)) {
$decoded = json_decode($call->actions_taken, true);
if ($decoded) {
$call->actions_taken = json_encode($decoded, JSON_PRETTY_PRINT);
}
}
wp_send_json_success($call);
} else {
wp_send_json_error('Call not found');
}
}
/**
* AJAX handler for requesting callback
*/
public function ajax_request_callback() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$phone_number = sanitize_text_field($_POST['phone_number']);
$queue_id = isset($_POST['queue_id']) ? intval($_POST['queue_id']) : null;
$call_sid = isset($_POST['call_sid']) ? sanitize_text_field($_POST['call_sid']) : null;
if (empty($phone_number)) {
wp_send_json_error(array('message' => 'Phone number is required'));
}
$callback_id = TWP_Callback_Manager::request_callback($phone_number, $queue_id, $call_sid);
if ($callback_id) {
wp_send_json_success(array(
'callback_id' => $callback_id,
'message' => 'Callback requested successfully'
));
} else {
wp_send_json_error(array('message' => 'Failed to request callback'));
}
}
/**
* AJAX handler for initiating outbound calls
*/
public function ajax_initiate_outbound_call() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$to_number = sanitize_text_field($_POST['to_number']);
$agent_user_id = get_current_user_id();
if (empty($to_number)) {
wp_send_json_error(array('message' => 'Phone number is required'));
}
$result = TWP_Callback_Manager::initiate_outbound_call($to_number, $agent_user_id);
if ($result['success']) {
wp_send_json_success(array(
'call_sid' => $result['call_sid'],
'message' => 'Outbound call initiated successfully'
));
} else {
wp_send_json_error(array('message' => $result['error']));
}
}
/**
* AJAX handler for getting pending callbacks
*/
public function ajax_get_callbacks() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$pending_callbacks = TWP_Callback_Manager::get_pending_callbacks();
$callback_stats = TWP_Callback_Manager::get_callback_stats();
wp_send_json_success(array(
'callbacks' => $pending_callbacks,
'stats' => $callback_stats
));
}
/**
* AJAX handler for updating phone numbers with status callbacks
*/
public function ajax_update_phone_status_callbacks() {
check_ajax_referer('twp_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
try {
$twilio = new TWP_Twilio_API();
$result = $twilio->enable_status_callbacks_for_all_numbers();
if ($result['success']) {
wp_send_json_success($result['data']);
} else {
wp_send_json_error($result['error']);
}
} catch (Exception $e) {
wp_send_json_error('Failed to update phone numbers: ' . $e->getMessage());
}
}
/**
* AJAX handler for toggling individual phone number status callbacks
*/
public function ajax_toggle_number_status_callback() {
check_ajax_referer('twp_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
$sid = isset($_POST['sid']) ? sanitize_text_field($_POST['sid']) : '';
$enable = isset($_POST['enable']) ? $_POST['enable'] === 'true' : false;
if (empty($sid)) {
wp_send_json_error('Phone number SID is required');
}
try {
$twilio = new TWP_Twilio_API();
$result = $twilio->toggle_number_status_callback($sid, $enable);
if ($result['success']) {
wp_send_json_success($result['data']);
} else {
wp_send_json_error($result['error']);
}
} catch (Exception $e) {
wp_send_json_error('Failed to update phone number: ' . $e->getMessage());
}
}
/**
* AJAX handler for initiating outbound calls with from number
*/
public function ajax_initiate_outbound_call_with_from() {
check_ajax_referer('twp_ajax_nonce', 'nonce');
$from_number = sanitize_text_field($_POST['from_number']);
$to_number = sanitize_text_field($_POST['to_number']);
$agent_phone = sanitize_text_field($_POST['agent_phone']);
if (empty($from_number) || empty($to_number) || empty($agent_phone)) {
wp_send_json_error(array('message' => 'All fields are required'));
}
// Validate phone numbers
if (!preg_match('/^\+?[1-9]\d{1,14}$/', str_replace([' ', '-', '(', ')'], '', $to_number))) {
wp_send_json_error(array('message' => 'Invalid destination phone number format'));
}
if (!preg_match('/^\+?[1-9]\d{1,14}$/', str_replace([' ', '-', '(', ')'], '', $agent_phone))) {
wp_send_json_error(array('message' => 'Invalid agent phone number format'));
}
$result = $this->initiate_outbound_call_with_from($from_number, $to_number, $agent_phone);
if ($result['success']) {
wp_send_json_success(array(
'call_sid' => $result['call_sid'],
'message' => 'Outbound call initiated successfully'
));
} else {
wp_send_json_error(array('message' => $result['error']));
}
}
/**
* Initiate outbound call with specific from number
*/
private function initiate_outbound_call_with_from($from_number, $to_number, $agent_phone) {
$twilio = new TWP_Twilio_API();
// Build webhook URL with parameters
$webhook_url = home_url('/wp-json/twilio-webhook/v1/outbound-agent-with-from') . '?' . http_build_query(array(
'target_number' => $to_number,
'agent_user_id' => get_current_user_id(),
'from_number' => $from_number
));
// First call the agent
$agent_call_result = $twilio->make_call(
$agent_phone,
$webhook_url,
null, // No status callback needed for this
$from_number // Use specified from number
);
if ($agent_call_result['success']) {
$call_sid = isset($agent_call_result['data']['sid']) ? $agent_call_result['data']['sid'] : null;
// Set agent to busy
TWP_Agent_Manager::set_agent_status(get_current_user_id(), 'busy', $call_sid);
// Log the outbound call
TWP_Call_Logger::log_call(array(
'call_sid' => $call_sid,
'from_number' => $from_number,
'to_number' => $to_number,
'status' => 'outbound_initiated',
'workflow_name' => 'Outbound Call',
'actions_taken' => json_encode(array(
'agent_id' => get_current_user_id(),
'agent_name' => wp_get_current_user()->display_name,
'type' => 'click_to_call_with_from',
'agent_phone' => $agent_phone
))
));
return array('success' => true, 'call_sid' => $call_sid);
}
return array('success' => false, 'error' => $agent_call_result['error']);
}
}