progress made
This commit is contained in:
@@ -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']);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user