testing progress

This commit is contained in:
2025-08-12 09:12:54 -07:00
parent 75fae0fbdb
commit e18e046431
7 changed files with 372 additions and 119 deletions

View File

@@ -101,7 +101,7 @@ class TWP_Activator {
$sql_queues = "CREATE TABLE $table_queues (
id int(11) NOT NULL AUTO_INCREMENT,
queue_name varchar(100) NOT NULL,
phone_number varchar(20),
notification_number varchar(20),
agent_group_id int(11),
max_size int(11) DEFAULT 10,
wait_music_url varchar(255),
@@ -110,7 +110,7 @@ class TWP_Activator {
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY agent_group_id (agent_group_id),
KEY phone_number (phone_number)
KEY notification_number (notification_number)
) $charset_collate;";
// Queued calls table
@@ -304,20 +304,29 @@ class TWP_Activator {
$wpdb->query("ALTER TABLE $table_schedules MODIFY COLUMN days_of_week varchar(100) NOT NULL");
}
// Add new columns to call queues table
// Add new columns to call queues table and migrate phone_number to notification_number
$table_queues = $wpdb->prefix . 'twp_call_queues';
// Check if phone_number column exists in queues table
// Check if phone_number column exists and notification_number doesn't - need migration
$phone_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'phone_number'");
if (empty($phone_column_exists)) {
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN phone_number varchar(20) AFTER queue_name");
$wpdb->query("ALTER TABLE $table_queues ADD INDEX phone_number (phone_number)");
$notification_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'notification_number'");
if (!empty($phone_column_exists) && empty($notification_column_exists)) {
// Migrate phone_number to notification_number
$wpdb->query("ALTER TABLE $table_queues CHANGE phone_number notification_number varchar(20)");
// Update the index name
$wpdb->query("ALTER TABLE $table_queues DROP INDEX phone_number");
$wpdb->query("ALTER TABLE $table_queues ADD INDEX notification_number (notification_number)");
} elseif (empty($phone_column_exists) && empty($notification_column_exists)) {
// Fresh installation - add notification_number column
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN notification_number varchar(20) AFTER queue_name");
$wpdb->query("ALTER TABLE $table_queues ADD INDEX notification_number (notification_number)");
}
// Check if agent_group_id column exists in queues table
$group_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'agent_group_id'");
if (empty($group_column_exists)) {
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN agent_group_id int(11) AFTER phone_number");
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN agent_group_id int(11) AFTER notification_number");
$wpdb->query("ALTER TABLE $table_queues ADD INDEX agent_group_id (agent_group_id)");
}

View File

@@ -243,18 +243,18 @@ class TWP_Agent_Manager {
// Make a new call to the agent with proper caller ID
$twilio = new TWP_Twilio_API();
// Get the queue's phone number for proper caller ID (same logic as SMS webhook)
// Get the queue's notification number for proper caller ID (same logic as SMS webhook)
$queues_table = $wpdb->prefix . 'twp_call_queues';
$queue_info = $wpdb->get_row($wpdb->prepare(
"SELECT phone_number FROM $queues_table WHERE id = %d",
"SELECT notification_number FROM $queues_table WHERE id = %d",
$call->queue_id
));
// Priority: 1) Queue's phone number, 2) Call's original to_number, 3) Default SMS number
// Priority: 1) Queue's notification number, 2) Call's original to_number, 3) Default SMS number
$workflow_number = null;
if (!empty($queue_info->phone_number)) {
$workflow_number = $queue_info->phone_number;
error_log('TWP Web Accept: Using queue phone number: ' . $workflow_number);
if (!empty($queue_info->notification_number)) {
$workflow_number = $queue_info->notification_number;
error_log('TWP Web Accept: Using queue notification number: ' . $workflow_number);
} elseif (!empty($call->to_number)) {
$workflow_number = $call->to_number;
error_log('TWP Web Accept: Using original workflow number: ' . $workflow_number);

View File

@@ -435,7 +435,7 @@ class TWP_Call_Queue {
$insert_data = array(
'queue_name' => sanitize_text_field($data['queue_name']),
'phone_number' => !empty($data['phone_number']) ? sanitize_text_field($data['phone_number']) : '',
'notification_number' => !empty($data['notification_number']) ? sanitize_text_field($data['notification_number']) : '',
'agent_group_id' => !empty($data['agent_group_id']) ? intval($data['agent_group_id']) : null,
'max_size' => intval($data['max_size']),
'wait_music_url' => esc_url_raw($data['wait_music_url']),
@@ -463,7 +463,7 @@ class TWP_Call_Queue {
$update_data = array(
'queue_name' => sanitize_text_field($data['queue_name']),
'phone_number' => !empty($data['phone_number']) ? sanitize_text_field($data['phone_number']) : '',
'notification_number' => !empty($data['notification_number']) ? sanitize_text_field($data['notification_number']) : '',
'agent_group_id' => !empty($data['agent_group_id']) ? intval($data['agent_group_id']) : null,
'max_size' => intval($data['max_size']),
'wait_music_url' => esc_url_raw($data['wait_music_url']),
@@ -585,8 +585,8 @@ class TWP_Call_Queue {
$twilio = new TWP_Twilio_API();
// Use the queue's phone number as the from number, or fall back to default
$from_number = !empty($queue->phone_number) ? $queue->phone_number : TWP_Twilio_API::get_sms_from_number();
// Use the queue's notification number as the from number, or fall back to default
$from_number = !empty($queue->notification_number) ? $queue->notification_number : TWP_Twilio_API::get_sms_from_number();
if (empty($from_number)) {
error_log("TWP: No SMS from number available for queue notifications");

View File

@@ -767,57 +767,121 @@ class TWP_Webhooks {
/**
* Handle IVR response
*/
private function handle_ivr_response() {
$digits = isset($_POST['Digits']) ? $_POST['Digits'] : '';
$workflow_id = isset($_GET['workflow_id']) ? intval($_GET['workflow_id']) : 0;
$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 0;
public function handle_ivr_response($request) {
$digits = $request->get_param('Digits') ?: '';
$workflow_id = intval($request->get_param('workflow_id') ?: 0);
$step_id = intval($request->get_param('step_id') ?: 0);
// Debug logging
error_log('TWP IVR: Received digits="' . $digits . '", workflow_id=' . $workflow_id . ', step_id=' . $step_id);
error_log('TWP IVR: All request params: ' . json_encode($request->get_params()));
if (!$workflow_id || !$step_id) {
$this->send_default_response();
return;
return $this->send_twiml_response($this->get_default_twiml());
}
$workflow = TWP_Workflow::get_workflow($workflow_id);
if (!$workflow) {
$this->send_default_response();
return;
return $this->send_twiml_response($this->get_default_twiml());
}
$workflow_data = json_decode($workflow->workflow_data, true);
// Debug: log all steps in workflow
error_log('TWP IVR: Looking for step_id ' . $step_id . ' in workflow ' . $workflow_id);
foreach ($workflow_data['steps'] as $index => $step) {
error_log('TWP IVR: Step ' . $index . ' has ID: ' . (isset($step['id']) ? $step['id'] : 'NO ID'));
}
// Find the step and its options
foreach ($workflow_data['steps'] as $step) {
if ($step['id'] == $step_id && isset($step['options'][$digits])) {
$option = $step['options'][$digits];
if ($step['id'] == $step_id) {
error_log('TWP IVR: Found matching step with ID ' . $step_id);
// Options can be in step['data']['options'] or step['options']
$options = isset($step['data']['options']) ? $step['data']['options'] :
(isset($step['options']) ? $step['options'] : array());
switch ($option['action']) {
// Debug: log all available options
error_log('TWP IVR: All available options for step ' . $step_id . ': ' . json_encode($options));
if (isset($options[$digits])) {
$option = $options[$digits];
// Log for debugging
error_log('TWP IVR: Found option for digit ' . $digits . ': ' . json_encode($option));
switch ($option['action']) {
case 'forward':
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
$dial = $twiml->addChild('Dial');
$dial->addChild('Number', $option['number']);
echo $twiml->asXML();
return;
return $this->send_twiml_response($twiml->asXML());
case 'queue':
// Determine queue ID - could be in queue_id field or legacy queue_name field
$queue_id = null;
if (isset($option['queue_id']) && is_numeric($option['queue_id']) && $option['queue_id'] > 0) {
$queue_id = intval($option['queue_id']);
} elseif (isset($option['queue_name']) && is_numeric($option['queue_name']) && $option['queue_name'] > 0) {
// Legacy format where queue_name contains the queue ID
$queue_id = intval($option['queue_name']);
} elseif (isset($option['number']) && is_numeric($option['number']) && $option['number'] > 0) {
// Another legacy format where number contains the queue ID
$queue_id = intval($option['number']);
}
error_log('TWP IVR Queue: Determined queue_id=' . ($queue_id ? $queue_id : 'NULL') . ' from option: ' . json_encode($option));
// Use the TWP queue system if we have a valid queue_id
if ($queue_id && $queue_id > 0) {
$call_data = array(
'call_sid' => $request->get_param('CallSid'),
'from_number' => $request->get_param('From'),
'to_number' => $request->get_param('To')
);
error_log('TWP IVR Queue: Adding call to queue_id=' . $queue_id . ', call_sid=' . $call_data['call_sid']);
$position = TWP_Call_Queue::add_to_queue($queue_id, $call_data);
if ($position) {
error_log('TWP IVR Queue: Call added to position ' . $position);
// Generate TwiML for queue wait with proper callback URL
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
$enqueue = $twiml->addChild('Enqueue');
$enqueue->addAttribute('waitUrl', home_url('/wp-json/twilio-webhook/v1/queue-wait?queue_id=' . $queue_id));
$enqueue->addChild('Task', json_encode(array('queue_id' => $queue_id, 'position' => $position)));
return $this->send_twiml_response($twiml->asXML());
} else {
error_log('TWP IVR Queue: Failed to add call to queue');
}
}
// If we reach here, no valid queue was found - provide helpful message
error_log('TWP IVR Queue: No valid queue_id found, providing error message to caller');
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
$enqueue = $twiml->addChild('Enqueue', $option['queue_name']);
echo $twiml->asXML();
return;
$say = $twiml->addChild('Say', 'Sorry, that option is not currently available. Please try again or hang up.');
$say->addAttribute('voice', 'alice');
$twiml->addChild('Redirect'); // Redirect back to IVR menu
return $this->send_twiml_response($twiml->asXML());
case 'voicemail':
$elevenlabs = new TWP_ElevenLabs_API();
$twiml = TWP_Workflow::create_voicemail_twiml($option, $elevenlabs);
echo $twiml;
return;
return $this->send_twiml_response($twiml);
case 'message':
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
$say = $twiml->addChild('Say', $option['message']);
$say->addAttribute('voice', 'alice');
$twiml->addChild('Hangup');
echo $twiml->asXML();
return;
return $this->send_twiml_response($twiml->asXML());
}
} else {
// Log for debugging when option not found
error_log('TWP IVR: No option found for digit "' . $digits . '" in step ' . $step_id);
error_log('TWP IVR: Available options: ' . json_encode(array_keys($options)));
error_log('TWP IVR: Full step data: ' . json_encode($step));
}
}
}
@@ -827,7 +891,7 @@ class TWP_Webhooks {
$say = $twiml->addChild('Say', 'Invalid option. Please try again.');
$say->addAttribute('voice', 'alice');
$twiml->addChild('Redirect');
echo $twiml->asXML();
return $this->send_twiml_response($twiml->asXML());
}
/**
@@ -1308,6 +1372,13 @@ class TWP_Webhooks {
echo $response->asXML();
}
private function get_default_twiml() {
$response = new \Twilio\TwiML\VoiceResponse();
$response->say('Thank you for calling. Goodbye.', ['voice' => 'alice']);
$response->hangup();
return $response->asXML();
}
/**
* Send status SMS
*/
@@ -1764,7 +1835,7 @@ class TWP_Webhooks {
// Find waiting calls from queues assigned to this agent's groups
$placeholders = implode(',', array_fill(0, count($agent_groups), '%d'));
$waiting_call = $wpdb->get_row($wpdb->prepare("
SELECT qc.*, q.phone_number as queue_phone_number, q.agent_group_id
SELECT qc.*, q.notification_number as queue_notification_number, q.agent_group_id
FROM $calls_table qc
LEFT JOIN $queues_table q ON qc.queue_id = q.id
WHERE qc.status = 'waiting'
@@ -1775,7 +1846,7 @@ class TWP_Webhooks {
} else {
// Agent not in any group - can only handle calls from queues with no assigned group
$waiting_call = $wpdb->get_row($wpdb->prepare("
SELECT qc.*, q.phone_number as queue_phone_number, q.agent_group_id
SELECT qc.*, q.notification_number as queue_notification_number, q.agent_group_id
FROM $calls_table qc
LEFT JOIN $queues_table q ON qc.queue_id = q.id
WHERE qc.status = %s
@@ -1796,12 +1867,12 @@ class TWP_Webhooks {
error_log('TWP Debug: Waiting call data: ' . print_r($waiting_call, true));
// Detailed debugging of phone number selection
error_log('TWP Debug: Queue workflow_number field: ' . (empty($waiting_call->queue_phone_number) ? 'EMPTY' : $waiting_call->queue_phone_number));
error_log('TWP Debug: Queue notification_number field: ' . (empty($waiting_call->queue_notification_number) ? 'EMPTY' : $waiting_call->queue_notification_number));
error_log('TWP Debug: Original workflow_number field: ' . (empty($waiting_call->to_number) ? 'EMPTY' : $waiting_call->to_number));
if (!empty($waiting_call->queue_phone_number)) {
$workflow_number = $waiting_call->queue_phone_number;
error_log('TWP Debug: SELECTED queue workflow_number: ' . $workflow_number);
if (!empty($waiting_call->queue_notification_number)) {
$workflow_number = $waiting_call->queue_notification_number;
error_log('TWP Debug: SELECTED queue notification_number: ' . $workflow_number);
} elseif (!empty($waiting_call->to_number)) {
$workflow_number = $waiting_call->to_number;
error_log('TWP Debug: SELECTED original workflow_number: ' . $workflow_number);

View File

@@ -72,6 +72,8 @@ class TWP_Workflow {
break;
case 'ivr_menu':
// Add workflow_id to the step data
$step['workflow_id'] = $workflow_id;
$step_twiml = self::create_ivr_menu_twiml($step, $elevenlabs);
$stop_after_step = true; // IVR menu needs user input, stop here
break;
@@ -301,8 +303,12 @@ class TWP_Workflow {
$gather->addAttribute('action', $step['action_url']);
} else {
$webhook_url = home_url('/wp-json/twilio-webhook/v1/ivr-response');
$webhook_url = add_query_arg('workflow_id', $step['workflow_id'], $webhook_url);
$webhook_url = add_query_arg('step_id', $step['id'], $webhook_url);
if (isset($step['workflow_id'])) {
$webhook_url = add_query_arg('workflow_id', $step['workflow_id'], $webhook_url);
}
if (isset($step['id'])) {
$webhook_url = add_query_arg('step_id', $step['id'], $webhook_url);
}
$gather->addAttribute('action', $webhook_url);
}