diff --git a/admin/class-twp-admin.php b/admin/class-twp-admin.php
index 73b1e04..302dccd 100644
--- a/admin/class-twp-admin.php
+++ b/admin/class-twp-admin.php
@@ -1582,8 +1582,8 @@ class TWP_Admin {
';
@@ -506,15 +572,15 @@ jQuery(document).ready(function($) {
html += '
Define what happens when calls come in outside business hours:
';
html += '
';
html += '
';
- console.log('Generating after-hours steps HTML, data:', data.after_hours_steps);
+ // console.log('Generating after-hours steps HTML, data:', data.after_hours_steps);
if (data.after_hours_steps && data.after_hours_steps.length > 0) {
- console.log('Found', data.after_hours_steps.length, 'after-hours steps');
+ // console.log('Found', data.after_hours_steps.length, 'after-hours steps');
data.after_hours_steps.forEach(function(step, index) {
- console.log('Generating HTML for after-hours step', index, ':', step);
+ // console.log('Generating HTML for after-hours step', index, ':', step);
html += generateAfterHoursStepHtml(step, index);
});
} else {
- console.log('No after-hours steps found');
+ // console.log('No after-hours steps found');
html += '
No after-hours steps configured. Add steps below.
';
}
html += '
';
@@ -569,12 +635,17 @@ jQuery(document).ready(function($) {
// Forward action - phone number input
html += '
';
+ '" style="display: ' + (option.action === 'forward' || !option.action ? 'block' : 'none') + ';" ' +
+ (option.action !== 'forward' && option.action ? 'disabled' : '') + '>';
// Queue action - queue dropdown
- html += '
';
+ var queueCurrent = '';
+ if (option.action === 'queue') {
+ queueCurrent = option.queue_id || option.target || option.number || '';
+ }
+ html += '';
html += 'Select queue... ';
// Queue options will be populated by loadQueues function
html += ' ';
@@ -582,12 +653,14 @@ jQuery(document).ready(function($) {
// Voicemail action - message input
html += ' ';
+ '" style="display: ' + (option.action === 'voicemail' ? 'block' : 'none') + ';" ' +
+ (option.action !== 'voicemail' ? 'disabled' : '') + '>';
// Message action - message input
html += ' ';
+ '" style="display: ' + (option.action === 'message' ? 'block' : 'none') + ';" ' +
+ (option.action !== 'message' ? 'disabled' : '') + '>';
html += ' ';
html += '
Remove ';
@@ -678,26 +751,23 @@ jQuery(document).ready(function($) {
};
window.loadSchedulesForStep = function() {
- console.log('Loading schedules for step modal');
+ // console.log('Loading schedules for step modal');
$.post(twp_ajax.ajax_url, {
action: 'twp_get_schedules',
nonce: twp_ajax.nonce
}, function(response) {
- console.log('Schedule response:', response);
+ // console.log('Schedule response:', response);
if (response.success) {
var $select = $('#schedule-select');
var currentScheduleId = $select.data('current') || $select.val();
- console.log('Current schedule ID to select:', currentScheduleId);
- console.log('Available schedules:', response.data.length);
- console.log('Select element found:', $select.length > 0);
- console.log('Select element name attribute:', $select.attr('name'));
+ // console.log('Current schedule ID to select:', currentScheduleId);
var options = '
Select a schedule... ';
response.data.forEach(function(schedule) {
var selected = (schedule.id == currentScheduleId) ? ' selected' : '';
options += '
' + schedule.schedule_name + ' ';
- console.log('Added schedule option:', schedule.schedule_name, 'ID:', schedule.id, 'Selected:', selected);
+ // console.log('Added schedule option:', schedule.schedule_name, 'ID:', schedule.id, 'Selected:', selected);
});
$select.html(options);
@@ -706,7 +776,7 @@ jQuery(document).ready(function($) {
setTimeout(function() {
if (currentScheduleId) {
$select.val(currentScheduleId);
- console.log('Set schedule select value to:', currentScheduleId, 'Current value:', $select.val());
+ // console.log('Set schedule select value to:', currentScheduleId, 'Current value:', $select.val());
// Force trigger change event to ensure any listeners are notified
$select.trigger('change');
@@ -745,9 +815,9 @@ jQuery(document).ready(function($) {
// Parse form data into step data
step.data = parseStepFormData(stepType, formData);
- console.log('Saved step data:', step);
+ // console.log('Saved step data:', step);
if (stepType === 'schedule_check') {
- console.log('Saved schedule step data:', step.data);
+ // console.log('Saved schedule step data:', step.data);
}
updateWorkflowDisplay();
@@ -757,11 +827,10 @@ jQuery(document).ready(function($) {
function parseStepFormData(stepType, formData) {
var data = {};
- console.log('Parsing form data for step type:', stepType);
- console.log('Raw form data:', formData);
+ // console.log('Parsing form data for step type:', stepType);
formData.forEach(function(field) {
- console.log('Processing field:', field.name, '=', field.value);
+ // console.log('Processing field:', field.name, '=', field.value);
if (field.name === 'use_tts') {
data.use_tts = true;
@@ -780,7 +849,12 @@ jQuery(document).ready(function($) {
}
});
- console.log('Parsed data object:', data);
+ // console.log('Parsed data object:', data);
+
+ // Debug voice_id field specifically for greeting/IVR steps
+ if ((stepType === 'greeting' || stepType === 'ivr_menu') && data.voice_id) {
+ // console.log('Found voice_id in', stepType, 'step:', data.voice_id);
+ }
// For queue steps, also save the queue name for display purposes
if (stepType === 'queue' && data.queue_id) {
@@ -800,15 +874,33 @@ jQuery(document).ready(function($) {
// Handle IVR options specially
if (stepType === 'ivr_menu' && data.digit) {
+ // console.log('Processing IVR options - raw data:', data);
data.options = {};
for (var i = 0; i < data.digit.length; i++) {
- data.options[data.digit[i]] = {
+ var option = {
action: data.action[i],
description: data.description[i],
number: data.target[i],
queue_name: data.target[i],
message: data.target[i]
};
+
+ // console.log('Processing option', i, '- action:', data.action[i], 'target:', data.target[i]);
+
+ // For queue action, get the actual queue name from the select option text
+ if (data.action[i] === 'queue' && data.target[i]) {
+ var $queueSelect = $('#step-config-form .ivr-option:eq(' + i + ') .target-queue');
+ var selectedOption = $queueSelect.find('option:selected');
+ // console.log('Queue select for option', i, ':', $queueSelect.length ? 'found' : 'not found');
+ if (selectedOption.length && selectedOption.text() !== 'Select queue...') {
+ option.queue_name = selectedOption.text();
+ option.queue_id = data.target[i];
+ // console.log('Set queue_name to:', option.queue_name, 'queue_id to:', option.queue_id);
+ }
+ }
+
+ // console.log('Final option', i, ':', option);
+ data.options[data.digit[i]] = option;
}
delete data.digit;
delete data.action;
@@ -818,7 +910,7 @@ jQuery(document).ready(function($) {
// Handle schedule check after-hours steps
if (stepType === 'schedule_check') {
- console.log('Parsing schedule_check step data:', data);
+ // console.log('Parsing schedule_check step data:', data);
var afterHoursSteps = [];
@@ -852,9 +944,7 @@ jQuery(document).ready(function($) {
return step && step.type;
});
- console.log('Parsed schedule_check data:', data);
- console.log('After hours steps count:', data.after_hours_steps.length);
- console.log('After hours steps:', data.after_hours_steps);
+ // console.log('Parsed schedule_check data:', data);
}
return data;
@@ -1030,26 +1120,26 @@ jQuery(document).ready(function($) {
// Load workflow data
currentWorkflowId = workflowId;
- console.log('Loading workflow data:', workflow.workflow_data);
+ // console.log('Loading workflow data:', workflow.workflow_data);
if (workflow.workflow_data) {
try {
var workflowData = JSON.parse(workflow.workflow_data);
workflowSteps = workflowData.steps || [];
- console.log('Parsed workflow steps:', workflowSteps);
+ // console.log('Parsed workflow steps:', workflowSteps);
// Debug schedule steps specifically
workflowSteps.forEach(function(step, index) {
if (step.type === 'schedule_check') {
- console.log('Found schedule step at index', index, ':', step);
+ // console.log('Found schedule step at index', index, ':', step);
}
});
} catch (e) {
console.error('Error parsing workflow data:', e);
- console.log('Raw workflow data that failed to parse:', workflow.workflow_data);
+ // console.log('Raw workflow data that failed to parse:', workflow.workflow_data);
workflowSteps = [];
}
} else {
- console.log('No workflow data found');
+ // console.log('No workflow data found');
workflowSteps = [];
}
@@ -1121,7 +1211,7 @@ jQuery(document).ready(function($) {
var queue = response.data;
$('#queue-id').val(queue.id);
$('[name="queue_name"]').val(queue.queue_name);
- $('[name="phone_number"]').val(queue.phone_number);
+ $('[name="notification_number"]').val(queue.notification_number);
$('[name="agent_group_id"]').val(queue.agent_group_id);
$('[name="max_size"]').val(queue.max_size);
$('[name="timeout_seconds"]').val(queue.timeout_seconds);
@@ -1464,8 +1554,9 @@ jQuery(document).ready(function($) {
window.loadQueues = function(button) {
var $button = $(button);
var $select = $button.prev('select.queue-select');
- var currentValue = $select.data('current');
+ var currentValue = $select.attr('data-current') || $select.val() || '';
+ // console.log('loadQueues - currentValue from data-current:', currentValue);
$button.text('Loading...').prop('disabled', true);
$.post(twp_ajax.ajax_url, {
@@ -1494,50 +1585,106 @@ jQuery(document).ready(function($) {
// Load queues for a specific select element (used in IVR options)
function loadQueuesForSelect($select) {
- var currentValue = $select.data('current') || $select.val();
+ var currentValue = $select.attr('data-current') || $select.val() || '';
+ // console.log('loadQueuesForSelect called with currentValue:', currentValue);
$.post(twp_ajax.ajax_url, {
action: 'twp_get_all_queues',
nonce: twp_ajax.nonce
}, function(response) {
+ // console.log('Queue loading response:', response);
if (response.success) {
var options = '
Select queue... ';
response.data.forEach(function(queue) {
var selected = queue.id == currentValue ? ' selected' : '';
options += '
' + queue.queue_name + ' ';
+ // console.log('Added queue option:', queue.queue_name, 'ID:', queue.id, 'Selected:', selected);
});
$select.html(options);
+ // console.log('Queue options loaded, final select value:', $select.val());
+ } else {
+ console.error('Failed to load queues:', response);
}
+ }).fail(function(xhr, status, error) {
+ console.error('Queue loading failed:', error);
});
}
// Voice management for workflow steps
- window.loadWorkflowVoices = function(button) {
- var $button = $(button);
- var $select = $button.prev('select.voice-select');
- var currentValue = $select.data('current');
+ window.loadWorkflowVoices = function(buttonOrSelect) {
+ var $button, $select;
- $button.text('Loading...').prop('disabled', true);
+ // Check if we were passed a button or a select element
+ if ($(buttonOrSelect).is('button')) {
+ $button = $(buttonOrSelect);
+ $select = $button.prev('select.voice-select');
+ } else if ($(buttonOrSelect).is('select')) {
+ $select = $(buttonOrSelect);
+ $button = $select.next('button');
+ } else {
+ // Fallback - assume it's a button
+ $button = $(buttonOrSelect);
+ $select = $button.prev('select.voice-select');
+ }
+
+ // Read the current value - first check if there's a selected option with a value, then data-current attribute
+ var currentValue = '';
+ var selectedOption = $select.find('option:selected');
+ if (selectedOption.length && selectedOption.val()) {
+ currentValue = selectedOption.val();
+ } else {
+ currentValue = $select.attr('data-current') || '';
+ }
+
+ // console.log('loadWorkflowVoices - currentValue:', currentValue, 'from data-current:', $select.attr('data-current'));
+
+ if ($button.length) {
+ $button.text('Loading...').prop('disabled', true);
+ }
$.post(twp_ajax.ajax_url, {
action: 'twp_get_elevenlabs_voices',
nonce: twp_ajax.nonce
}, function(response) {
- $button.text('Load Voices').prop('disabled', false);
+ // console.log('Voice loading response:', response);
+ if ($button.length) {
+ $button.text('Load Voices').prop('disabled', false);
+ }
if (response.success) {
var options = '
Default voice ';
response.data.forEach(function(voice) {
- var selected = voice.voice_id === currentValue ? ' selected' : '';
+ var selected = (voice.voice_id === currentValue) ? ' selected' : '';
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
var optionText = voice.name + (description ? ' (' + description + ')' : '');
- options += '
' + optionText + ' ';
+ options += '
' + optionText + ' ';
+
+ if (selected) {
+ // console.log('Setting voice as selected:', voice.name, 'ID:', voice.voice_id);
+ }
});
$select.html(options);
+
+ // If we had a current value, make sure it's selected
+ if (currentValue) {
+ $select.val(currentValue);
+ // Update the voice name field with the selected voice's name
+ var $voiceNameInput = $select.siblings('input[name="voice_name"]');
+ if ($voiceNameInput.length) {
+ var selectedVoice = $select.find('option:selected');
+ var voiceName = selectedVoice.data('voice-name') || selectedVoice.text() || '';
+ if (selectedVoice.val() === '') {
+ voiceName = '';
+ }
+ $voiceNameInput.val(voiceName);
+ }
+ }
+
+ // console.log('Voice loaded and set to:', currentValue, '- Final select value:', $select.val());
} else {
var errorMessage = 'Error loading voices: ';
if (typeof response.data === 'string') {
@@ -1552,7 +1699,9 @@ jQuery(document).ready(function($) {
alert(errorMessage);
}
}).fail(function() {
- $button.text('Load Voices').prop('disabled', false);
+ if ($button.length) {
+ $button.text('Load Voices').prop('disabled', false);
+ }
alert('Failed to load voices. Please check your API key.');
});
};
@@ -1587,22 +1736,40 @@ jQuery(document).ready(function($) {
}
});
+ // Handle voice selection changes to update hidden voice_name field
+ $(document).on('change', 'select.voice-select', function() {
+ var $select = $(this);
+ var $voiceNameInput = $select.siblings('input[name="voice_name"]');
+ var selectedOption = $select.find('option:selected');
+ var voiceName = selectedOption.data('voice-name') || selectedOption.text() || '';
+
+ // If it's the default option, clear the name
+ if (selectedOption.val() === '') {
+ voiceName = '';
+ }
+
+ if ($voiceNameInput.length) {
+ $voiceNameInput.val(voiceName);
+ // console.log('Voice name updated to:', voiceName);
+ }
+ });
+
// Handle IVR action changes to show/hide appropriate target inputs
$(document).on('change', '.ivr-action-select', function() {
var $option = $(this).closest('.ivr-option');
var $container = $option.find('.ivr-target-container');
var action = $(this).val();
- // Hide all target inputs
- $container.find('.target-forward, .target-queue, .target-voicemail, .target-message').hide();
+ // Hide and disable all target inputs first
+ $container.find('.target-forward, .target-queue, .target-voicemail, .target-message').hide().prop('disabled', true);
- // Show the appropriate input based on action
+ // Show and enable the appropriate input based on action
switch(action) {
case 'forward':
- $container.find('.target-forward').show();
+ $container.find('.target-forward').show().prop('disabled', false);
break;
case 'queue':
- $container.find('.target-queue').show();
+ $container.find('.target-queue').show().prop('disabled', false);
// Load queues if not already loaded
var $queueSelect = $container.find('.target-queue');
if ($queueSelect.find('option').length <= 1) {
@@ -1610,10 +1777,10 @@ jQuery(document).ready(function($) {
}
break;
case 'voicemail':
- $container.find('.target-voicemail').show();
+ $container.find('.target-voicemail').show().prop('disabled', false);
break;
case 'message':
- $container.find('.target-message').show();
+ $container.find('.target-message').show().prop('disabled', false);
break;
}
});
@@ -1672,7 +1839,7 @@ jQuery(document).ready(function($) {
// Play after loading
audio.play().catch(function(error) {
- console.log('Audio play error:', error);
+ // console.log('Audio play error:', error);
// If data URL fails, try direct Twilio URL as fallback
if (voicemail.recording_url) {
audio.src = voicemail.recording_url + '.mp3';
diff --git a/includes/class-twp-activator.php b/includes/class-twp-activator.php
index 74f89c2..5e83850 100644
--- a/includes/class-twp-activator.php
+++ b/includes/class-twp-activator.php
@@ -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)");
}
diff --git a/includes/class-twp-agent-manager.php b/includes/class-twp-agent-manager.php
index c1a7e3e..02cb48c 100644
--- a/includes/class-twp-agent-manager.php
+++ b/includes/class-twp-agent-manager.php
@@ -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);
diff --git a/includes/class-twp-call-queue.php b/includes/class-twp-call-queue.php
index ad94df0..9e020e2 100644
--- a/includes/class-twp-call-queue.php
+++ b/includes/class-twp-call-queue.php
@@ -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");
diff --git a/includes/class-twp-webhooks.php b/includes/class-twp-webhooks.php
index 57f7f53..18a9b7b 100644
--- a/includes/class-twp-webhooks.php
+++ b/includes/class-twp-webhooks.php
@@ -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('
');
$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('
');
+ $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('
');
- $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('
');
$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);
diff --git a/includes/class-twp-workflow.php b/includes/class-twp-workflow.php
index 340fcaa..075ae37 100644
--- a/includes/class-twp-workflow.php
+++ b/includes/class-twp-workflow.php
@@ -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);
}