3631 lines
156 KiB
PHP
3631 lines
156 KiB
PHP
<?php
|
|
/**
|
|
* Admin interface class
|
|
*/
|
|
class TWP_Admin {
|
|
|
|
private $plugin_name;
|
|
private $version;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct($plugin_name, $version) {
|
|
$this->plugin_name = $plugin_name;
|
|
$this->version = $version;
|
|
}
|
|
|
|
/**
|
|
* Register admin menu
|
|
*/
|
|
public function add_plugin_admin_menu() {
|
|
add_menu_page(
|
|
'Twilio WP Plugin',
|
|
'Twilio Phone',
|
|
'manage_options',
|
|
'twilio-wp-plugin',
|
|
array($this, 'display_plugin_dashboard'),
|
|
'dashicons-phone',
|
|
30
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Dashboard',
|
|
'Dashboard',
|
|
'manage_options',
|
|
'twilio-wp-plugin',
|
|
array($this, 'display_plugin_dashboard')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Settings',
|
|
'Settings',
|
|
'manage_options',
|
|
'twilio-wp-settings',
|
|
array($this, 'display_plugin_settings')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Phone Schedules',
|
|
'Schedules',
|
|
'manage_options',
|
|
'twilio-wp-schedules',
|
|
array($this, 'display_schedules_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Workflows',
|
|
'Workflows',
|
|
'manage_options',
|
|
'twilio-wp-workflows',
|
|
array($this, 'display_workflows_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Call Queues',
|
|
'Queues',
|
|
'manage_options',
|
|
'twilio-wp-queues',
|
|
array($this, 'display_queues_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Phone Numbers',
|
|
'Phone Numbers',
|
|
'manage_options',
|
|
'twilio-wp-numbers',
|
|
array($this, 'display_numbers_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Voicemails',
|
|
'Voicemails',
|
|
'manage_options',
|
|
'twilio-wp-voicemails',
|
|
array($this, 'display_voicemails_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Call Logs',
|
|
'Call Logs',
|
|
'manage_options',
|
|
'twilio-wp-call-logs',
|
|
array($this, 'display_call_logs_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Agent Groups',
|
|
'Agent Groups',
|
|
'manage_options',
|
|
'twilio-wp-groups',
|
|
array($this, 'display_groups_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Agent Queue',
|
|
'Agent Queue',
|
|
'manage_options',
|
|
'twilio-wp-agent-queue',
|
|
array($this, 'display_agent_queue_page')
|
|
);
|
|
|
|
add_submenu_page(
|
|
'twilio-wp-plugin',
|
|
'Outbound Calls',
|
|
'Outbound Calls',
|
|
'manage_options',
|
|
'twilio-wp-outbound',
|
|
array($this, 'display_outbound_calls_page')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Display dashboard
|
|
*/
|
|
public function display_plugin_dashboard() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Twilio Phone System Dashboard</h1>
|
|
|
|
<div class="twp-dashboard">
|
|
<div class="twp-stats-grid">
|
|
<div class="twp-stat-card">
|
|
<h3>Active Calls</h3>
|
|
<div class="twp-stat-value" id="active-calls">0</div>
|
|
</div>
|
|
|
|
<div class="twp-stat-card">
|
|
<h3>Calls in Queue</h3>
|
|
<div class="twp-stat-value" id="queued-calls">0</div>
|
|
</div>
|
|
|
|
<div class="twp-stat-card">
|
|
<h3>Active Schedules</h3>
|
|
<div class="twp-stat-value" id="active-schedules">
|
|
<?php
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . 'twp_phone_schedules';
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE is_active = 1");
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="twp-stat-card">
|
|
<h3>Active Workflows</h3>
|
|
<div class="twp-stat-value" id="active-workflows">
|
|
<?php
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . 'twp_workflows';
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE is_active = 1");
|
|
?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="twp-recent-activity">
|
|
<h2>Recent Call Activity</h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>From</th>
|
|
<th>To</th>
|
|
<th>Status</th>
|
|
<th>Duration</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="recent-calls">
|
|
<tr>
|
|
<td colspan="5">No recent calls</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display settings page
|
|
*/
|
|
public function display_plugin_settings() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Twilio WP Plugin Settings</h1>
|
|
|
|
<form method="post" action="options.php">
|
|
<?php settings_fields('twilio-wp-settings-group'); ?>
|
|
|
|
<h2>Twilio API Settings</h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">Account SID</th>
|
|
<td>
|
|
<input type="text" name="twp_twilio_account_sid"
|
|
value="<?php echo esc_attr(get_option('twp_twilio_account_sid')); ?>"
|
|
class="regular-text" />
|
|
<p class="description">Your Twilio Account SID</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">Auth Token</th>
|
|
<td>
|
|
<input type="password" name="twp_twilio_auth_token"
|
|
value="<?php echo esc_attr(get_option('twp_twilio_auth_token')); ?>"
|
|
class="regular-text" />
|
|
<p class="description">Your Twilio Auth Token</p>
|
|
</td>
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
<h2>Eleven Labs API Settings</h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">API Key</th>
|
|
<td>
|
|
<input type="password" name="twp_elevenlabs_api_key"
|
|
value="<?php echo esc_attr(get_option('twp_elevenlabs_api_key')); ?>"
|
|
class="regular-text" />
|
|
<p class="description">Your Eleven Labs API Key</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">Model</th>
|
|
<td>
|
|
<select name="twp_elevenlabs_model_id" id="elevenlabs-model-select" class="regular-text">
|
|
<option value="">Select a model...</option>
|
|
<option value="eleven_multilingual_v2" <?php selected(get_option('twp_elevenlabs_model_id', 'eleven_multilingual_v2'), 'eleven_multilingual_v2'); ?>>
|
|
Multilingual v2 (Recommended)
|
|
</option>
|
|
<option value="eleven_monolingual_v1" <?php selected(get_option('twp_elevenlabs_model_id'), 'eleven_monolingual_v1'); ?>>
|
|
Monolingual v1
|
|
</option>
|
|
<option value="eleven_multilingual_v1" <?php selected(get_option('twp_elevenlabs_model_id'), 'eleven_multilingual_v1'); ?>>
|
|
Multilingual v1
|
|
</option>
|
|
<option value="eleven_turbo_v2" <?php selected(get_option('twp_elevenlabs_model_id'), 'eleven_turbo_v2'); ?>>
|
|
Turbo v2 (Faster)
|
|
</option>
|
|
</select>
|
|
<button type="button" class="button" onclick="loadElevenLabsModels()">Load Available Models</button>
|
|
<p class="description">Text-to-speech model to use. Multilingual v2 is recommended for best quality. Turbo v2 offers faster generation.</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">Default Voice</th>
|
|
<td>
|
|
<select name="twp_elevenlabs_voice_id" id="elevenlabs-voice-select" class="regular-text"
|
|
data-current="<?php echo esc_attr(get_option('twp_elevenlabs_voice_id')); ?>">
|
|
<option value="">Select a voice...</option>
|
|
<?php
|
|
$current_voice = get_option('twp_elevenlabs_voice_id');
|
|
if ($current_voice): ?>
|
|
<option value="<?php echo esc_attr($current_voice); ?>" selected>
|
|
Current Voice (<?php echo esc_html($current_voice); ?>)
|
|
</option>
|
|
<?php endif; ?>
|
|
</select>
|
|
<button type="button" class="button" onclick="loadElevenLabsVoices()">Load Voices</button>
|
|
<p class="description">Default voice for text-to-speech. Click "Load Voices" after entering your API key.</p>
|
|
<?php if (WP_DEBUG): ?>
|
|
<p class="description"><small>Debug: Current saved voice ID = "<?php echo esc_html(get_option('twp_elevenlabs_voice_id', 'empty')); ?>"</small></p>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>Default Queue Settings</h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">Queue Timeout (seconds)</th>
|
|
<td>
|
|
<input type="number" name="twp_default_queue_timeout"
|
|
value="<?php echo esc_attr(get_option('twp_default_queue_timeout', 300)); ?>"
|
|
min="30" max="3600" />
|
|
<p class="description">Default timeout for calls in queue</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">Queue Size</th>
|
|
<td>
|
|
<input type="number" name="twp_default_queue_size"
|
|
value="<?php echo esc_attr(get_option('twp_default_queue_size', 10)); ?>"
|
|
min="1" max="100" />
|
|
<p class="description">Default maximum queue size</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>Webhook URLs</h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">Voice Webhook</th>
|
|
<td>
|
|
<code><?php echo rest_url('twilio-webhook/v1/voice'); ?></code>
|
|
<button type="button" class="button" onclick="copyToClipboard('<?php echo rest_url('twilio-webhook/v1/voice'); ?>')">Copy</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">SMS Webhook</th>
|
|
<td>
|
|
<code><?php echo rest_url('twilio-webhook/v1/sms'); ?></code>
|
|
<button type="button" class="button" onclick="copyToClipboard('<?php echo rest_url('twilio-webhook/v1/sms'); ?>')">Copy</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">Status Webhook</th>
|
|
<td>
|
|
<code><?php echo rest_url('twilio-webhook/v1/status'); ?></code>
|
|
<button type="button" class="button" onclick="copyToClipboard('<?php echo rest_url('twilio-webhook/v1/status'); ?>')">Copy</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">Transcription Webhook</th>
|
|
<td>
|
|
<code><?php echo rest_url('twilio-webhook/v1/transcription'); ?></code>
|
|
<button type="button" class="button" onclick="copyToClipboard('<?php echo rest_url('twilio-webhook/v1/transcription'); ?>')">Copy</button>
|
|
<p class="description">Used for automatic voicemail transcription callbacks</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>Voicemail & Transcription Settings</h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">Urgent Keywords</th>
|
|
<td>
|
|
<input type="text" name="twp_urgent_keywords"
|
|
value="<?php echo esc_attr(get_option('twp_urgent_keywords', 'urgent,emergency,important,asap,help')); ?>"
|
|
class="large-text" />
|
|
<p class="description">Comma-separated keywords that trigger urgent notifications when found in voicemail transcriptions. Example: urgent,emergency,important,asap,help</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">SMS Notification Number</th>
|
|
<td>
|
|
<input type="text" name="twp_sms_notification_number"
|
|
value="<?php echo esc_attr(get_option('twp_sms_notification_number')); ?>"
|
|
class="regular-text"
|
|
placeholder="+1234567890" />
|
|
<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!');
|
|
});
|
|
}
|
|
|
|
function loadElevenLabsModels() {
|
|
var select = document.getElementById('elevenlabs-model-select');
|
|
var button = select.nextElementSibling;
|
|
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 = 'Load Available Models';
|
|
button.disabled = false;
|
|
|
|
try {
|
|
var response = JSON.parse(xhr.responseText);
|
|
|
|
if (response.success) {
|
|
var options = '<option value="">Select a model...</option>';
|
|
|
|
response.data.forEach(function(model) {
|
|
var selected = model.model_id === currentValue ? ' selected' : '';
|
|
var displayName = model.name || model.model_id;
|
|
if (model.description) {
|
|
displayName += ' - ' + model.description;
|
|
}
|
|
options += '<option value="' + model.model_id + '"' + selected + '>' + displayName + '</option>';
|
|
});
|
|
|
|
select.innerHTML = options;
|
|
} else {
|
|
var errorMessage = 'Error loading models: ';
|
|
if (typeof response.data === 'string') {
|
|
errorMessage += response.data;
|
|
} else if (response.data && response.data.detail) {
|
|
errorMessage += response.data.detail;
|
|
} else if (response.data && response.data.error) {
|
|
errorMessage += response.data.error;
|
|
} else {
|
|
errorMessage += 'Unknown error occurred';
|
|
}
|
|
alert(errorMessage);
|
|
}
|
|
} catch (e) {
|
|
alert('Failed to load models. Please check your API key.');
|
|
}
|
|
};
|
|
|
|
xhr.send('action=twp_get_elevenlabs_models&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>');
|
|
}
|
|
|
|
function loadElevenLabsVoices() {
|
|
var select = document.getElementById('elevenlabs-voice-select');
|
|
var button = select.nextElementSibling;
|
|
var currentValue = select.getAttribute('data-current') || select.value;
|
|
|
|
console.log('Loading voices, current value:', currentValue);
|
|
|
|
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 = 'Load Voices';
|
|
button.disabled = false;
|
|
|
|
try {
|
|
var response = JSON.parse(xhr.responseText);
|
|
|
|
if (response.success) {
|
|
var options = '<option value="">Select a voice...</option>';
|
|
|
|
response.data.forEach(function(voice) {
|
|
var selected = voice.voice_id === currentValue ? ' selected' : '';
|
|
if (selected) {
|
|
console.log('Found matching voice:', voice.name, 'ID:', voice.voice_id);
|
|
}
|
|
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
|
|
var optionText = voice.name + (description ? ' (' + description + ')' : '');
|
|
options += '<option value="' + voice.voice_id + '"' + selected + '>' + optionText + '</option>';
|
|
});
|
|
|
|
select.innerHTML = options;
|
|
|
|
// Update the data-current attribute with the selected value
|
|
if (currentValue) {
|
|
select.setAttribute('data-current', currentValue);
|
|
}
|
|
|
|
// Add preview buttons
|
|
addVoicePreviewButtons(select, response.data);
|
|
} else {
|
|
var errorMessage = 'Error loading voices: ';
|
|
if (typeof response.data === 'string') {
|
|
errorMessage += response.data;
|
|
} else if (response.data && response.data.detail) {
|
|
errorMessage += response.data.detail;
|
|
} else if (response.data && response.data.error) {
|
|
errorMessage += response.data.error;
|
|
} else {
|
|
errorMessage += 'Unknown error occurred';
|
|
}
|
|
alert(errorMessage);
|
|
}
|
|
} catch (e) {
|
|
alert('Failed to load voices. Please check your API key.');
|
|
}
|
|
};
|
|
|
|
xhr.send('action=twp_get_elevenlabs_voices&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>');
|
|
}
|
|
|
|
function addVoicePreviewButtons(select, voices) {
|
|
// Remove existing preview container
|
|
var existingPreview = document.getElementById('voice-preview-container');
|
|
if (existingPreview) {
|
|
existingPreview.remove();
|
|
}
|
|
|
|
// Create preview container
|
|
var previewContainer = document.createElement('div');
|
|
previewContainer.id = 'voice-preview-container';
|
|
previewContainer.style.marginTop = '10px';
|
|
previewContainer.innerHTML = '<button type="button" class="button" onclick="previewSelectedVoice()">Preview Voice</button> <span id="preview-status"></span>';
|
|
|
|
select.parentNode.appendChild(previewContainer);
|
|
}
|
|
|
|
function previewSelectedVoice() {
|
|
var select = document.getElementById('elevenlabs-voice-select');
|
|
var voiceId = select.value;
|
|
var statusSpan = document.getElementById('preview-status');
|
|
|
|
if (!voiceId) {
|
|
alert('Please select a voice first.');
|
|
return;
|
|
}
|
|
|
|
statusSpan.textContent = 'Generating preview...';
|
|
|
|
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);
|
|
|
|
if (response.success) {
|
|
statusSpan.innerHTML = '<audio controls><source src="' + response.data.audio_url + '" type="audio/mpeg">Your browser does not support the audio element.</audio>';
|
|
} else {
|
|
statusSpan.textContent = 'Error: ' + response.data;
|
|
}
|
|
} catch (e) {
|
|
statusSpan.textContent = 'Failed to generate preview.';
|
|
}
|
|
};
|
|
|
|
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"]');
|
|
var voiceSelect = document.getElementById('elevenlabs-voice-select');
|
|
|
|
// Add change listener to maintain selection
|
|
if (voiceSelect) {
|
|
voiceSelect.addEventListener('change', function() {
|
|
this.setAttribute('data-current', this.value);
|
|
console.log('Voice selection changed to:', this.value);
|
|
});
|
|
}
|
|
|
|
if (apiKeyField && apiKeyField.value && voiceSelect) {
|
|
loadElevenLabsVoices();
|
|
}
|
|
});
|
|
</script>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display schedules page
|
|
*/
|
|
public function display_schedules_page() {
|
|
// Ensure database tables exist
|
|
TWP_Activator::ensure_tables_exist();
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Business Hours Schedules</h1>
|
|
<p>Define business hours that determine when different workflows are active. Schedules automatically switch between workflows based on time and day.</p>
|
|
<button class="button button-primary" onclick="openScheduleModal()">Add New Schedule</button>
|
|
|
|
<table class="wp-list-table widefat fixed striped" style="margin-top: 20px;">
|
|
<thead>
|
|
<tr>
|
|
<th>Schedule Name</th>
|
|
<th>Days</th>
|
|
<th>Business Hours</th>
|
|
<th>Holidays</th>
|
|
<th>Workflow</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$schedules = TWP_Scheduler::get_schedules();
|
|
foreach ($schedules as $schedule) {
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html($schedule->schedule_name); ?></td>
|
|
<td><?php echo esc_html(ucwords(str_replace(',', ', ', $schedule->days_of_week))); ?></td>
|
|
<td><?php echo esc_html($schedule->start_time . ' - ' . $schedule->end_time); ?></td>
|
|
<td>
|
|
<?php
|
|
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>None</em>';
|
|
}
|
|
?>
|
|
</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;
|
|
} else {
|
|
echo '<em>No specific workflow</em>';
|
|
}
|
|
?>
|
|
</td>
|
|
<td>
|
|
<span class="twp-status <?php echo $schedule->is_active ? 'active' : 'inactive'; ?>">
|
|
<?php echo $schedule->is_active ? 'Active' : 'Inactive'; ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="button" onclick="editSchedule(<?php echo $schedule->id; ?>)">Edit</button>
|
|
<button class="button" onclick="deleteSchedule(<?php echo $schedule->id; ?>)">Delete</button>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
if (empty($schedules)) {
|
|
echo '<tr><td colspan="7">No schedules found. <a href="#" onclick="openScheduleModal()">Create your first schedule</a>.</td></tr>';
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Schedule Modal -->
|
|
<div id="schedule-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<h2 id="schedule-modal-title">Add New Schedule</h2>
|
|
<form id="schedule-form">
|
|
<input type="hidden" id="schedule-id" name="schedule_id" value="">
|
|
|
|
<div class="form-field">
|
|
<label for="schedule-name">Schedule Name:</label>
|
|
<input type="text" id="schedule-name" name="schedule_name" required placeholder="e.g., Business Hours, Weekend Schedule">
|
|
<p class="description">Give this schedule a descriptive name</p>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="days-of-week">Days of Week:</label>
|
|
<div class="days-checkboxes">
|
|
<label><input type="checkbox" name="days_of_week[]" value="monday"> Monday</label>
|
|
<label><input type="checkbox" name="days_of_week[]" value="tuesday"> Tuesday</label>
|
|
<label><input type="checkbox" name="days_of_week[]" value="wednesday"> Wednesday</label>
|
|
<label><input type="checkbox" name="days_of_week[]" value="thursday"> Thursday</label>
|
|
<label><input type="checkbox" name="days_of_week[]" value="friday"> Friday</label>
|
|
<label><input type="checkbox" name="days_of_week[]" value="saturday"> Saturday</label>
|
|
<label><input type="checkbox" name="days_of_week[]" value="sunday"> Sunday</label>
|
|
</div>
|
|
<p class="description">Select the days when this schedule should be active</p>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="start-time">Business Hours Start:</label>
|
|
<input type="time" id="start-time" name="start_time" required>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="end-time">Business Hours End:</label>
|
|
<input type="time" id="end-time" name="end_time" required>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<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)) {
|
|
foreach ($workflows as $workflow) {
|
|
echo '<option value="' . $workflow->id . '">' . esc_html($workflow->workflow_name) . '</option>';
|
|
}
|
|
} else {
|
|
echo '<option value="" disabled>No workflows found - create a workflow first</option>';
|
|
}
|
|
?>
|
|
</select>
|
|
<p class="description">This workflow will handle calls during business hours</p>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="after-hours-action">After Hours Action:</label>
|
|
<select id="after-hours-action" name="after_hours_action" onchange="toggleAfterHoursFields(this)">
|
|
<option value="default">Use Default Workflow</option>
|
|
<option value="forward">Forward to Number</option>
|
|
<option value="workflow">Use Different Workflow</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="after-hours-forward" class="form-field" style="display: none;">
|
|
<label for="forward-number">Forward Number:</label>
|
|
<input type="text" id="forward-number" name="forward_number" placeholder="+1234567890">
|
|
<p class="description">Calls will be forwarded to this number after hours</p>
|
|
</div>
|
|
|
|
<div id="after-hours-workflow" class="form-field" style="display: none;">
|
|
<label for="after-hours-workflow-select">After Hours Workflow:</label>
|
|
<select id="after-hours-workflow-select" name="after_hours_workflow_id">
|
|
<option value="">Select a workflow...</option>
|
|
<?php
|
|
if ($workflows && is_array($workflows)) {
|
|
foreach ($workflows as $workflow) {
|
|
echo '<option value="' . $workflow->id . '">' . esc_html($workflow->workflow_name) . '</option>';
|
|
}
|
|
}
|
|
?>
|
|
</select>
|
|
<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
|
|
</label>
|
|
<p class="description">Uncheck to temporarily disable this schedule</p>
|
|
</div>
|
|
|
|
<div class="modal-buttons">
|
|
<button type="submit" class="button button-primary">Save Schedule</button>
|
|
<button type="button" class="button" onclick="closeScheduleModal()">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display workflows page
|
|
*/
|
|
public function display_workflows_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Call Workflows</h1>
|
|
<button class="button button-primary" onclick="openWorkflowBuilder()">Create New Workflow</button>
|
|
|
|
<table class="wp-list-table widefat fixed striped" style="margin-top: 20px;">
|
|
<thead>
|
|
<tr>
|
|
<th>Workflow Name</th>
|
|
<th>Phone Number</th>
|
|
<th>Steps</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$workflows = TWP_Workflow::get_workflows();
|
|
foreach ($workflows as $workflow) {
|
|
$workflow_data = json_decode($workflow->workflow_data, true);
|
|
$step_count = isset($workflow_data['steps']) ? count($workflow_data['steps']) : 0;
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html($workflow->workflow_name); ?></td>
|
|
<td><?php echo esc_html($workflow->phone_number); ?></td>
|
|
<td><?php echo $step_count; ?> steps</td>
|
|
<td>
|
|
<span class="twp-status <?php echo $workflow->is_active ? 'active' : 'inactive'; ?>">
|
|
<?php echo $workflow->is_active ? 'Active' : 'Inactive'; ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="button" onclick="editWorkflow(<?php echo $workflow->id; ?>)">Edit</button>
|
|
<button class="button" onclick="testWorkflow(<?php echo $workflow->id; ?>)">Test</button>
|
|
<button class="button" onclick="deleteWorkflow(<?php echo $workflow->id; ?>)">Delete</button>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Workflow Builder Modal -->
|
|
<div id="workflow-builder" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content large">
|
|
<h2 id="workflow-modal-title">Create New Workflow</h2>
|
|
|
|
<form id="workflow-basic-info">
|
|
<div class="workflow-info-grid">
|
|
<div>
|
|
<label>Workflow Name:</label>
|
|
<input type="text" id="workflow-name" name="workflow_name" required>
|
|
</div>
|
|
<div>
|
|
<label>Phone Number:</label>
|
|
<select id="workflow-phone" name="phone_number" required>
|
|
<option value="">Select a phone number...</option>
|
|
<!-- Will be populated via AJAX -->
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label>
|
|
<input type="checkbox" id="workflow-active" name="is_active" checked> Active
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="workflow-builder-container">
|
|
<div class="workflow-steps">
|
|
<h3>Workflow Steps</h3>
|
|
<div class="step-types-toolbar">
|
|
<button type="button" class="button step-btn" data-step-type="greeting">
|
|
<span class="dashicons dashicons-megaphone"></span> Greeting
|
|
</button>
|
|
<button type="button" class="button step-btn" data-step-type="ivr_menu">
|
|
<span class="dashicons dashicons-menu"></span> IVR Menu
|
|
</button>
|
|
<button type="button" class="button step-btn" data-step-type="forward">
|
|
<span class="dashicons dashicons-phone"></span> Forward
|
|
</button>
|
|
<button type="button" class="button step-btn" data-step-type="queue">
|
|
<span class="dashicons dashicons-groups"></span> Queue
|
|
</button>
|
|
<button type="button" class="button step-btn" data-step-type="voicemail">
|
|
<span class="dashicons dashicons-microphone"></span> Voicemail
|
|
</button>
|
|
<button type="button" class="button step-btn" data-step-type="schedule_check">
|
|
<span class="dashicons dashicons-clock"></span> Schedule
|
|
</button>
|
|
<button type="button" class="button step-btn" data-step-type="sms">
|
|
<span class="dashicons dashicons-email-alt"></span> SMS
|
|
</button>
|
|
</div>
|
|
|
|
<div id="workflow-steps-list" class="workflow-steps-container">
|
|
<!-- Steps will be added here -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="workflow-preview">
|
|
<h3>Call Flow Preview</h3>
|
|
<div id="workflow-preview-content" class="workflow-flow-chart">
|
|
<div class="flow-start">📞 Incoming Call</div>
|
|
<div id="flow-steps"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-buttons">
|
|
<button type="button" class="button button-primary" id="save-workflow-btn">Save Workflow</button>
|
|
<button type="button" class="button" onclick="closeWorkflowBuilder()">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step Configuration Modal -->
|
|
<div id="step-config-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<h2 id="step-config-title">Configure Step</h2>
|
|
<form id="step-config-form">
|
|
<input type="hidden" id="step-id" name="step_id">
|
|
<input type="hidden" id="step-type" name="step_type">
|
|
|
|
<div id="step-config-content">
|
|
<!-- Dynamic content based on step type -->
|
|
</div>
|
|
|
|
<div class="modal-buttons">
|
|
<button type="button" class="button button-primary" id="save-step-btn">Save Step</button>
|
|
<button type="button" class="button" onclick="closeStepConfigModal()">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display queues page
|
|
*/
|
|
public function display_queues_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Call Queues</h1>
|
|
<button class="button button-primary" onclick="openQueueModal()">Create New Queue</button>
|
|
|
|
<div class="twp-queue-grid" style="margin-top: 20px;">
|
|
<?php
|
|
global $wpdb;
|
|
$queue_table = $wpdb->prefix . 'twp_call_queues';
|
|
$queues = $wpdb->get_results("SELECT * FROM $queue_table");
|
|
|
|
foreach ($queues as $queue) {
|
|
$queue_status = TWP_Call_Queue::get_queue_status();
|
|
$waiting_calls = 0;
|
|
|
|
foreach ($queue_status as $status) {
|
|
if ($status['queue_id'] == $queue->id) {
|
|
$waiting_calls = $status['waiting_calls'];
|
|
break;
|
|
}
|
|
}
|
|
?>
|
|
<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>
|
|
</div>
|
|
<div class="stat">
|
|
<span class="label">Max Size:</span>
|
|
<span class="value"><?php echo $queue->max_size; ?></span>
|
|
</div>
|
|
<div class="stat">
|
|
<span class="label">Timeout:</span>
|
|
<span class="value"><?php echo $queue->timeout_seconds; ?>s</span>
|
|
</div>
|
|
</div>
|
|
<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
|
|
}
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Queue Modal -->
|
|
<div id="queue-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<h2>Create/Edit Queue</h2>
|
|
<form id="queue-form">
|
|
<input type="hidden" id="queue-id" name="queue_id" value="">
|
|
|
|
<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">
|
|
|
|
<label>Timeout (seconds):</label>
|
|
<input type="number" name="timeout_seconds" min="30" max="3600" value="300">
|
|
|
|
<label>Wait Music URL:</label>
|
|
<input type="url" name="wait_music_url">
|
|
|
|
<label>TTS Welcome Message:</label>
|
|
<textarea name="tts_message" rows="3"></textarea>
|
|
|
|
<div class="modal-buttons">
|
|
<button type="submit" class="button button-primary">Save</button>
|
|
<button type="button" class="button" onclick="closeQueueModal()">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display phone numbers page
|
|
*/
|
|
public function display_numbers_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Phone Numbers</h1>
|
|
|
|
<div class="twp-numbers-actions">
|
|
<button class="button button-primary" onclick="searchAvailableNumbers()">Buy New Number</button>
|
|
<button class="button" onclick="refreshNumbers()">Refresh</button>
|
|
</div>
|
|
|
|
<h2>Your Twilio Phone Numbers</h2>
|
|
<div id="twp-numbers-list">
|
|
<div class="twp-spinner"></div>
|
|
<p>Loading phone numbers...</p>
|
|
</div>
|
|
|
|
<h2>Available Numbers for Purchase</h2>
|
|
<div id="twp-available-numbers" style="display: none;">
|
|
<div class="twp-search-form">
|
|
<label>Country:</label>
|
|
<select id="country-code">
|
|
<option value="US">United States</option>
|
|
<option value="CA">Canada</option>
|
|
<option value="GB">United Kingdom</option>
|
|
<option value="AU">Australia</option>
|
|
</select>
|
|
|
|
<label>Area Code:</label>
|
|
<input type="text" id="area-code" placeholder="Optional">
|
|
|
|
<label>Contains:</label>
|
|
<input type="text" id="contains" placeholder="Optional">
|
|
|
|
<button class="button" onclick="searchNumbers()">Search</button>
|
|
</div>
|
|
|
|
<div id="search-results"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Number Configuration Modal -->
|
|
<div id="number-config-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<h2>Configure Phone Number</h2>
|
|
<form id="number-config-form">
|
|
<input type="hidden" id="number-sid" name="number_sid" value="">
|
|
|
|
<label>Phone Number:</label>
|
|
<input type="text" id="phone-number" readonly>
|
|
|
|
<label>Voice URL:</label>
|
|
<select name="voice_url">
|
|
<option value="">Select a workflow or schedule...</option>
|
|
<optgroup label="Workflows">
|
|
<?php
|
|
$workflows = TWP_Workflow::get_workflows();
|
|
foreach ($workflows as $workflow) {
|
|
$webhook_url = rest_url('twilio-webhook/v1/voice');
|
|
$webhook_url = add_query_arg('workflow_id', $workflow->id, $webhook_url);
|
|
echo '<option value="' . esc_url($webhook_url) . '">' . esc_html($workflow->workflow_name) . '</option>';
|
|
}
|
|
?>
|
|
</optgroup>
|
|
<optgroup label="Schedules">
|
|
<?php
|
|
$schedules = TWP_Scheduler::get_schedules();
|
|
foreach ($schedules as $schedule) {
|
|
$webhook_url = rest_url('twilio-webhook/v1/voice');
|
|
$webhook_url = add_query_arg('schedule_id', $schedule->id, $webhook_url);
|
|
echo '<option value="' . esc_url($webhook_url) . '">' . esc_html($schedule->schedule_name) . '</option>';
|
|
}
|
|
?>
|
|
</optgroup>
|
|
</select>
|
|
|
|
<label>SMS URL:</label>
|
|
<input type="url" name="sms_url" value="<?php echo rest_url('twilio-webhook/v1/sms'); ?>">
|
|
|
|
<div class="modal-buttons">
|
|
<button type="submit" class="button button-primary">Save</button>
|
|
<button type="button" class="button" onclick="closeNumberConfigModal()">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display voicemails page
|
|
*/
|
|
public function display_voicemails_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Voicemails</h1>
|
|
|
|
<div class="twp-voicemail-filters">
|
|
<label>Filter by workflow:</label>
|
|
<select id="voicemail-workflow-filter">
|
|
<option value="">All workflows</option>
|
|
<?php
|
|
$workflows = TWP_Workflow::get_workflows();
|
|
foreach ($workflows as $workflow) {
|
|
echo '<option value="' . $workflow->id . '">' . esc_html($workflow->workflow_name) . '</option>';
|
|
}
|
|
?>
|
|
</select>
|
|
|
|
<label>Date range:</label>
|
|
<input type="date" id="voicemail-date-from" />
|
|
<input type="date" id="voicemail-date-to" />
|
|
|
|
<button class="button" onclick="filterVoicemails()">Filter</button>
|
|
<button class="button" onclick="exportVoicemails()">Export</button>
|
|
</div>
|
|
|
|
<div class="twp-voicemail-stats">
|
|
<div class="stat-card">
|
|
<h3>Total Voicemails</h3>
|
|
<div class="stat-value" id="total-voicemails">
|
|
<?php
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . 'twp_voicemails';
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table");
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>Today</h3>
|
|
<div class="stat-value" id="today-voicemails">
|
|
<?php
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE DATE(created_at) = CURDATE()");
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>This Week</h3>
|
|
<div class="stat-value" id="week-voicemails">
|
|
<?php
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE YEARWEEK(created_at) = YEARWEEK(NOW())");
|
|
?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="wp-list-table widefat fixed striped" id="voicemails-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>From Number</th>
|
|
<th>Workflow</th>
|
|
<th>Duration</th>
|
|
<th>Transcription</th>
|
|
<th>Recording</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php $this->display_voicemails_table(); ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Voicemail Player Modal -->
|
|
<div id="voicemail-player-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<h2 id="voicemail-modal-title">Voicemail Player</h2>
|
|
|
|
<div class="voicemail-details">
|
|
<div class="detail-row">
|
|
<span class="label">From:</span>
|
|
<span id="voicemail-from"></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="label">Date:</span>
|
|
<span id="voicemail-date"></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="label">Duration:</span>
|
|
<span id="voicemail-duration"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="voicemail-player">
|
|
<audio id="voicemail-audio" controls style="width: 100%; margin: 20px 0;">
|
|
Your browser does not support the audio element.
|
|
</audio>
|
|
</div>
|
|
|
|
<div class="voicemail-transcription">
|
|
<h4>Transcription:</h4>
|
|
<div id="voicemail-transcription-text">
|
|
<em>No transcription available</em>
|
|
</div>
|
|
<button class="button" onclick="transcribeVoicemail()" id="transcribe-btn">Generate Transcription</button>
|
|
</div>
|
|
|
|
<div class="voicemail-actions">
|
|
<button class="button" onclick="downloadVoicemail()">Download</button>
|
|
<button class="button button-danger" onclick="deleteVoicemail()">Delete</button>
|
|
<button class="button" onclick="closeVoicemailModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display call logs page
|
|
*/
|
|
public function display_call_logs_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Call Logs</h1>
|
|
|
|
<div class="twp-call-log-filters">
|
|
<label>Phone Number:</label>
|
|
<select id="call-log-phone-filter">
|
|
<option value="">All numbers</option>
|
|
<?php
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . 'twp_call_log';
|
|
$numbers = $wpdb->get_results("SELECT DISTINCT from_number FROM $table WHERE from_number != '' ORDER BY from_number");
|
|
foreach ($numbers as $number) {
|
|
echo '<option value="' . esc_attr($number->from_number) . '">' . esc_html($number->from_number) . '</option>';
|
|
}
|
|
?>
|
|
</select>
|
|
|
|
<label>Status:</label>
|
|
<select id="call-log-status-filter">
|
|
<option value="">All statuses</option>
|
|
<option value="initiated">Initiated</option>
|
|
<option value="ringing">Ringing</option>
|
|
<option value="answered">Answered</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="busy">Busy</option>
|
|
<option value="failed">Failed</option>
|
|
<option value="no-answer">No Answer</option>
|
|
</select>
|
|
|
|
<label>Date range:</label>
|
|
<input type="date" id="call-log-date-from" />
|
|
<input type="date" id="call-log-date-to" />
|
|
|
|
<button class="button" onclick="filterCallLogs()">Filter</button>
|
|
<button class="button" onclick="exportCallLogs()">Export</button>
|
|
</div>
|
|
|
|
<div class="twp-call-log-stats">
|
|
<div class="stat-card">
|
|
<h3>Total Calls</h3>
|
|
<div class="stat-value">
|
|
<?php
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table");
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>Today</h3>
|
|
<div class="stat-value">
|
|
<?php
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE DATE(created_at) = CURDATE()");
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>Answered</h3>
|
|
<div class="stat-value">
|
|
<?php
|
|
echo $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status = 'completed' AND duration > 0");
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>Avg Duration</h3>
|
|
<div class="stat-value">
|
|
<?php
|
|
$avg = $wpdb->get_var("SELECT AVG(duration) FROM $table WHERE duration > 0");
|
|
echo $avg ? round($avg) . 's' : '0s';
|
|
?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="wp-list-table widefat fixed striped" id="call-logs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>From Number</th>
|
|
<th>To Number</th>
|
|
<th>Status</th>
|
|
<th>Duration</th>
|
|
<th>Workflow</th>
|
|
<th>Queue Time</th>
|
|
<th>Actions Taken</th>
|
|
<th>Details</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php $this->display_call_logs_table(); ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Call Detail Modal -->
|
|
<div id="call-detail-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<h2 id="call-detail-title">Call Details</h2>
|
|
|
|
<div class="call-timeline">
|
|
<h4>Call Timeline:</h4>
|
|
<div id="call-timeline-content">
|
|
<!-- Timeline will be populated here -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="call-details-grid">
|
|
<div class="detail-section">
|
|
<h4>Call Information</h4>
|
|
<div id="call-basic-info"></div>
|
|
</div>
|
|
|
|
<div class="detail-section">
|
|
<h4>Actions Taken</h4>
|
|
<div id="call-actions-taken"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-buttons">
|
|
<button class="button" onclick="closeCallDetailModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display agent groups page
|
|
*/
|
|
public function display_groups_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Agent Groups <button class="button button-primary" onclick="openGroupModal()">Add New Group</button></h1>
|
|
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Group Name</th>
|
|
<th>Description</th>
|
|
<th>Members</th>
|
|
<th>Ring Strategy</th>
|
|
<th>Timeout</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="groups-list">
|
|
<?php
|
|
$groups = TWP_Agent_Groups::get_all_groups();
|
|
foreach ($groups as $group) {
|
|
$members = TWP_Agent_Groups::get_group_members($group->id);
|
|
$member_count = count($members);
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html($group->group_name); ?></td>
|
|
<td><?php echo esc_html($group->description); ?></td>
|
|
<td><?php echo $member_count; ?> members</td>
|
|
<td><?php echo esc_html($group->ring_strategy); ?></td>
|
|
<td><?php echo esc_html($group->timeout_seconds); ?>s</td>
|
|
<td>
|
|
<button class="button" onclick="editGroup(<?php echo $group->id; ?>)">Edit</button>
|
|
<button class="button" onclick="manageGroupMembers(<?php echo $group->id; ?>)">Members</button>
|
|
<button class="button" onclick="deleteGroup(<?php echo $group->id; ?>)">Delete</button>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Group Modal -->
|
|
<div id="group-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content">
|
|
<div class="twp-modal-header">
|
|
<h2 id="group-modal-title">Add New Group</h2>
|
|
<button class="twp-modal-close" onclick="closeGroupModal()">×</button>
|
|
</div>
|
|
<div class="twp-modal-body">
|
|
<form id="group-form">
|
|
<input type="hidden" id="group-id" name="group_id" value="">
|
|
|
|
<label>Group Name:</label>
|
|
<input type="text" name="group_name" required class="regular-text">
|
|
|
|
<label>Description:</label>
|
|
<textarea name="description" rows="3" class="regular-text"></textarea>
|
|
|
|
<label>Ring Strategy:</label>
|
|
<select name="ring_strategy">
|
|
<option value="simultaneous">Simultaneous (ring all at once)</option>
|
|
<option value="sequential">Sequential (ring in order)</option>
|
|
</select>
|
|
|
|
<label>Timeout (seconds):</label>
|
|
<input type="number" name="timeout_seconds" value="30" min="5" max="120">
|
|
</form>
|
|
</div>
|
|
<div class="twp-modal-footer">
|
|
<button class="button button-primary" onclick="saveGroup()">Save Group</button>
|
|
<button class="button" onclick="closeGroupModal()">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Members Modal -->
|
|
<div id="members-modal" class="twp-modal" style="display: none;">
|
|
<div class="twp-modal-content" style="max-width: 800px;">
|
|
<div class="twp-modal-header">
|
|
<h2 id="members-modal-title">Manage Group Members</h2>
|
|
<button class="twp-modal-close" onclick="closeMembersModal()">×</button>
|
|
</div>
|
|
<div class="twp-modal-body">
|
|
<input type="hidden" id="current-group-id" value="">
|
|
|
|
<div class="add-member-section">
|
|
<h3>Add Member</h3>
|
|
<select id="add-member-select">
|
|
<option value="">Select a user...</option>
|
|
<?php
|
|
$users = get_users(array('orderby' => 'display_name'));
|
|
foreach ($users as $user) {
|
|
$phone = get_user_meta($user->ID, 'twp_phone_number', true);
|
|
?>
|
|
<option value="<?php echo $user->ID; ?>">
|
|
<?php echo esc_html($user->display_name); ?>
|
|
<?php echo $phone ? '(' . esc_html($phone) . ')' : '(no phone)'; ?>
|
|
</option>
|
|
<?php
|
|
}
|
|
?>
|
|
</select>
|
|
<input type="number" id="add-member-priority" placeholder="Priority" value="0" min="0">
|
|
<button class="button" onclick="addGroupMember()">Add Member</button>
|
|
</div>
|
|
|
|
<h3>Current Members</h3>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Phone Number</th>
|
|
<th>Priority</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="group-members-list">
|
|
<!-- Populated by JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="twp-modal-footer">
|
|
<button class="button" onclick="closeMembersModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display agent queue page
|
|
*/
|
|
public function display_agent_queue_page() {
|
|
$current_user_id = get_current_user_id();
|
|
$agent_status = TWP_Agent_Manager::get_agent_status($current_user_id);
|
|
$agent_stats = TWP_Agent_Manager::get_agent_stats($current_user_id);
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Agent Queue Dashboard</h1>
|
|
|
|
<div class="agent-status-bar">
|
|
<div class="status-info">
|
|
<strong>Your Status:</strong>
|
|
<select id="agent-status-select" onchange="updateAgentStatus(this.value)">
|
|
<option value="available" <?php selected($agent_status->status ?? '', 'available'); ?>>Available</option>
|
|
<option value="busy" <?php selected($agent_status->status ?? '', 'busy'); ?>>Busy</option>
|
|
<option value="offline" <?php selected($agent_status->status ?? 'offline', 'offline'); ?>>Offline</option>
|
|
</select>
|
|
</div>
|
|
<div class="agent-stats">
|
|
<span>Calls Today: <strong><?php echo $agent_stats['calls_today']; ?></strong></span>
|
|
<span>Total Calls: <strong><?php echo $agent_stats['total_calls']; ?></strong></span>
|
|
<span>Avg Duration: <strong><?php echo round($agent_stats['avg_duration'] ?? 0); ?>s</strong></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="queue-section">
|
|
<h2>Waiting Calls</h2>
|
|
<div id="waiting-calls-container">
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Position</th>
|
|
<th>Queue</th>
|
|
<th>From Number</th>
|
|
<th>Wait Time</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="waiting-calls-list">
|
|
<tr><td colspan="5">Loading...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-groups-section">
|
|
<h2>My Groups</h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Group Name</th>
|
|
<th>Members</th>
|
|
<th>Your Priority</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$my_groups = TWP_Agent_Groups::get_user_groups($current_user_id);
|
|
foreach ($my_groups as $group) {
|
|
$members = TWP_Agent_Groups::get_group_members($group->id);
|
|
$my_priority = 0;
|
|
foreach ($members as $member) {
|
|
if ($member->user_id == $current_user_id) {
|
|
$my_priority = $member->priority;
|
|
break;
|
|
}
|
|
}
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html($group->group_name); ?></td>
|
|
<td><?php echo count($members); ?> members</td>
|
|
<td><?php echo $my_priority; ?></td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.agent-status-bar {
|
|
background: #fff;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ccc;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.agent-stats span {
|
|
margin-left: 20px;
|
|
}
|
|
.queue-section, .my-groups-section {
|
|
background: #fff;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ccc;
|
|
}
|
|
#waiting-calls-list .accept-btn {
|
|
background: #4CAF50;
|
|
color: white;
|
|
border: none;
|
|
padding: 5px 15px;
|
|
cursor: pointer;
|
|
border-radius: 3px;
|
|
}
|
|
#waiting-calls-list .accept-btn:hover {
|
|
background: #45a049;
|
|
}
|
|
#waiting-calls-list .accept-btn:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
</style>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display outbound calls page
|
|
*/
|
|
public function display_outbound_calls_page() {
|
|
// Ensure database tables exist
|
|
TWP_Activator::ensure_tables_exist();
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Outbound Calls</h1>
|
|
<p>Initiate outbound calls to connect customers with your phone. Click-to-call functionality allows you to dial any number.</p>
|
|
|
|
<div class="outbound-call-section">
|
|
<h2>Make an Outbound Call</h2>
|
|
<div class="call-form">
|
|
<div class="form-field">
|
|
<label for="from-number">From Number:</label>
|
|
<select id="from-number" name="from_number" required>
|
|
<option value="">Select a number...</option>
|
|
<?php
|
|
// 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) {
|
|
echo '<option value="' . esc_attr($number['phone_number']) . '">' . esc_html($number['phone_number']) . '</option>';
|
|
}
|
|
} else {
|
|
echo '<option value="" disabled>No phone numbers found - purchase a number first</option>';
|
|
}
|
|
} else {
|
|
echo '<option value="" disabled>Error loading phone numbers - check API credentials</option>';
|
|
if (isset($numbers_result['error'])) {
|
|
echo '<option value="" disabled>Error: ' . esc_html($numbers_result['error']) . '</option>';
|
|
}
|
|
// Debug info for troubleshooting
|
|
if (current_user_can('manage_options') && WP_DEBUG) {
|
|
echo '<option value="" disabled>Debug: ' . esc_html(json_encode($numbers_result)) . '</option>';
|
|
}
|
|
}
|
|
?>
|
|
</select>
|
|
<p class="description">Select the Twilio number to call from</p>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="to-number">To Number:</label>
|
|
<input type="tel" id="to-number" name="to_number" placeholder="+1234567890" required>
|
|
<p class="description">Enter the number you want to call (include country code)</p>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="agent-phone">Your Phone Number:</label>
|
|
<input type="tel" id="agent-phone" name="agent_phone"
|
|
value="<?php echo esc_attr(get_user_meta(get_current_user_id(), 'twp_phone_number', true)); ?>"
|
|
placeholder="+1234567890" required>
|
|
<p class="description">The number where you'll receive the call first</p>
|
|
</div>
|
|
|
|
<button type="button" class="button button-primary" onclick="initiateOutboundCall()">
|
|
Place Call
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="recent-calls-section">
|
|
<h2>Recent Outbound Calls</h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>From</th>
|
|
<th>To</th>
|
|
<th>Agent</th>
|
|
<th>Status</th>
|
|
<th>Duration</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="recent-outbound-calls">
|
|
<?php
|
|
// Get recent outbound calls from log
|
|
global $wpdb;
|
|
$log_table = $wpdb->prefix . 'twp_call_log';
|
|
|
|
$recent_calls = $wpdb->get_results($wpdb->prepare("
|
|
SELECT cl.*, u.display_name as agent_name
|
|
FROM $log_table cl
|
|
LEFT JOIN {$wpdb->users} u ON JSON_EXTRACT(cl.actions_taken, '$.agent_id') = u.ID
|
|
WHERE cl.workflow_name = 'Outbound Call'
|
|
OR cl.status = 'outbound_initiated'
|
|
ORDER BY cl.created_at DESC
|
|
LIMIT 20
|
|
"));
|
|
|
|
if (empty($recent_calls)) {
|
|
echo '<tr><td colspan="6">No outbound calls yet</td></tr>';
|
|
} else {
|
|
foreach ($recent_calls as $call) {
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html(date('M j, Y g:i A', strtotime($call->created_at))); ?></td>
|
|
<td><?php echo esc_html($call->from_number ?: 'N/A'); ?></td>
|
|
<td><?php echo esc_html($call->to_number ?: 'N/A'); ?></td>
|
|
<td><?php echo esc_html($call->agent_name ?: 'N/A'); ?></td>
|
|
<td>
|
|
<span class="status-<?php echo esc_attr($call->status); ?>">
|
|
<?php echo esc_html(ucwords(str_replace('_', ' ', $call->status))); ?>
|
|
</span>
|
|
</td>
|
|
<td><?php echo $call->duration ? esc_html($call->duration . 's') : 'N/A'; ?></td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.outbound-call-section {
|
|
background: #fff;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ccc;
|
|
}
|
|
.call-form .form-field {
|
|
margin-bottom: 15px;
|
|
}
|
|
.call-form label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
}
|
|
.call-form input, .call-form select {
|
|
width: 300px;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 3px;
|
|
}
|
|
.call-form .description {
|
|
margin-top: 5px;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
.recent-calls-section {
|
|
background: #fff;
|
|
padding: 20px;
|
|
border: 1px solid #ccc;
|
|
}
|
|
.status-completed { color: #4CAF50; }
|
|
.status-outbound_initiated { color: #2196F3; }
|
|
.status-busy, .status-failed { color: #f44336; }
|
|
.status-no-answer { color: #ff9800; }
|
|
</style>
|
|
|
|
<script>
|
|
function initiateOutboundCall() {
|
|
const fromNumber = document.getElementById('from-number').value;
|
|
const toNumber = document.getElementById('to-number').value;
|
|
const agentPhone = document.getElementById('agent-phone').value;
|
|
|
|
if (!fromNumber || !toNumber || !agentPhone) {
|
|
alert('Please fill in all fields');
|
|
return;
|
|
}
|
|
|
|
// Validate phone number format
|
|
const phoneRegex = /^\+?[1-9]\d{1,14}$/;
|
|
if (!phoneRegex.test(toNumber.replace(/[\s\-\(\)]/g, ''))) {
|
|
alert('Please enter a valid phone number with country code (e.g., +1234567890)');
|
|
return;
|
|
}
|
|
|
|
const button = event.target;
|
|
button.disabled = true;
|
|
button.textContent = 'Placing Call...';
|
|
|
|
jQuery.post(twp_ajax.ajax_url, {
|
|
action: 'twp_initiate_outbound_call_with_from',
|
|
from_number: fromNumber,
|
|
to_number: toNumber,
|
|
agent_phone: agentPhone,
|
|
nonce: twp_ajax.nonce
|
|
}, function(response) {
|
|
if (response.success) {
|
|
alert('Call initiated! You should receive a call on ' + agentPhone + ' shortly, then the call will connect to ' + toNumber);
|
|
// Clear form
|
|
document.getElementById('to-number').value = '';
|
|
// Refresh recent calls (you could implement this)
|
|
} else {
|
|
alert('Error initiating call: ' + (response.data.message || response.data || 'Unknown error'));
|
|
}
|
|
}).fail(function() {
|
|
alert('Failed to initiate call. Please try again.');
|
|
}).always(function() {
|
|
button.disabled = false;
|
|
button.textContent = 'Place Call';
|
|
});
|
|
}
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Display voicemails table content
|
|
*/
|
|
private function display_voicemails_table() {
|
|
global $wpdb;
|
|
$voicemails_table = $wpdb->prefix . 'twp_voicemails';
|
|
$workflows_table = $wpdb->prefix . 'twp_workflows';
|
|
|
|
$voicemails = $wpdb->get_results("
|
|
SELECT v.*, w.workflow_name
|
|
FROM $voicemails_table v
|
|
LEFT JOIN $workflows_table w ON v.workflow_id = w.id
|
|
ORDER BY v.created_at DESC
|
|
LIMIT 50
|
|
");
|
|
|
|
foreach ($voicemails as $voicemail) {
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html(date('M j, Y g:i A', strtotime($voicemail->created_at))); ?></td>
|
|
<td><?php echo esc_html($voicemail->from_number); ?></td>
|
|
<td><?php echo esc_html($voicemail->workflow_name ?: 'N/A'); ?></td>
|
|
<td><?php echo $voicemail->duration ? esc_html($voicemail->duration . 's') : 'Unknown'; ?></td>
|
|
<td>
|
|
<?php if ($voicemail->transcription): ?>
|
|
<span class="transcription-preview" title="<?php echo esc_attr($voicemail->transcription); ?>">
|
|
<?php echo esc_html(substr($voicemail->transcription, 0, 50) . '...'); ?>
|
|
</span>
|
|
<?php else: ?>
|
|
<em>No transcription</em>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<?php if ($voicemail->recording_url): ?>
|
|
<button class="button button-small"
|
|
onclick="playVoicemail(<?php echo $voicemail->id; ?>, '<?php echo esc_js($voicemail->recording_url); ?>')">
|
|
Play
|
|
</button>
|
|
<?php else: ?>
|
|
<em>No recording</em>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<button class="button button-small" onclick="viewVoicemail(<?php echo $voicemail->id; ?>)">View</button>
|
|
<button class="button button-small button-danger" onclick="deleteVoicemailConfirm(<?php echo $voicemail->id; ?>)">Delete</button>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
|
|
if (empty($voicemails)) {
|
|
echo '<tr><td colspan="7">No voicemails found.</td></tr>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display call logs table content
|
|
*/
|
|
private function display_call_logs_table() {
|
|
global $wpdb;
|
|
$logs_table = $wpdb->prefix . 'twp_call_log';
|
|
|
|
$logs = $wpdb->get_results("
|
|
SELECT *
|
|
FROM $logs_table
|
|
ORDER BY created_at DESC
|
|
LIMIT 100
|
|
");
|
|
|
|
foreach ($logs as $log) {
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html(date('M j, Y g:i A', strtotime($log->created_at))); ?></td>
|
|
<td><?php echo esc_html($log->from_number ?: 'Unknown'); ?></td>
|
|
<td><?php echo esc_html($log->to_number ?: 'System'); ?></td>
|
|
<td>
|
|
<span class="status-badge status-<?php echo esc_attr(strtolower($log->status)); ?>">
|
|
<?php echo esc_html(ucfirst($log->status)); ?>
|
|
</span>
|
|
</td>
|
|
<td><?php echo $log->duration ? esc_html($log->duration . 's') : '-'; ?></td>
|
|
<td><?php echo esc_html($log->workflow_name ?: 'N/A'); ?></td>
|
|
<td><?php echo $log->queue_time ? esc_html($log->queue_time . 's') : '-'; ?></td>
|
|
<td><?php echo esc_html($log->actions_taken ?: 'None'); ?></td>
|
|
<td>
|
|
<button class="button button-small" onclick="viewCallDetails('<?php echo esc_js($log->call_sid); ?>')">
|
|
View
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
|
|
if (empty($logs)) {
|
|
echo '<tr><td colspan="9">No call logs found.</td></tr>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show admin notices
|
|
*/
|
|
public function show_admin_notices() {
|
|
// Check if we're on a plugin page
|
|
$screen = get_current_screen();
|
|
if (!$screen || strpos($screen->id, 'twilio-wp') === false) {
|
|
return;
|
|
}
|
|
|
|
// Check if database tables exist
|
|
require_once TWP_PLUGIN_DIR . 'includes/class-twp-activator.php';
|
|
$tables_exist = TWP_Activator::ensure_tables_exist();
|
|
|
|
if (!$tables_exist) {
|
|
?>
|
|
<div class="notice notice-warning is-dismissible">
|
|
<p>
|
|
<strong>Twilio WP Plugin:</strong> Database tables were missing and have been created automatically.
|
|
If you continue to experience issues, please deactivate and reactivate the plugin.
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
// Check if ElevenLabs API key is configured
|
|
if (empty(get_option('twp_elevenlabs_api_key'))) {
|
|
?>
|
|
<div class="notice notice-info is-dismissible">
|
|
<p>
|
|
<strong>Twilio WP Plugin:</strong> To use text-to-speech features, please configure your
|
|
<a href="<?php echo admin_url('admin.php?page=twilio-wp-settings'); ?>">ElevenLabs API key</a>.
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
// Check if Twilio credentials are configured
|
|
if (empty(get_option('twp_twilio_account_sid')) || empty(get_option('twp_twilio_auth_token'))) {
|
|
?>
|
|
<div class="notice notice-error">
|
|
<p>
|
|
<strong>Twilio WP Plugin:</strong> Please configure your
|
|
<a href="<?php echo admin_url('admin.php?page=twilio-wp-settings'); ?>">Twilio credentials</a>
|
|
to start using the plugin.
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register settings
|
|
*/
|
|
public function register_settings() {
|
|
register_setting('twilio-wp-settings-group', 'twp_twilio_account_sid');
|
|
register_setting('twilio-wp-settings-group', 'twp_twilio_auth_token');
|
|
register_setting('twilio-wp-settings-group', 'twp_elevenlabs_api_key');
|
|
register_setting('twilio-wp-settings-group', 'twp_elevenlabs_voice_id');
|
|
register_setting('twilio-wp-settings-group', 'twp_elevenlabs_model_id');
|
|
register_setting('twilio-wp-settings-group', 'twp_default_queue_timeout');
|
|
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('thickbox'),
|
|
$this->version,
|
|
'all'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Enqueue scripts
|
|
*/
|
|
public function enqueue_scripts() {
|
|
// Enqueue ThickBox for WordPress native modals
|
|
wp_enqueue_script('thickbox');
|
|
|
|
wp_enqueue_script(
|
|
$this->plugin_name,
|
|
TWP_PLUGIN_URL . 'assets/js/admin.js',
|
|
array('jquery', 'thickbox'),
|
|
$this->version,
|
|
false
|
|
);
|
|
|
|
wp_localize_script(
|
|
$this->plugin_name,
|
|
'twp_ajax',
|
|
array(
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('twp_ajax_nonce'),
|
|
'rest_url' => rest_url(),
|
|
'has_elevenlabs_key' => !empty(get_option('twp_elevenlabs_api_key')),
|
|
'timezone' => wp_timezone_string()
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for saving schedule
|
|
*/
|
|
public function ajax_save_schedule() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
// Debug logging - log incoming POST data
|
|
error_log('TWP Schedule Save: POST data: ' . print_r($_POST, true));
|
|
|
|
$schedule_id = isset($_POST['schedule_id']) ? intval($_POST['schedule_id']) : 0;
|
|
|
|
// Remove duplicate days and sanitize
|
|
$days_of_week = isset($_POST['days_of_week']) ? $_POST['days_of_week'] : array();
|
|
$unique_days = array_unique(array_map('sanitize_text_field', $days_of_week));
|
|
|
|
$data = array(
|
|
'schedule_name' => sanitize_text_field($_POST['schedule_name']),
|
|
'days_of_week' => implode(',', $unique_days),
|
|
'start_time' => sanitize_text_field($_POST['start_time']),
|
|
'end_time' => sanitize_text_field($_POST['end_time']),
|
|
'workflow_id' => isset($_POST['workflow_id']) && !empty($_POST['workflow_id']) ? intval($_POST['workflow_id']) : null,
|
|
'holiday_dates' => isset($_POST['holiday_dates']) ? sanitize_textarea_field($_POST['holiday_dates']) : '',
|
|
'is_active' => isset($_POST['is_active']) ? 1 : 0
|
|
);
|
|
|
|
// Add optional fields if provided
|
|
if (!empty($_POST['phone_number'])) {
|
|
$data['phone_number'] = sanitize_text_field($_POST['phone_number']);
|
|
}
|
|
|
|
if (!empty($_POST['forward_number'])) {
|
|
$data['forward_number'] = sanitize_text_field($_POST['forward_number']);
|
|
}
|
|
|
|
if (!empty($_POST['after_hours_action'])) {
|
|
$data['after_hours_action'] = sanitize_text_field($_POST['after_hours_action']);
|
|
}
|
|
|
|
if (!empty($_POST['after_hours_workflow_id'])) {
|
|
$data['after_hours_workflow_id'] = intval($_POST['after_hours_workflow_id']);
|
|
}
|
|
|
|
if (!empty($_POST['after_hours_forward_number'])) {
|
|
$data['after_hours_forward_number'] = sanitize_text_field($_POST['after_hours_forward_number']);
|
|
}
|
|
|
|
// Debug logging - log processed data
|
|
error_log('TWP Schedule Save: Processed data: ' . print_r($data, true));
|
|
error_log('TWP Schedule Save: Schedule ID: ' . $schedule_id);
|
|
|
|
if ($schedule_id) {
|
|
error_log('TWP Schedule Save: Updating existing schedule');
|
|
$result = TWP_Scheduler::update_schedule($schedule_id, $data);
|
|
} else {
|
|
error_log('TWP Schedule Save: Creating new schedule');
|
|
$result = TWP_Scheduler::create_schedule($data);
|
|
}
|
|
|
|
error_log('TWP Schedule Save: Result: ' . ($result ? 'true' : 'false'));
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for deleting schedule
|
|
*/
|
|
public function ajax_delete_schedule() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$schedule_id = intval($_POST['schedule_id']);
|
|
$result = TWP_Scheduler::delete_schedule($schedule_id);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting all schedules
|
|
*/
|
|
public function ajax_get_schedules() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$schedules = TWP_Scheduler::get_schedules();
|
|
wp_send_json_success($schedules);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting a single schedule
|
|
*/
|
|
public function ajax_get_schedule() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$schedule_id = intval($_POST['schedule_id']);
|
|
$schedule = TWP_Scheduler::get_schedule($schedule_id);
|
|
|
|
if ($schedule) {
|
|
wp_send_json_success($schedule);
|
|
} else {
|
|
wp_send_json_error('Schedule not found');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for saving workflow
|
|
*/
|
|
public function ajax_save_workflow() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$workflow_id = isset($_POST['workflow_id']) ? intval($_POST['workflow_id']) : 0;
|
|
|
|
// Parse the workflow data JSON
|
|
$workflow_data_json = isset($_POST['workflow_data']) ? stripslashes($_POST['workflow_data']) : '{}';
|
|
|
|
// Log for debugging
|
|
error_log('TWP Workflow Save - Raw data: ' . $workflow_data_json);
|
|
|
|
// Handle empty workflow data
|
|
if (empty($workflow_data_json) || $workflow_data_json === '{}') {
|
|
$workflow_data_parsed = array(
|
|
'steps' => array(),
|
|
'conditions' => array(),
|
|
'actions' => array()
|
|
);
|
|
} else {
|
|
$workflow_data_parsed = json_decode($workflow_data_json, true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
error_log('TWP Workflow Save - JSON Error: ' . json_last_error_msg());
|
|
wp_send_json_error('Invalid workflow data format: ' . json_last_error_msg());
|
|
return;
|
|
}
|
|
}
|
|
|
|
$data = array(
|
|
'workflow_name' => sanitize_text_field($_POST['workflow_name']),
|
|
'phone_number' => sanitize_text_field($_POST['phone_number']),
|
|
'steps' => isset($workflow_data_parsed['steps']) ? $workflow_data_parsed['steps'] : array(),
|
|
'conditions' => isset($workflow_data_parsed['conditions']) ? $workflow_data_parsed['conditions'] : array(),
|
|
'actions' => isset($workflow_data_parsed['actions']) ? $workflow_data_parsed['actions'] : array(),
|
|
'is_active' => isset($_POST['is_active']) ? intval($_POST['is_active']) : 0,
|
|
'workflow_data' => $workflow_data_json // Keep the raw JSON for update_workflow
|
|
);
|
|
|
|
if ($workflow_id) {
|
|
$result = TWP_Workflow::update_workflow($workflow_id, $data);
|
|
} else {
|
|
$result = TWP_Workflow::create_workflow($data);
|
|
}
|
|
|
|
if ($result === false) {
|
|
wp_send_json_error('Failed to save workflow to database');
|
|
} else {
|
|
global $wpdb;
|
|
wp_send_json_success(array('success' => true, 'workflow_id' => $workflow_id ?: $wpdb->insert_id));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting workflow
|
|
*/
|
|
public function ajax_get_workflow() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$workflow_id = intval($_POST['workflow_id']);
|
|
$workflow = TWP_Workflow::get_workflow($workflow_id);
|
|
|
|
wp_send_json_success($workflow);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for deleting workflow
|
|
*/
|
|
public function ajax_delete_workflow() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$workflow_id = intval($_POST['workflow_id']);
|
|
$result = TWP_Workflow::delete_workflow($workflow_id);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for test call
|
|
*/
|
|
public function ajax_test_call() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$to_number = sanitize_text_field($_POST['to_number']);
|
|
$workflow_id = intval($_POST['workflow_id']);
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
$twiml_url = home_url('/wp-json/twilio-webhook/v1/voice');
|
|
$twiml_url = add_query_arg('workflow_id', $workflow_id, $twiml_url);
|
|
|
|
$result = $twilio->make_call($to_number, $twiml_url);
|
|
|
|
wp_send_json_success($result);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting phone numbers
|
|
*/
|
|
public function ajax_get_phone_numbers() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->get_phone_numbers();
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result['data']['incoming_phone_numbers']);
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for searching available phone numbers
|
|
*/
|
|
public function ajax_search_available_numbers() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$country_code = sanitize_text_field($_POST['country_code']);
|
|
$area_code = sanitize_text_field($_POST['area_code']);
|
|
$contains = sanitize_text_field($_POST['contains']);
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->search_available_numbers($country_code, $area_code, $contains);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result['data']['available_phone_numbers']);
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for purchasing a phone number
|
|
*/
|
|
public function ajax_purchase_number() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$phone_number = sanitize_text_field($_POST['phone_number']);
|
|
$voice_url = isset($_POST['voice_url']) ? esc_url_raw($_POST['voice_url']) : null;
|
|
$sms_url = isset($_POST['sms_url']) ? esc_url_raw($_POST['sms_url']) : null;
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->purchase_phone_number($phone_number, $voice_url, $sms_url);
|
|
|
|
wp_send_json($result);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for configuring a phone number
|
|
*/
|
|
public function ajax_configure_number() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$number_sid = sanitize_text_field($_POST['number_sid']);
|
|
$voice_url = esc_url_raw($_POST['voice_url']);
|
|
$sms_url = esc_url_raw($_POST['sms_url']);
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->configure_phone_number($number_sid, $voice_url, $sms_url);
|
|
|
|
wp_send_json($result);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for releasing a phone number
|
|
*/
|
|
public function ajax_release_number() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$number_sid = sanitize_text_field($_POST['number_sid']);
|
|
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->release_phone_number($number_sid);
|
|
|
|
wp_send_json($result);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting queue details
|
|
*/
|
|
public function ajax_get_queue() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$queue_id = intval($_POST['queue_id']);
|
|
$queue = TWP_Call_Queue::get_queue($queue_id);
|
|
|
|
wp_send_json_success($queue);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for saving queue
|
|
*/
|
|
public function ajax_save_queue() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$queue_id = isset($_POST['queue_id']) ? intval($_POST['queue_id']) : 0;
|
|
|
|
$data = array(
|
|
'queue_name' => sanitize_text_field($_POST['queue_name']),
|
|
'phone_number' => sanitize_text_field($_POST['phone_number']),
|
|
'agent_group_id' => !empty($_POST['agent_group_id']) ? intval($_POST['agent_group_id']) : null,
|
|
'max_size' => intval($_POST['max_size']),
|
|
'wait_music_url' => esc_url_raw($_POST['wait_music_url']),
|
|
'tts_message' => sanitize_textarea_field($_POST['tts_message']),
|
|
'timeout_seconds' => intval($_POST['timeout_seconds'])
|
|
);
|
|
|
|
if ($queue_id) {
|
|
// Update existing queue
|
|
$result = TWP_Call_Queue::update_queue($queue_id, $data);
|
|
} else {
|
|
// Create new queue
|
|
$result = TWP_Call_Queue::create_queue($data);
|
|
}
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting queue details with call info
|
|
*/
|
|
public function ajax_get_queue_details() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$queue_id = intval($_POST['queue_id']);
|
|
$queue = TWP_Call_Queue::get_queue($queue_id);
|
|
|
|
if (!$queue) {
|
|
wp_send_json_error('Queue not found');
|
|
}
|
|
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
|
|
// Get current waiting calls
|
|
$waiting_calls = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM $calls_table WHERE queue_id = %d AND status = 'waiting' ORDER BY position ASC",
|
|
$queue_id
|
|
));
|
|
|
|
// Calculate average wait time
|
|
$avg_wait = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT AVG(TIMESTAMPDIFF(SECOND, joined_at, answered_at))
|
|
FROM $calls_table
|
|
WHERE queue_id = %d AND status = 'answered'
|
|
AND joined_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)",
|
|
$queue_id
|
|
));
|
|
|
|
$queue_status = TWP_Call_Queue::get_queue_status();
|
|
$waiting_count = 0;
|
|
|
|
foreach ($queue_status as $status) {
|
|
if ($status['queue_id'] == $queue_id) {
|
|
$waiting_count = $status['waiting_calls'];
|
|
break;
|
|
}
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'queue' => $queue,
|
|
'waiting_calls' => $waiting_count,
|
|
'avg_wait_time' => $avg_wait ? round($avg_wait) . ' seconds' : 'N/A',
|
|
'calls' => $waiting_calls
|
|
));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting all queues
|
|
*/
|
|
public function ajax_get_all_queues() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$queues = TWP_Call_Queue::get_all_queues();
|
|
|
|
wp_send_json_success($queues);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for deleting queue
|
|
*/
|
|
public function ajax_delete_queue() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$queue_id = intval($_POST['queue_id']);
|
|
$result = TWP_Call_Queue::delete_queue($queue_id);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for dashboard stats
|
|
*/
|
|
public function ajax_get_dashboard_stats() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
// Ensure database tables exist
|
|
require_once TWP_PLUGIN_DIR . 'includes/class-twp-activator.php';
|
|
$tables_exist = TWP_Activator::ensure_tables_exist();
|
|
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
$log_table = $wpdb->prefix . 'twp_call_log';
|
|
|
|
$active_calls = 0;
|
|
$queued_calls = 0;
|
|
$recent_calls = array();
|
|
|
|
try {
|
|
// Check if tables exist before querying
|
|
$calls_table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $calls_table));
|
|
$log_table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $log_table));
|
|
|
|
if ($calls_table_exists) {
|
|
// First, clean up old answered calls that might be stuck (older than 2 hours)
|
|
$wpdb->query(
|
|
"UPDATE $calls_table
|
|
SET status = 'completed', ended_at = NOW()
|
|
WHERE status = 'answered'
|
|
AND joined_at < DATE_SUB(NOW(), INTERVAL 2 HOUR)"
|
|
);
|
|
|
|
// Get active calls - only recent ones to avoid counting stuck records
|
|
$active_calls = $wpdb->get_var(
|
|
"SELECT COUNT(*) FROM $calls_table
|
|
WHERE status IN ('waiting', 'answered')
|
|
AND joined_at >= DATE_SUB(NOW(), INTERVAL 4 HOUR)"
|
|
);
|
|
|
|
// Get queued calls
|
|
$queued_calls = $wpdb->get_var(
|
|
"SELECT COUNT(*) FROM $calls_table WHERE status = 'waiting'"
|
|
);
|
|
}
|
|
|
|
if ($log_table_exists) {
|
|
// Get recent calls from last 24 hours
|
|
$recent_calls = $wpdb->get_results(
|
|
"SELECT call_sid, status, duration, updated_at
|
|
FROM $log_table
|
|
WHERE updated_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
|
ORDER BY updated_at DESC
|
|
LIMIT 10"
|
|
);
|
|
}
|
|
} catch (Exception $e) {
|
|
error_log('TWP Plugin Dashboard Stats Error: ' . $e->getMessage());
|
|
// Continue with default values
|
|
}
|
|
|
|
$formatted_calls = array();
|
|
foreach ($recent_calls as $call) {
|
|
$formatted_calls[] = array(
|
|
'time' => date('H:i', strtotime($call->updated_at)),
|
|
'from' => substr($call->call_sid, 0, 10) . '...',
|
|
'to' => 'System',
|
|
'status' => ucfirst($call->status),
|
|
'duration' => $call->duration ? $call->duration . 's' : '-'
|
|
);
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'active_calls' => $active_calls ?: 0,
|
|
'queued_calls' => $queued_calls ?: 0,
|
|
'recent_calls' => $formatted_calls
|
|
));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting Eleven Labs voices
|
|
*/
|
|
public function ajax_get_elevenlabs_voices() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$elevenlabs = new TWP_ElevenLabs_API();
|
|
$result = $elevenlabs->get_cached_voices();
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result['data']['voices']);
|
|
} else {
|
|
$error_message = 'Failed to load voices';
|
|
if (is_string($result['error'])) {
|
|
$error_message = $result['error'];
|
|
} elseif (is_array($result['error']) && isset($result['error']['detail'])) {
|
|
$error_message = $result['error']['detail'];
|
|
} elseif (is_array($result['error']) && isset($result['error']['error'])) {
|
|
$error_message = $result['error']['error'];
|
|
}
|
|
|
|
// Check if it's an API key issue and provide better error messages
|
|
if (empty(get_option('twp_elevenlabs_api_key'))) {
|
|
$error_message = 'Please configure your ElevenLabs API key in the settings first.';
|
|
} elseif (strpos(strtolower($error_message), 'unauthorized') !== false ||
|
|
strpos(strtolower($error_message), 'invalid') !== false ||
|
|
strpos(strtolower($error_message), '401') !== false) {
|
|
$error_message = 'Invalid API key. Please check your ElevenLabs API key in the settings.';
|
|
} elseif (strpos(strtolower($error_message), 'quota') !== false ||
|
|
strpos(strtolower($error_message), 'limit') !== false) {
|
|
$error_message = 'API quota exceeded. Please check your ElevenLabs subscription limits.';
|
|
} elseif (strpos(strtolower($error_message), 'network') !== false ||
|
|
strpos(strtolower($error_message), 'timeout') !== false ||
|
|
strpos(strtolower($error_message), 'connection') !== false) {
|
|
$error_message = 'Network error connecting to ElevenLabs. Please try again later.';
|
|
} elseif ($error_message === 'Failed to load voices') {
|
|
// Generic error - provide more helpful message
|
|
$api_key = get_option('twp_elevenlabs_api_key');
|
|
if (empty($api_key)) {
|
|
$error_message = 'No ElevenLabs API key configured. Please add your API key in the settings.';
|
|
} else {
|
|
$error_message = 'Unable to connect to ElevenLabs API. Please check your API key and internet connection.';
|
|
}
|
|
}
|
|
|
|
wp_send_json_error($error_message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting ElevenLabs models
|
|
*/
|
|
public function ajax_get_elevenlabs_models() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$elevenlabs = new TWP_ElevenLabs_API();
|
|
$result = $elevenlabs->get_cached_models();
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result['data']);
|
|
} else {
|
|
$error_message = 'Failed to load models';
|
|
if (is_string($result['error'])) {
|
|
$error_message = $result['error'];
|
|
} elseif (is_array($result['error']) && isset($result['error']['detail'])) {
|
|
$error_message = $result['error']['detail'];
|
|
} elseif (is_array($result['error']) && isset($result['error']['error'])) {
|
|
$error_message = $result['error']['error'];
|
|
}
|
|
|
|
// Check if it's an API key issue and provide better error messages
|
|
if (empty(get_option('twp_elevenlabs_api_key'))) {
|
|
$error_message = 'Please configure your ElevenLabs API key in the settings first.';
|
|
} elseif (strpos(strtolower($error_message), 'unauthorized') !== false ||
|
|
strpos(strtolower($error_message), 'invalid') !== false ||
|
|
strpos(strtolower($error_message), '401') !== false) {
|
|
$error_message = 'Invalid API key. Please check your ElevenLabs API key in the settings.';
|
|
} elseif (strpos(strtolower($error_message), 'quota') !== false ||
|
|
strpos(strtolower($error_message), 'limit') !== false) {
|
|
$error_message = 'API quota exceeded. Please check your ElevenLabs subscription limits.';
|
|
} elseif (strpos(strtolower($error_message), 'network') !== false ||
|
|
strpos(strtolower($error_message), 'timeout') !== false ||
|
|
strpos(strtolower($error_message), 'connection') !== false) {
|
|
$error_message = 'Network error connecting to ElevenLabs. Please try again later.';
|
|
} elseif ($error_message === 'Failed to load models') {
|
|
// Generic error - provide more helpful message
|
|
$api_key = get_option('twp_elevenlabs_api_key');
|
|
if (empty($api_key)) {
|
|
$error_message = 'No ElevenLabs API key configured. Please add your API key in the settings.';
|
|
} else {
|
|
$error_message = 'Unable to connect to ElevenLabs API. Please check your API key and internet connection.';
|
|
}
|
|
}
|
|
|
|
wp_send_json_error($error_message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for previewing a voice
|
|
*/
|
|
public function ajax_preview_voice() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$voice_id = sanitize_text_field($_POST['voice_id']);
|
|
$text = sanitize_text_field($_POST['text']) ?: 'Hello, this is a preview of this voice.';
|
|
|
|
$elevenlabs = new TWP_ElevenLabs_API();
|
|
$result = $elevenlabs->text_to_speech($text, $voice_id);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success(array(
|
|
'audio_url' => $result['file_url']
|
|
));
|
|
} else {
|
|
$error_message = 'Failed to generate voice preview';
|
|
if (is_string($result['error'])) {
|
|
$error_message = $result['error'];
|
|
} elseif (is_array($result['error']) && isset($result['error']['detail'])) {
|
|
$error_message = $result['error']['detail'];
|
|
} elseif (is_array($result['error']) && isset($result['error']['error'])) {
|
|
$error_message = $result['error']['error'];
|
|
}
|
|
|
|
wp_send_json_error($error_message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler to get voicemail details
|
|
*/
|
|
public function ajax_get_voicemail() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$voicemail_id = intval($_POST['voicemail_id']);
|
|
|
|
if (!$voicemail_id) {
|
|
wp_send_json_error('Invalid voicemail ID');
|
|
}
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$voicemail = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE id = %d",
|
|
$voicemail_id
|
|
));
|
|
|
|
if ($voicemail) {
|
|
wp_send_json_success($voicemail);
|
|
} else {
|
|
wp_send_json_error('Voicemail not found');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler to delete voicemail
|
|
*/
|
|
public function ajax_delete_voicemail() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$voicemail_id = intval($_POST['voicemail_id']);
|
|
|
|
if (!$voicemail_id) {
|
|
wp_send_json_error('Invalid voicemail ID');
|
|
}
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$result = $wpdb->delete(
|
|
$table_name,
|
|
array('id' => $voicemail_id),
|
|
array('%d')
|
|
);
|
|
|
|
if ($result !== false) {
|
|
wp_send_json_success('Voicemail deleted successfully');
|
|
} else {
|
|
wp_send_json_error('Error deleting voicemail');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler to get voicemail audio URL
|
|
*/
|
|
public function ajax_get_voicemail_audio() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Unauthorized');
|
|
return;
|
|
}
|
|
|
|
$voicemail_id = isset($_POST['voicemail_id']) ? intval($_POST['voicemail_id']) : 0;
|
|
|
|
if (!$voicemail_id) {
|
|
wp_send_json_error('Invalid voicemail ID');
|
|
return;
|
|
}
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$voicemail = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT recording_url FROM $table_name WHERE id = %d",
|
|
$voicemail_id
|
|
));
|
|
|
|
if (!$voicemail || !$voicemail->recording_url) {
|
|
wp_send_json_error('Voicemail not found');
|
|
return;
|
|
}
|
|
|
|
// Fetch the audio from Twilio using authenticated request
|
|
$account_sid = get_option('twp_twilio_account_sid');
|
|
$auth_token = get_option('twp_twilio_auth_token');
|
|
|
|
// Add .mp3 to the URL if not present
|
|
$audio_url = $voicemail->recording_url;
|
|
if (strpos($audio_url, '.mp3') === false && strpos($audio_url, '.wav') === false) {
|
|
$audio_url .= '.mp3';
|
|
}
|
|
|
|
// Log for debugging
|
|
error_log('TWP Voicemail Audio - Fetching from: ' . $audio_url);
|
|
|
|
// Fetch audio with authentication
|
|
$response = wp_remote_get($audio_url, array(
|
|
'headers' => array(
|
|
'Authorization' => 'Basic ' . base64_encode($account_sid . ':' . $auth_token)
|
|
),
|
|
'timeout' => 30
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
error_log('TWP Voicemail Audio - Error: ' . $response->get_error_message());
|
|
wp_send_json_error('Unable to fetch audio: ' . $response->get_error_message());
|
|
return;
|
|
}
|
|
|
|
$response_code = wp_remote_retrieve_response_code($response);
|
|
if ($response_code !== 200) {
|
|
error_log('TWP Voicemail Audio - HTTP Error: ' . $response_code);
|
|
wp_send_json_error('Audio fetch failed with code: ' . $response_code);
|
|
return;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$content_type = wp_remote_retrieve_header($response, 'content-type') ?: 'audio/mpeg';
|
|
|
|
// Return audio as base64 data URL
|
|
$base64_audio = base64_encode($body);
|
|
$data_url = 'data:' . $content_type . ';base64,' . $base64_audio;
|
|
|
|
wp_send_json_success(array(
|
|
'audio_url' => $data_url,
|
|
'content_type' => $content_type,
|
|
'size' => strlen($body)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler to manually transcribe voicemail
|
|
*/
|
|
public function ajax_transcribe_voicemail() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$voicemail_id = intval($_POST['voicemail_id']);
|
|
|
|
if (!$voicemail_id) {
|
|
wp_send_json_error('Invalid voicemail ID');
|
|
}
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
|
|
|
$voicemail = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE id = %d",
|
|
$voicemail_id
|
|
));
|
|
|
|
if (!$voicemail) {
|
|
wp_send_json_error('Voicemail not found');
|
|
}
|
|
|
|
// For now, we'll use a placeholder transcription since we'd need a speech-to-text service
|
|
// In a real implementation, you'd send the recording URL to a transcription service
|
|
$placeholder_transcription = "This is a placeholder transcription. In a production environment, this would be generated using a speech-to-text service like Google Cloud Speech-to-Text, Amazon Transcribe, or Twilio's built-in transcription service.";
|
|
|
|
$result = $wpdb->update(
|
|
$table_name,
|
|
array('transcription' => $placeholder_transcription),
|
|
array('id' => $voicemail_id),
|
|
array('%s'),
|
|
array('%d')
|
|
);
|
|
|
|
if ($result !== false) {
|
|
wp_send_json_success(array('transcription' => $placeholder_transcription));
|
|
} else {
|
|
wp_send_json_error('Error generating transcription');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting all groups
|
|
*/
|
|
public function ajax_get_all_groups() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$groups = TWP_Agent_Groups::get_all_groups();
|
|
wp_send_json_success($groups);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting a group
|
|
*/
|
|
public function ajax_get_group() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$group_id = intval($_POST['group_id']);
|
|
$group = TWP_Agent_Groups::get_group($group_id);
|
|
|
|
wp_send_json_success($group);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for saving a group
|
|
*/
|
|
public function ajax_save_group() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$group_id = isset($_POST['group_id']) ? intval($_POST['group_id']) : 0;
|
|
|
|
$data = array(
|
|
'group_name' => sanitize_text_field($_POST['group_name']),
|
|
'description' => sanitize_textarea_field($_POST['description']),
|
|
'ring_strategy' => sanitize_text_field($_POST['ring_strategy'] ?? 'simultaneous'),
|
|
'timeout_seconds' => intval($_POST['timeout_seconds'] ?? 30)
|
|
);
|
|
|
|
if ($group_id) {
|
|
$result = TWP_Agent_Groups::update_group($group_id, $data);
|
|
} else {
|
|
$result = TWP_Agent_Groups::create_group($data);
|
|
}
|
|
|
|
wp_send_json_success(array('success' => $result !== false, 'group_id' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for deleting a group
|
|
*/
|
|
public function ajax_delete_group() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$group_id = intval($_POST['group_id']);
|
|
$result = TWP_Agent_Groups::delete_group($group_id);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting group members
|
|
*/
|
|
public function ajax_get_group_members() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$group_id = intval($_POST['group_id']);
|
|
$members = TWP_Agent_Groups::get_group_members($group_id);
|
|
|
|
wp_send_json_success($members);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for adding a group member
|
|
*/
|
|
public function ajax_add_group_member() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$group_id = intval($_POST['group_id']);
|
|
$user_id = intval($_POST['user_id']);
|
|
$priority = intval($_POST['priority'] ?? 0);
|
|
|
|
$result = TWP_Agent_Groups::add_member($group_id, $user_id, $priority);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for removing a group member
|
|
*/
|
|
public function ajax_remove_group_member() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$group_id = intval($_POST['group_id']);
|
|
$user_id = intval($_POST['user_id']);
|
|
|
|
$result = TWP_Agent_Groups::remove_member($group_id, $user_id);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for accepting a call
|
|
*/
|
|
public function ajax_accept_call() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$call_id = intval($_POST['call_id']);
|
|
$user_id = get_current_user_id();
|
|
|
|
$result = TWP_Agent_Manager::accept_queued_call($call_id, $user_id);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result);
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting waiting calls
|
|
*/
|
|
public function ajax_get_waiting_calls() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
global $wpdb;
|
|
$calls_table = $wpdb->prefix . 'twp_queued_calls';
|
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
|
|
|
$waiting_calls = $wpdb->get_results("
|
|
SELECT
|
|
c.*,
|
|
q.queue_name,
|
|
TIMESTAMPDIFF(SECOND, c.joined_at, NOW()) as wait_seconds
|
|
FROM $calls_table c
|
|
JOIN $queues_table q ON c.queue_id = q.id
|
|
WHERE c.status = 'waiting'
|
|
ORDER BY c.position ASC
|
|
");
|
|
|
|
wp_send_json_success($waiting_calls);
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for setting agent status
|
|
*/
|
|
public function ajax_set_agent_status() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$user_id = get_current_user_id();
|
|
$status = sanitize_text_field($_POST['status']);
|
|
|
|
$result = TWP_Agent_Manager::set_agent_status($user_id, $status);
|
|
|
|
wp_send_json_success(array('success' => $result));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting call details
|
|
*/
|
|
public function ajax_get_call_details() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
if (!isset($_POST['call_sid'])) {
|
|
wp_send_json_error('Call SID is required');
|
|
}
|
|
|
|
$call_sid = sanitize_text_field($_POST['call_sid']);
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'twp_call_log';
|
|
|
|
$call = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE call_sid = %s",
|
|
$call_sid
|
|
));
|
|
|
|
if ($call) {
|
|
// Parse actions_taken if it's JSON
|
|
if ($call->actions_taken && is_string($call->actions_taken)) {
|
|
$decoded = json_decode($call->actions_taken, true);
|
|
if ($decoded) {
|
|
$call->actions_taken = json_encode($decoded, JSON_PRETTY_PRINT);
|
|
}
|
|
}
|
|
|
|
wp_send_json_success($call);
|
|
} else {
|
|
wp_send_json_error('Call not found');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for requesting callback
|
|
*/
|
|
public function ajax_request_callback() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$phone_number = sanitize_text_field($_POST['phone_number']);
|
|
$queue_id = isset($_POST['queue_id']) ? intval($_POST['queue_id']) : null;
|
|
$call_sid = isset($_POST['call_sid']) ? sanitize_text_field($_POST['call_sid']) : null;
|
|
|
|
if (empty($phone_number)) {
|
|
wp_send_json_error(array('message' => 'Phone number is required'));
|
|
}
|
|
|
|
$callback_id = TWP_Callback_Manager::request_callback($phone_number, $queue_id, $call_sid);
|
|
|
|
if ($callback_id) {
|
|
wp_send_json_success(array(
|
|
'callback_id' => $callback_id,
|
|
'message' => 'Callback requested successfully'
|
|
));
|
|
} else {
|
|
wp_send_json_error(array('message' => 'Failed to request callback'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for initiating outbound calls
|
|
*/
|
|
public function ajax_initiate_outbound_call() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$to_number = sanitize_text_field($_POST['to_number']);
|
|
$agent_user_id = get_current_user_id();
|
|
|
|
if (empty($to_number)) {
|
|
wp_send_json_error(array('message' => 'Phone number is required'));
|
|
}
|
|
|
|
$result = TWP_Callback_Manager::initiate_outbound_call($to_number, $agent_user_id);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success(array(
|
|
'call_sid' => $result['call_sid'],
|
|
'message' => 'Outbound call initiated successfully'
|
|
));
|
|
} else {
|
|
wp_send_json_error(array('message' => $result['error']));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting pending callbacks
|
|
*/
|
|
public function ajax_get_callbacks() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$pending_callbacks = TWP_Callback_Manager::get_pending_callbacks();
|
|
$callback_stats = TWP_Callback_Manager::get_callback_stats();
|
|
|
|
wp_send_json_success(array(
|
|
'callbacks' => $pending_callbacks,
|
|
'stats' => $callback_stats
|
|
));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for updating phone numbers with status callbacks
|
|
*/
|
|
public function ajax_update_phone_status_callbacks() {
|
|
check_ajax_referer('twp_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
}
|
|
|
|
try {
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->enable_status_callbacks_for_all_numbers();
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result['data']);
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
} catch (Exception $e) {
|
|
wp_send_json_error('Failed to update phone numbers: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for toggling individual phone number status callbacks
|
|
*/
|
|
public function ajax_toggle_number_status_callback() {
|
|
check_ajax_referer('twp_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
}
|
|
|
|
$sid = isset($_POST['sid']) ? sanitize_text_field($_POST['sid']) : '';
|
|
$enable = isset($_POST['enable']) ? $_POST['enable'] === 'true' : false;
|
|
|
|
if (empty($sid)) {
|
|
wp_send_json_error('Phone number SID is required');
|
|
}
|
|
|
|
try {
|
|
$twilio = new TWP_Twilio_API();
|
|
$result = $twilio->toggle_number_status_callback($sid, $enable);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success($result['data']);
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
} catch (Exception $e) {
|
|
wp_send_json_error('Failed to update phone number: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for initiating outbound calls with from number
|
|
*/
|
|
public function ajax_initiate_outbound_call_with_from() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$from_number = sanitize_text_field($_POST['from_number']);
|
|
$to_number = sanitize_text_field($_POST['to_number']);
|
|
$agent_phone = sanitize_text_field($_POST['agent_phone']);
|
|
|
|
if (empty($from_number) || empty($to_number) || empty($agent_phone)) {
|
|
wp_send_json_error(array('message' => 'All fields are required'));
|
|
}
|
|
|
|
// Validate phone numbers
|
|
if (!preg_match('/^\+?[1-9]\d{1,14}$/', str_replace([' ', '-', '(', ')'], '', $to_number))) {
|
|
wp_send_json_error(array('message' => 'Invalid destination phone number format'));
|
|
}
|
|
|
|
if (!preg_match('/^\+?[1-9]\d{1,14}$/', str_replace([' ', '-', '(', ')'], '', $agent_phone))) {
|
|
wp_send_json_error(array('message' => 'Invalid agent phone number format'));
|
|
}
|
|
|
|
$result = $this->initiate_outbound_call_with_from($from_number, $to_number, $agent_phone);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success(array(
|
|
'call_sid' => $result['call_sid'],
|
|
'message' => 'Outbound call initiated successfully'
|
|
));
|
|
} else {
|
|
wp_send_json_error(array('message' => $result['error']));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initiate outbound call with specific from number
|
|
*/
|
|
private function initiate_outbound_call_with_from($from_number, $to_number, $agent_phone) {
|
|
$twilio = new TWP_Twilio_API();
|
|
|
|
// Build webhook URL with parameters
|
|
$webhook_url = home_url('/wp-json/twilio-webhook/v1/outbound-agent-with-from') . '?' . http_build_query(array(
|
|
'target_number' => $to_number,
|
|
'agent_user_id' => get_current_user_id(),
|
|
'from_number' => $from_number
|
|
));
|
|
|
|
// First call the agent
|
|
$agent_call_result = $twilio->make_call(
|
|
$agent_phone,
|
|
$webhook_url,
|
|
null, // No status callback needed for this
|
|
$from_number // Use specified from number
|
|
);
|
|
|
|
if ($agent_call_result['success']) {
|
|
$call_sid = isset($agent_call_result['data']['sid']) ? $agent_call_result['data']['sid'] : null;
|
|
|
|
// Set agent to busy
|
|
TWP_Agent_Manager::set_agent_status(get_current_user_id(), 'busy', $call_sid);
|
|
|
|
// Log the outbound call
|
|
TWP_Call_Logger::log_call(array(
|
|
'call_sid' => $call_sid,
|
|
'from_number' => $from_number,
|
|
'to_number' => $to_number,
|
|
'status' => 'outbound_initiated',
|
|
'workflow_name' => 'Outbound Call',
|
|
'actions_taken' => json_encode(array(
|
|
'agent_id' => get_current_user_id(),
|
|
'agent_name' => wp_get_current_user()->display_name,
|
|
'type' => 'click_to_call_with_from',
|
|
'agent_phone' => $agent_phone
|
|
))
|
|
));
|
|
|
|
return array('success' => true, 'call_sid' => $call_sid);
|
|
}
|
|
|
|
return array('success' => false, 'error' => $agent_call_result['error']);
|
|
}
|
|
|
|
}
|