progress made

This commit is contained in:
2025-08-11 20:31:48 -07:00
parent 805af2f199
commit 304b5de40b
15 changed files with 4028 additions and 404 deletions

View File

@@ -370,12 +370,247 @@ class TWP_Admin {
<p class="description">Phone number to receive SMS notifications for urgent voicemails. Use full international format (e.g., +1234567890)</p>
</td>
</tr>
<tr>
<th scope="row">Default SMS From Number</th>
<td>
<select name="twp_default_sms_number" id="default-sms-number" class="regular-text">
<option value="">Select a Twilio number...</option>
<?php
// Get current value
$current_sms_number = get_option('twp_default_sms_number');
try {
// Get Twilio phone numbers
$twilio = new TWP_Twilio_API();
$numbers_result = $twilio->get_phone_numbers();
if ($numbers_result['success'] && isset($numbers_result['data']['incoming_phone_numbers'])) {
$numbers = $numbers_result['data']['incoming_phone_numbers'];
if (is_array($numbers) && !empty($numbers)) {
foreach ($numbers as $number) {
$phone = isset($number['phone_number']) ? $number['phone_number'] : '';
$friendly_name = isset($number['friendly_name']) ? $number['friendly_name'] : $phone;
if (!empty($phone)) {
$selected = ($phone === $current_sms_number) ? ' selected' : '';
echo '<option value="' . esc_attr($phone) . '"' . $selected . '>' . esc_html($friendly_name . ' (' . $phone . ')') . '</option>';
}
}
}
}
} catch (Exception $e) {
// If there's an error loading numbers, show the current value as a manual input
if (!empty($current_sms_number)) {
echo '<option value="' . esc_attr($current_sms_number) . '" selected>' . esc_html($current_sms_number . ' (configured)') . '</option>';
}
}
?>
</select>
<button type="button" onclick="loadTwilioNumbers('default-sms-number')" class="button" style="margin-left: 10px;">Refresh Numbers</button>
<p class="description">Default Twilio phone number to use as sender for SMS messages when not in a workflow context.</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<hr>
<h2>Phone Number Maintenance</h2>
<div class="card">
<h3>Real-Time Queue Cleanup Configuration</h3>
<p>Configure individual phone numbers to send status callbacks when calls end, enabling real-time queue cleanup.</p>
<p><strong>When enabled:</strong> Calls will be removed from queue immediately when callers hang up.</p>
<div id="phone-numbers-list" style="margin: 20px 0;">
<p style="color: #666;">Loading phone numbers...</p>
</div>
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd;">
<button type="button" class="button" id="refresh-numbers-btn">
Refresh List
</button>
<button type="button" class="button button-primary" id="update-all-numbers-btn" style="display: none;">
Enable for All Numbers
</button>
</div>
<div id="update-result" style="margin-top: 10px;"></div>
</div>
<script>
// Phone number management
var statusCallbackUrl = '<?php echo home_url('/wp-json/twilio-webhook/v1/status'); ?>';
function loadPhoneNumbers() {
var listDiv = document.getElementById('phone-numbers-list');
listDiv.innerHTML = '<p style="color: #666;">Loading phone numbers...</p>';
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
try {
var response = JSON.parse(xhr.responseText);
console.log('Phone numbers response:', response);
if (response.success && response.data) {
if (response.data.length === 0) {
listDiv.innerHTML = '<p style="color: #666;">No phone numbers found in your Twilio account. <a href="#" onclick="location.reload();">Refresh</a></p>';
return;
}
var html = '<table style="width: 100%; border-collapse: collapse;">';
html += '<thead><tr>';
html += '<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;">Phone Number</th>';
html += '<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;">Name</th>';
html += '<th style="text-align: center; padding: 10px; border-bottom: 2px solid #ddd;">Status Callbacks</th>';
html += '<th style="text-align: center; padding: 10px; border-bottom: 2px solid #ddd;">Action</th>';
html += '</tr></thead><tbody>';
response.data.forEach(function(number) {
var isEnabled = number.status_callback_url === statusCallbackUrl;
var statusColor = isEnabled ? '#28a745' : '#dc3545';
var statusText = isEnabled ? 'Enabled' : 'Disabled';
var buttonText = isEnabled ? 'Disable' : 'Enable';
var buttonClass = isEnabled ? 'button-secondary' : 'button-primary';
html += '<tr>';
html += '<td style="padding: 10px; border-bottom: 1px solid #eee;">' + number.phone_number + '</td>';
html += '<td style="padding: 10px; border-bottom: 1px solid #eee;">' + (number.friendly_name || 'N/A') + '</td>';
html += '<td style="text-align: center; padding: 10px; border-bottom: 1px solid #eee;">';
html += '<span style="color: ' + statusColor + '; font-weight: bold;">' + statusText + '</span>';
if (isEnabled) {
html += '<br><small style="color: #666;">Real-time cleanup active</small>';
}
html += '</td>';
html += '<td style="text-align: center; padding: 10px; border-bottom: 1px solid #eee;">';
html += '<button type="button" class="button ' + buttonClass + ' toggle-status-btn" ';
html += 'data-sid="' + number.sid + '" ';
html += 'data-number="' + number.phone_number + '" ';
html += 'data-enabled="' + isEnabled + '">';
html += buttonText + '</button>';
html += '</td>';
html += '</tr>';
});
html += '</tbody></table>';
listDiv.innerHTML = html;
// Show "Enable All" button if there are disabled numbers
var hasDisabled = response.data.some(function(n) {
return n.status_callback_url !== statusCallbackUrl;
});
document.getElementById('update-all-numbers-btn').style.display = hasDisabled ? 'inline-block' : 'none';
// Attach event listeners to toggle buttons
document.querySelectorAll('.toggle-status-btn').forEach(function(btn) {
btn.addEventListener('click', toggleNumberStatus);
});
} else {
var errorMsg = response.error || 'Failed to load phone numbers';
listDiv.innerHTML = '<p style="color: #dc3545;">' + errorMsg + '</p>';
console.error('Failed to load phone numbers:', response);
}
} catch(e) {
listDiv.innerHTML = '<p style="color: #dc3545;">Error loading phone numbers: ' + e.message + '</p>';
console.error('Error parsing response:', e, xhr.responseText);
}
};
xhr.send('action=twp_get_phone_numbers&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>');
}
function toggleNumberStatus(e) {
var button = e.target;
var sid = button.dataset.sid;
var number = button.dataset.number;
var isEnabled = button.dataset.enabled === 'true';
var resultDiv = document.getElementById('update-result');
button.disabled = true;
button.textContent = 'Updating...';
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
button.disabled = false;
try {
var response = JSON.parse(xhr.responseText);
if (response.success) {
resultDiv.innerHTML = '<div style="background: #d4edda; border: 1px solid #c3e6cb; padding: 10px; border-radius: 4px; margin-top: 10px;">' +
'<strong style="color: #155724;">✅ Success!</strong> ' + number + ' has been updated.</div>';
// Reload the list to show updated status
setTimeout(loadPhoneNumbers, 1000);
} else {
button.textContent = isEnabled ? 'Disable' : 'Enable';
resultDiv.innerHTML = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 10px; border-radius: 4px; margin-top: 10px;">' +
'<strong style="color: #721c24;">❌ Error:</strong> ' + response.error + '</div>';
}
} catch(e) {
button.textContent = isEnabled ? 'Disable' : 'Enable';
resultDiv.innerHTML = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 10px; border-radius: 4px; margin-top: 10px;">' +
'<strong style="color: #721c24;">❌ Error:</strong> Failed to update number</div>';
}
};
var params = 'action=twp_toggle_number_status_callback&nonce=' + '<?php echo wp_create_nonce('twp_nonce'); ?>' +
'&sid=' + encodeURIComponent(sid) +
'&enable=' + (!isEnabled);
xhr.send(params);
}
// Refresh button
document.getElementById('refresh-numbers-btn').addEventListener('click', loadPhoneNumbers);
// Enable all button
document.getElementById('update-all-numbers-btn').addEventListener('click', function() {
var button = this;
var resultDiv = document.getElementById('update-result');
button.disabled = true;
button.textContent = 'Updating All...';
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
button.disabled = false;
button.textContent = 'Enable for All Numbers';
try {
var response = JSON.parse(xhr.responseText);
if (response.success) {
resultDiv.innerHTML = '<div style="background: #d4edda; border: 1px solid #c3e6cb; padding: 10px; border-radius: 4px; margin-top: 10px;">' +
'<strong style="color: #155724;">✅ Success!</strong> Updated ' + response.data.updated_count + ' numbers.</div>';
setTimeout(loadPhoneNumbers, 1000);
} else {
resultDiv.innerHTML = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 10px; border-radius: 4px; margin-top: 10px;">' +
'<strong style="color: #721c24;">❌ Error:</strong> ' + response.error + '</div>';
}
} catch(e) {
resultDiv.innerHTML = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 10px; border-radius: 4px; margin-top: 10px;">' +
'<strong style="color: #721c24;">❌ Error:</strong> Failed to process response</div>';
}
};
xhr.send('action=twp_update_phone_status_callbacks&nonce=' + '<?php echo wp_create_nonce('twp_nonce'); ?>');
});
// Load numbers on page load
loadPhoneNumbers();
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
alert('Copied to clipboard!');
@@ -548,6 +783,51 @@ class TWP_Admin {
xhr.send('action=twp_preview_voice&voice_id=' + voiceId + '&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>');
}
function loadTwilioNumbers(selectId) {
var select = document.getElementById(selectId);
var button = event.target;
var currentValue = select.value;
button.textContent = 'Loading...';
button.disabled = true;
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
button.textContent = 'Refresh Numbers';
button.disabled = false;
try {
var response = JSON.parse(xhr.responseText);
if (response.success) {
var options = '<option value="">Select a Twilio number...</option>';
if (Array.isArray(response.data)) {
response.data.forEach(function(number) {
var phone = number.phone_number || '';
var friendlyName = number.friendly_name || phone;
if (phone) {
var selected = phone === currentValue ? ' selected' : '';
options += '<option value="' + phone + '"' + selected + '>' + friendlyName + ' (' + phone + ')' + '</option>';
}
});
}
select.innerHTML = options;
} else {
alert('Error loading Twilio numbers: ' + (response.data || 'Unknown error'));
}
} catch (e) {
alert('Failed to load Twilio numbers. Please check your Twilio credentials.');
}
};
xhr.send('action=twp_get_phone_numbers&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>');
}
// Auto-load voices if API key exists
document.addEventListener('DOMContentLoaded', function() {
var apiKeyField = document.querySelector('[name="twp_elevenlabs_api_key"]');
@@ -588,8 +868,8 @@ class TWP_Admin {
<th>Schedule Name</th>
<th>Days</th>
<th>Business Hours</th>
<th>Business Hours Workflow</th>
<th>After Hours Workflow</th>
<th>Holidays</th>
<th>Workflow</th>
<th>Status</th>
<th>Actions</th>
</tr>
@@ -605,20 +885,21 @@ class TWP_Admin {
<td><?php echo esc_html($schedule->start_time . ' - ' . $schedule->end_time); ?></td>
<td>
<?php
if ($schedule->workflow_id) {
$workflow = TWP_Workflow::get_workflow($schedule->workflow_id);
echo $workflow ? esc_html($workflow->workflow_name) : 'Workflow #' . $schedule->workflow_id;
if (!empty($schedule->holiday_dates)) {
$holidays = array_map('trim', explode(',', $schedule->holiday_dates));
echo esc_html(count($holidays) . ' date' . (count($holidays) > 1 ? 's' : '') . ' set');
} else {
echo '<em>No workflow selected</em>';
echo '<em>None</em>';
}
?>
</td>
<td>
<?php
if ($schedule->forward_number) {
echo 'Forward to ' . esc_html($schedule->forward_number);
if ($schedule->workflow_id) {
$workflow = TWP_Workflow::get_workflow($schedule->workflow_id);
echo $workflow ? esc_html($workflow->workflow_name) : 'Workflow #' . $schedule->workflow_id;
} else {
echo '<em>Default behavior</em>';
echo '<em>No specific workflow</em>';
}
?>
</td>
@@ -680,9 +961,9 @@ class TWP_Admin {
</div>
<div class="form-field">
<label for="business-hours-workflow">Business Hours Workflow:</label>
<select id="business-hours-workflow" name="workflow_id" required>
<option value="">Select a workflow...</option>
<label for="business-hours-workflow">Business Hours Workflow (Optional):</label>
<select id="business-hours-workflow" name="workflow_id">
<option value="">No specific workflow</option>
<?php
$workflows = TWP_Workflow::get_workflows();
if ($workflows && is_array($workflows)) {
@@ -727,6 +1008,12 @@ class TWP_Admin {
<p class="description">This workflow will handle calls outside business hours</p>
</div>
<div class="form-field">
<label for="holiday-dates">Holiday Dates (Optional):</label>
<textarea id="holiday-dates" name="holiday_dates" rows="3" placeholder="2025-12-25, 2025-01-01, 2025-07-04"></textarea>
<p class="description">Enter dates (YYYY-MM-DD format) when this schedule should be inactive, separated by commas. These days will be treated as "after hours" regardless of time.</p>
</div>
<div class="form-field">
<label>
<input type="checkbox" name="is_active" checked> Active
@@ -917,6 +1204,25 @@ class TWP_Admin {
<div class="twp-queue-card">
<h3><?php echo esc_html($queue->queue_name); ?></h3>
<div class="queue-stats">
<div class="stat">
<span class="label">Phone Number:</span>
<span class="value"><?php echo esc_html($queue->phone_number ?: 'Not set'); ?></span>
</div>
<?php
// Get agent group name
$group_name = 'None';
if (!empty($queue->agent_group_id)) {
$groups_table = $wpdb->prefix . 'twp_agent_groups';
$group = $wpdb->get_row($wpdb->prepare("SELECT group_name FROM $groups_table WHERE id = %d", $queue->agent_group_id));
if ($group) {
$group_name = $group->group_name;
}
}
?>
<div class="stat">
<span class="label">Agent Group:</span>
<span class="value"><?php echo esc_html($group_name); ?></span>
</div>
<div class="stat">
<span class="label">Waiting:</span>
<span class="value"><?php echo $waiting_calls; ?></span>
@@ -933,6 +1239,7 @@ class TWP_Admin {
<div class="queue-actions">
<button class="button" onclick="viewQueueDetails(<?php echo $queue->id; ?>)">View Details</button>
<button class="button" onclick="editQueue(<?php echo $queue->id; ?>)">Edit</button>
<button class="button button-link-delete" onclick="deleteQueue(<?php echo $queue->id; ?>)" style="color: #dc3232;">Delete</button>
</div>
</div>
<?php
@@ -951,6 +1258,51 @@ class TWP_Admin {
<label>Queue Name:</label>
<input type="text" name="queue_name" required>
<label>Phone Number:</label>
<select name="phone_number" id="queue-phone-number" class="regular-text">
<option value="">Select a Twilio number...</option>
<?php
try {
// Get Twilio phone numbers
$twilio = new TWP_Twilio_API();
$numbers_result = $twilio->get_phone_numbers();
if ($numbers_result['success'] && isset($numbers_result['data']['incoming_phone_numbers'])) {
$numbers = $numbers_result['data']['incoming_phone_numbers'];
if (is_array($numbers) && !empty($numbers)) {
foreach ($numbers as $number) {
$phone = isset($number['phone_number']) ? $number['phone_number'] : '';
$friendly_name = isset($number['friendly_name']) ? $number['friendly_name'] : $phone;
if (!empty($phone)) {
echo '<option value="' . esc_attr($phone) . '">' . esc_html($friendly_name . ' (' . $phone . ')') . '</option>';
}
}
}
}
} catch (Exception $e) {
echo '<option value="">Error loading numbers</option>';
}
?>
</select>
<button type="button" onclick="loadTwilioNumbers('queue-phone-number')" class="button" style="margin-left: 10px;">Refresh Numbers</button>
<p class="description">Phone number that this queue is associated with (used for agent caller ID)</p>
<label>Agent Group:</label>
<select name="agent_group_id" id="queue-agent-group" class="regular-text">
<option value="">Select an agent group...</option>
<?php
// Get agent groups
global $wpdb;
$groups_table = $wpdb->prefix . 'twp_agent_groups';
$groups = $wpdb->get_results("SELECT * FROM $groups_table ORDER BY group_name");
foreach ($groups as $group) {
echo '<option value="' . esc_attr($group->id) . '">' . esc_html($group->group_name) . '</option>';
}
?>
</select>
<p class="description">Agent group that will handle calls from this queue</p>
<label>Max Size:</label>
<input type="number" name="max_size" min="1" max="100" value="10">
@@ -1942,16 +2294,20 @@ class TWP_Admin {
register_setting('twilio-wp-settings-group', 'twp_default_queue_size');
register_setting('twilio-wp-settings-group', 'twp_urgent_keywords');
register_setting('twilio-wp-settings-group', 'twp_sms_notification_number');
register_setting('twilio-wp-settings-group', 'twp_default_sms_number');
}
/**
* Enqueue styles
*/
public function enqueue_styles() {
// Enqueue ThickBox styles for WordPress native modals
wp_enqueue_style('thickbox');
wp_enqueue_style(
$this->plugin_name,
TWP_PLUGIN_URL . 'assets/css/admin.css',
array(),
array('thickbox'),
$this->version,
'all'
);
@@ -1961,10 +2317,13 @@ class TWP_Admin {
* 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'),
array('jquery', 'thickbox'),
$this->version,
false
);
@@ -1976,7 +2335,8 @@ class TWP_Admin {
'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'))
'has_elevenlabs_key' => !empty(get_option('twp_elevenlabs_api_key')),
'timezone' => wp_timezone_string()
)
);
}
@@ -1991,14 +2351,22 @@ class TWP_Admin {
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(',', array_map('sanitize_text_field', $_POST['days_of_week'])),
'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']) ? intval($_POST['workflow_id']) : null,
'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
);
@@ -2023,12 +2391,20 @@ class TWP_Admin {
$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));
}
@@ -2048,6 +2424,40 @@ class TWP_Admin {
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
*/
@@ -2060,11 +2470,37 @@ class TWP_Admin {
$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']),
'workflow_data' => $_POST['workflow_data'], // Already JSON
'is_active' => isset($_POST['is_active']) ? 1 : 0
'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) {
@@ -2073,7 +2509,12 @@ class TWP_Admin {
$result = TWP_Workflow::create_workflow($data);
}
wp_send_json_success(array('success' => $result));
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));
}
}
/**
@@ -2123,7 +2564,7 @@ class TWP_Admin {
$twilio = new TWP_Twilio_API();
$twiml_url = home_url('/twilio-webhook/voice');
$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);
@@ -2259,15 +2700,25 @@ class TWP_Admin {
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'])
);
$result = TWP_Call_Queue::create_queue($data);
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));
}
@@ -2384,9 +2835,19 @@ class TWP_Admin {
$log_table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $log_table));
if ($calls_table_exists) {
// Get active calls (assuming active calls are those in queue or in progress)
// 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')"
"SELECT COUNT(*) FROM $calls_table
WHERE status IN ('waiting', 'answered')
AND joined_at >= DATE_SUB(NOW(), INTERVAL 4 HOUR)"
);
// Get queued calls
@@ -2623,6 +3084,85 @@ class TWP_Admin {
}
}
/**
* 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
*/
@@ -2849,6 +3389,41 @@ class TWP_Admin {
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
*/
@@ -2915,6 +3490,61 @@ class TWP_Admin {
));
}
/**
* 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
*/
@@ -2997,4 +3627,5 @@ class TWP_Admin {
return array('success' => false, 'error' => $agent_call_result['error']);
}
}