This update adds two major features: 1. Queue Timeout Voicemail - Callers can now leave voicemail when queue timeout is reached - Configurable per-queue voicemail prompts with TTS support - Automatic transcription and urgent keyword detection - Admin setting to choose between voicemail or callback on timeout 2. Amazon SNS SMS Provider - Alternative SMS provider to Twilio for sending text messages - Useful when Twilio SMS approval is difficult to obtain - Provider abstraction layer allows switching between Twilio/SNS - Full AWS SNS configuration in admin settings - Supports custom sender IDs in compatible countries - Lower cost per SMS compared to Twilio New Files: - includes/class-twp-voicemail-handler.php - Voicemail recording handler - includes/interface-twp-sms-provider.php - SMS provider interface - includes/class-twp-sms-provider-twilio.php - Twilio SMS implementation - includes/class-twp-sms-provider-sns.php - Amazon SNS implementation - includes/class-twp-sms-manager.php - SMS provider abstraction manager - QUEUE_VOICEMAIL_SMS_FEATURES.md - Complete feature documentation Modified Files: - includes/class-twp-call-queue.php - Added voicemail option to timeout handler - includes/class-twp-twilio-api.php - Updated send_sms() to use provider abstraction - admin/class-twp-admin.php - Added SMS provider and timeout action settings - composer.json - Added AWS SDK dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			10053 lines
		
	
	
		
			450 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			10053 lines
		
	
	
		
			450 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;
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Verify AJAX nonce - checks both admin and frontend nonces
 | 
						||
     */
 | 
						||
    private function verify_ajax_nonce() {
 | 
						||
        // Try admin nonce first
 | 
						||
        if (wp_verify_nonce($_POST['nonce'] ?? '', 'twp_ajax_nonce')) {
 | 
						||
            return true;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Try frontend nonce
 | 
						||
        if (wp_verify_nonce($_POST['nonce'] ?? '', 'twp_frontend_nonce')) {
 | 
						||
            return true;
 | 
						||
        }
 | 
						||
        
 | 
						||
        return false;
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Format timestamp with WordPress timezone
 | 
						||
     * 
 | 
						||
     * @param string $timestamp Database timestamp (assumed to be in UTC)
 | 
						||
     * @param string $format Date format string
 | 
						||
     * @return string Formatted date in WordPress timezone
 | 
						||
     */
 | 
						||
    private function format_timestamp_with_timezone($timestamp, $format = 'M j, Y g:i A') {
 | 
						||
        // Get WordPress timezone
 | 
						||
        $timezone = wp_timezone();
 | 
						||
        
 | 
						||
        // Create DateTime object from the UTC timestamp
 | 
						||
        $date = new DateTime($timestamp, new DateTimeZone('UTC'));
 | 
						||
        
 | 
						||
        // Convert to WordPress timezone
 | 
						||
        $date->setTimezone($timezone);
 | 
						||
        
 | 
						||
        // Return formatted date
 | 
						||
        return $date->format($format);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Register admin menu
 | 
						||
     */
 | 
						||
    public function add_plugin_admin_menu() {
 | 
						||
        // Determine if user has any agent access
 | 
						||
        $has_agent_access = current_user_can('twp_access_voicemails') ||
 | 
						||
                           current_user_can('twp_access_call_log') ||
 | 
						||
                           current_user_can('twp_access_agent_queue') ||
 | 
						||
                           current_user_can('twp_access_sms_inbox') ||
 | 
						||
                           current_user_can('twp_access_browser_phone');
 | 
						||
        
 | 
						||
        // Only show menu if user is admin or has agent access
 | 
						||
        if (!current_user_can('manage_options') && !$has_agent_access) {
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Determine first available page for agents
 | 
						||
        $first_page = 'twilio-wp-browser-phone'; // Default to browser phone
 | 
						||
        if (current_user_can('twp_access_voicemails')) $first_page = 'twilio-wp-voicemails';
 | 
						||
        elseif (current_user_can('twp_access_call_log')) $first_page = 'twilio-wp-call-logs';
 | 
						||
        elseif (current_user_can('twp_access_agent_queue')) $first_page = 'twilio-wp-agent-queue';
 | 
						||
        elseif (current_user_can('twp_access_sms_inbox')) $first_page = 'twilio-wp-sms-inbox';
 | 
						||
        elseif (current_user_can('twp_access_browser_phone')) $first_page = 'twilio-wp-browser-phone';
 | 
						||
        
 | 
						||
        // Main menu - show dashboard for admins, redirect to first available page for agents
 | 
						||
        if (current_user_can('manage_options')) {
 | 
						||
            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')
 | 
						||
            );
 | 
						||
        } else {
 | 
						||
            add_menu_page(
 | 
						||
                'Twilio Phone',
 | 
						||
                'Twilio Phone',
 | 
						||
                'read',
 | 
						||
                $first_page,
 | 
						||
                null,
 | 
						||
                'dashicons-phone',
 | 
						||
                30
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Admin-only pages
 | 
						||
        if (current_user_can('manage_options')) {
 | 
						||
            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',
 | 
						||
                'Agent Groups',
 | 
						||
                'Agent Groups',
 | 
						||
                'manage_options',
 | 
						||
                'twilio-wp-groups',
 | 
						||
                array($this, 'display_groups_page')
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Agent-accessible pages
 | 
						||
        $menu_parent = current_user_can('manage_options') ? 'twilio-wp-plugin' : $first_page;
 | 
						||
        
 | 
						||
        if (current_user_can('manage_options') || current_user_can('twp_access_voicemails')) {
 | 
						||
            add_submenu_page(
 | 
						||
                $menu_parent,
 | 
						||
                'Voicemails',
 | 
						||
                'Voicemails',
 | 
						||
                current_user_can('manage_options') ? 'manage_options' : 'twp_access_voicemails',
 | 
						||
                'twilio-wp-voicemails',
 | 
						||
                array($this, 'display_voicemails_page')
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (current_user_can('manage_options') || current_user_can('twp_access_call_log')) {
 | 
						||
            add_submenu_page(
 | 
						||
                $menu_parent,
 | 
						||
                'Call Logs',
 | 
						||
                'Call Logs',
 | 
						||
                current_user_can('manage_options') ? 'manage_options' : 'twp_access_call_log',
 | 
						||
                'twilio-wp-call-logs',
 | 
						||
                array($this, 'display_call_logs_page')
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (current_user_can('manage_options') || current_user_can('twp_access_agent_queue')) {
 | 
						||
            add_submenu_page(
 | 
						||
                $menu_parent,
 | 
						||
                'Agent Queue',
 | 
						||
                'Agent Queue',
 | 
						||
                current_user_can('manage_options') ? 'manage_options' : 'twp_access_agent_queue',
 | 
						||
                'twilio-wp-agent-queue',
 | 
						||
                array($this, 'display_agent_queue_page')
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Outbound Calls page removed - functionality merged into Browser Phone
 | 
						||
        // Keeping capability 'twp_access_outbound_calls' for backwards compatibility
 | 
						||
        
 | 
						||
        if (current_user_can('manage_options') || current_user_can('twp_access_sms_inbox')) {
 | 
						||
            add_submenu_page(
 | 
						||
                $menu_parent,
 | 
						||
                'SMS Inbox',
 | 
						||
                'SMS Inbox',
 | 
						||
                current_user_can('manage_options') ? 'manage_options' : 'twp_access_sms_inbox',
 | 
						||
                'twilio-wp-sms-inbox',
 | 
						||
                array($this, 'display_sms_inbox_page')
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (current_user_can('manage_options') || current_user_can('twp_access_browser_phone')) {
 | 
						||
            add_submenu_page(
 | 
						||
                $menu_parent,
 | 
						||
                'Browser Phone',
 | 
						||
                'Browser Phone',
 | 
						||
                current_user_can('manage_options') ? 'manage_options' : 'twp_access_browser_phone',
 | 
						||
                'twilio-wp-browser-phone',
 | 
						||
                array($this, 'display_browser_phone_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>
 | 
						||
                    
 | 
						||
                    <tr>
 | 
						||
                        <th scope="row">TwiML App SID</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="text" name="twp_twiml_app_sid" 
 | 
						||
                                   value="<?php echo esc_attr(get_option('twp_twiml_app_sid')); ?>" 
 | 
						||
                                   class="regular-text" />
 | 
						||
                            <p class="description">TwiML Application SID for Browser Phone (optional). <a href="#twiml-app-instructions">See setup instructions below</a></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>
 | 
						||
                            <button type="button" class="button" onclick="refreshElevenLabsVoices()" title="Refresh voices from ElevenLabs">🔄 Refresh</button>
 | 
						||
                            <p class="description">Default voice for text-to-speech. Click "Load Voices" after entering your API key, or "Refresh" to get updated voices.</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>Call Settings</h2>
 | 
						||
                <table class="form-table">
 | 
						||
                    <tr>
 | 
						||
                        <th scope="row">Default Queue Music URL</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="url" name="twp_default_queue_music_url" 
 | 
						||
                                   value="<?php echo esc_attr(get_option('twp_default_queue_music_url', 'https://www.soundjay.com/misc/sounds/bell-ringing-05.wav')); ?>" 
 | 
						||
                                   class="regular-text" />
 | 
						||
                            <p class="description">Default music for queue wait times and call hold when no specific music is set. Must be publicly accessible MP3 or WAV file.</p>
 | 
						||
                            <p class="description"><strong>Default:</strong> Gentle bell tone (much better than cowbell!). <strong>Better alternatives:</strong></p>
 | 
						||
                            <ul class="description">
 | 
						||
                                <li>• Upload your own music to WordPress Media Library and use that URL</li>
 | 
						||
                                <li>• <strong>Freesound.org</strong> - Free royalty-free music and sounds</li>
 | 
						||
                                <li>• <strong>Archive.org</strong> - Public domain classical music</li>
 | 
						||
                                <li>• <strong>Incompetech.com</strong> - Kevin MacLeod's royalty-free music</li>
 | 
						||
                                <li>• <strong>Zapsplat.com</strong> - Professional hold music (free account required)</li>
 | 
						||
                            </ul>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                    
 | 
						||
                    <tr>
 | 
						||
                        <th scope="row">Hold Music URL</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="url" name="twp_hold_music_url" 
 | 
						||
                                   value="<?php echo esc_attr(get_option('twp_hold_music_url', '')); ?>" 
 | 
						||
                                   class="regular-text" 
 | 
						||
                                   placeholder="Leave empty to use default queue music" />
 | 
						||
                            <p class="description">Specific music for when calls are placed on hold. Leave empty to use the default queue music above.</p>
 | 
						||
                            <p class="description"><strong>Suggested sources:</strong> Upload to your Media Library or use a service like Freesound.org for royalty-free music.</p>
 | 
						||
                        </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>
 | 
						||
 | 
						||
                <h2>SMS Provider Settings</h2>
 | 
						||
                <table class="form-table">
 | 
						||
                    <tr>
 | 
						||
                        <th scope="row">SMS Provider</th>
 | 
						||
                        <td>
 | 
						||
                            <?php $sms_provider = get_option('twp_sms_provider', 'twilio'); ?>
 | 
						||
                            <select name="twp_sms_provider" id="twp_sms_provider" class="regular-text">
 | 
						||
                                <option value="twilio" <?php selected($sms_provider, 'twilio'); ?>>Twilio (Default)</option>
 | 
						||
                                <option value="aws_sns" <?php selected($sms_provider, 'aws_sns'); ?>>Amazon SNS</option>
 | 
						||
                            </select>
 | 
						||
                            <p class="description">Choose which service to use for sending SMS messages. If you're having trouble getting Twilio SMS approved, Amazon SNS is an alternative.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
 | 
						||
                    <!-- Amazon SNS Settings -->
 | 
						||
                    <tr class="aws-sns-setting" style="<?php echo ($sms_provider !== 'aws_sns') ? 'display:none;' : ''; ?>">
 | 
						||
                        <th scope="row">AWS Access Key ID</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="text" name="twp_aws_access_key"
 | 
						||
                                   value="<?php echo esc_attr(get_option('twp_aws_access_key')); ?>"
 | 
						||
                                   class="regular-text"
 | 
						||
                                   placeholder="AKIAIOSFODNN7EXAMPLE" />
 | 
						||
                            <p class="description">Your AWS IAM access key with SNS permissions. <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html" target="_blank">How to create AWS access keys</a></p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
 | 
						||
                    <tr class="aws-sns-setting" style="<?php echo ($sms_provider !== 'aws_sns') ? 'display:none;' : ''; ?>">
 | 
						||
                        <th scope="row">AWS Secret Access Key</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="password" name="twp_aws_secret_key"
 | 
						||
                                   value="<?php echo esc_attr(get_option('twp_aws_secret_key')); ?>"
 | 
						||
                                   class="regular-text"
 | 
						||
                                   placeholder="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" />
 | 
						||
                            <p class="description">Your AWS IAM secret key. Keep this secure.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
 | 
						||
                    <tr class="aws-sns-setting" style="<?php echo ($sms_provider !== 'aws_sns') ? 'display:none;' : ''; ?>">
 | 
						||
                        <th scope="row">AWS Region</th>
 | 
						||
                        <td>
 | 
						||
                            <?php $aws_region = get_option('twp_aws_region', 'us-east-1'); ?>
 | 
						||
                            <select name="twp_aws_region" class="regular-text">
 | 
						||
                                <option value="us-east-1" <?php selected($aws_region, 'us-east-1'); ?>>US East (N. Virginia) - us-east-1</option>
 | 
						||
                                <option value="us-east-2" <?php selected($aws_region, 'us-east-2'); ?>>US East (Ohio) - us-east-2</option>
 | 
						||
                                <option value="us-west-1" <?php selected($aws_region, 'us-west-1'); ?>>US West (N. California) - us-west-1</option>
 | 
						||
                                <option value="us-west-2" <?php selected($aws_region, 'us-west-2'); ?>>US West (Oregon) - us-west-2</option>
 | 
						||
                                <option value="eu-west-1" <?php selected($aws_region, 'eu-west-1'); ?>>EU (Ireland) - eu-west-1</option>
 | 
						||
                                <option value="eu-central-1" <?php selected($aws_region, 'eu-central-1'); ?>>EU (Frankfurt) - eu-central-1</option>
 | 
						||
                                <option value="ap-southeast-1" <?php selected($aws_region, 'ap-southeast-1'); ?>>Asia Pacific (Singapore) - ap-southeast-1</option>
 | 
						||
                                <option value="ap-northeast-1" <?php selected($aws_region, 'ap-northeast-1'); ?>>Asia Pacific (Tokyo) - ap-northeast-1</option>
 | 
						||
                            </select>
 | 
						||
                            <p class="description">AWS region where your SNS service is configured.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
 | 
						||
                    <tr class="aws-sns-setting" style="<?php echo ($sms_provider !== 'aws_sns') ? 'display:none;' : ''; ?>">
 | 
						||
                        <th scope="row">SMS Sender ID (Optional)</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="text" name="twp_aws_sns_sender_id"
 | 
						||
                                   value="<?php echo esc_attr(get_option('twp_aws_sns_sender_id')); ?>"
 | 
						||
                                   class="regular-text"
 | 
						||
                                   placeholder="MyCompany"
 | 
						||
                                   maxlength="11" />
 | 
						||
                            <p class="description">Alphanumeric sender ID (3-11 characters). Supported in some countries. Leave blank to use default AWS number.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
 | 
						||
                    <tr>
 | 
						||
                        <th scope="row">Queue Timeout Action</th>
 | 
						||
                        <td>
 | 
						||
                            <?php $timeout_action = get_option('twp_queue_timeout_action', 'voicemail'); ?>
 | 
						||
                            <select name="twp_queue_timeout_action" class="regular-text">
 | 
						||
                                <option value="voicemail" <?php selected($timeout_action, 'voicemail'); ?>>Take Voicemail</option>
 | 
						||
                                <option value="callback" <?php selected($timeout_action, 'callback'); ?>>Offer Callback</option>
 | 
						||
                            </select>
 | 
						||
                            <p class="description">What to do when a caller reaches the queue timeout limit. Voicemail is recommended for better caller experience.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                </table>
 | 
						||
 | 
						||
                <script>
 | 
						||
                jQuery(document).ready(function($) {
 | 
						||
                    // Show/hide AWS SNS settings based on provider selection
 | 
						||
                    $('#twp_sms_provider').on('change', function() {
 | 
						||
                        if ($(this).val() === 'aws_sns') {
 | 
						||
                            $('.aws-sns-setting').show();
 | 
						||
                        } else {
 | 
						||
                            $('.aws-sns-setting').hide();
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                });
 | 
						||
                </script>
 | 
						||
 | 
						||
                <h2>Voicemail & Notification Settings</h2>
 | 
						||
                <table class="form-table">
 | 
						||
                    <!-- Discord/Slack Notifications Section -->
 | 
						||
                    <tr valign="top">
 | 
						||
                        <td colspan="2">
 | 
						||
                            <h3 style="margin-top: 30px; margin-bottom: 15px;">Discord & Slack Notifications</h3>
 | 
						||
                            <p class="description">Configure webhook URLs to receive call notifications in Discord and/or Slack channels.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                    
 | 
						||
                    <tr valign="top">
 | 
						||
                        <th scope="row">Discord Webhook URL</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="url" name="twp_discord_webhook_url" value="<?php echo esc_attr(get_option('twp_discord_webhook_url')); ?>" class="regular-text" placeholder="https://discord.com/api/webhooks/..." />
 | 
						||
                            <p class="description">Discord webhook URL for call notifications. <a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" target="_blank">How to create a Discord webhook</a></p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                    
 | 
						||
                    <tr valign="top">
 | 
						||
                        <th scope="row">Slack Webhook URL</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="url" name="twp_slack_webhook_url" value="<?php echo esc_attr(get_option('twp_slack_webhook_url')); ?>" class="regular-text" placeholder="https://hooks.slack.com/services/..." />
 | 
						||
                            <p class="description">Slack webhook URL for call notifications. <a href="https://slack.com/help/articles/115005265063-Incoming-webhooks-for-Slack" target="_blank">How to create a Slack webhook</a></p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                    
 | 
						||
                    <tr valign="top">
 | 
						||
                        <th scope="row">Notification Settings</th>
 | 
						||
                        <td>
 | 
						||
                            <fieldset>
 | 
						||
                                <label>
 | 
						||
                                    <input type="checkbox" name="twp_notify_on_incoming_calls" value="1" <?php checked(get_option('twp_notify_on_incoming_calls', 1)); ?> />
 | 
						||
                                    Notify on incoming calls
 | 
						||
                                </label><br>
 | 
						||
                                <label>
 | 
						||
                                    <input type="checkbox" name="twp_notify_on_queue_timeout" value="1" <?php checked(get_option('twp_notify_on_queue_timeout', 1)); ?> />
 | 
						||
                                    Notify when calls stay in queue too long
 | 
						||
                                </label><br>
 | 
						||
                                <label>
 | 
						||
                                    <input type="checkbox" name="twp_notify_on_missed_calls" value="1" <?php checked(get_option('twp_notify_on_missed_calls', 1)); ?> />
 | 
						||
                                    Notify on missed calls
 | 
						||
                                </label>
 | 
						||
                            </fieldset>
 | 
						||
                            <p class="description">Choose which events trigger Discord/Slack notifications.</p>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                    
 | 
						||
                    <tr valign="top">
 | 
						||
                        <th scope="row">Queue Timeout Threshold</th>
 | 
						||
                        <td>
 | 
						||
                            <input type="number" name="twp_queue_timeout_threshold" value="<?php echo esc_attr(get_option('twp_queue_timeout_threshold', 300)); ?>" min="30" max="1800" />
 | 
						||
                            <span>seconds</span>
 | 
						||
                            <p class="description">Send notification if call stays in queue longer than this time (30-1800 seconds).</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>
 | 
						||
            
 | 
						||
            <hr id="twiml-app-instructions">
 | 
						||
            
 | 
						||
            <h2>TwiML App Setup for Browser Phone</h2>
 | 
						||
            <div class="card">
 | 
						||
                <h3>Auto-Configuration (Recommended)</h3>
 | 
						||
                <p>Let the plugin automatically set up everything for you:</p>
 | 
						||
                <div style="background: #e7f5e7; padding: 15px; border-radius: 4px; margin-bottom: 20px;">
 | 
						||
                    <div style="margin-bottom: 15px;">
 | 
						||
                        <button type="button" id="auto-configure-btn" class="button button-primary button-large">
 | 
						||
                            🔧 Auto-Configure Browser Phone
 | 
						||
                        </button>
 | 
						||
                        <button type="button" id="configure-numbers-btn" class="button button-secondary" style="margin-left: 10px;">
 | 
						||
                            📞 Configure Phone Numbers Only
 | 
						||
                        </button>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div style="margin-bottom: 15px; padding: 15px; background: #fff; border: 1px solid #c3e6cb; border-radius: 4px;">
 | 
						||
                        <h4 style="margin-top: 0;">Select Phone Numbers to Configure:</h4>
 | 
						||
                        <div id="phone-numbers-selection">
 | 
						||
                            <p style="color: #666;">Loading phone numbers...</p>
 | 
						||
                        </div>
 | 
						||
                        <div style="margin-top: 10px;">
 | 
						||
                            <button type="button" id="select-all-numbers" class="button button-small">Select All</button>
 | 
						||
                            <button type="button" id="deselect-all-numbers" class="button button-small" style="margin-left: 5px;">Deselect All</button>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div style="margin-bottom: 10px;">
 | 
						||
                        <label style="font-weight: bold;">
 | 
						||
                            <input type="checkbox" id="enable-smart-routing" checked> 
 | 
						||
                            Enable Smart Routing on Selected Numbers
 | 
						||
                        </label>
 | 
						||
                        <p style="margin: 5px 0 0 25px; color: #666; font-size: 13px;">Routes calls based on agent preferences (browser vs cell phone)</p>
 | 
						||
                    </div>
 | 
						||
                    <p style="margin: 10px 0 0 0; color: #155724;">
 | 
						||
                        <strong>Full Setup:</strong> Create TwiML App, set webhooks, configure selected phone numbers.<br>
 | 
						||
                        <strong>Numbers Only:</strong> Configure selected phone numbers with smart routing (requires TwiML App already set up).
 | 
						||
                    </p>
 | 
						||
                </div>
 | 
						||
                <div id="auto-configure-result" style="margin-top: 15px;"></div>
 | 
						||
                
 | 
						||
                <hr style="margin: 30px 0;">
 | 
						||
                
 | 
						||
                <h3>Manual Setup Instructions</h3>
 | 
						||
                <p>Or follow these steps to set up manually in your Twilio Console:</p>
 | 
						||
                
 | 
						||
                <div class="setup-steps">
 | 
						||
                    <div class="step">
 | 
						||
                        <h4>1. Create TwiML Application</h4>
 | 
						||
                        <ol>
 | 
						||
                            <li>Go to <a href="https://console.twilio.com/us1/develop/voice/manage/twiml-apps" target="_blank">Twilio Console → Voice → TwiML Apps</a></li>
 | 
						||
                            <li>Click <strong>"Create new TwiML App"</strong></li>
 | 
						||
                            <li>Enter a friendly name: <code>Browser Phone App</code></li>
 | 
						||
                            <li>Set Voice URL to: <code><?php echo home_url('/wp-json/twilio-webhook/v1/browser-voice'); ?></code> 
 | 
						||
                                <button type="button" class="button button-small" onclick="copyToClipboard('<?php echo home_url('/wp-json/twilio-webhook/v1/browser-voice'); ?>')">Copy</button>
 | 
						||
                            </li>
 | 
						||
                            <li>Set HTTP Method to: <strong>POST</strong></li>
 | 
						||
                            <li>Leave Status Callback URL empty (optional)</li>
 | 
						||
                            <li>Click <strong>"Save"</strong></li>
 | 
						||
                        </ol>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="step">
 | 
						||
                        <h4>2. Get TwiML App SID</h4>
 | 
						||
                        <ol>
 | 
						||
                            <li>After creating the app, copy the <strong>App SID</strong> (starts with <code>AP...</code>)</li>
 | 
						||
                            <li>Paste it in the <strong>"TwiML App SID"</strong> field above</li>
 | 
						||
                            <li>Click <strong>"Save Changes"</strong></li>
 | 
						||
                        </ol>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="step">
 | 
						||
                        <h4>3. Test Browser Phone</h4>
 | 
						||
                        <ol>
 | 
						||
                            <li>Go to <strong>Twilio WP Plugin → Browser Phone</strong></li>
 | 
						||
                            <li>Wait for status to show <span style="color: #4CAF50;">"Ready"</span></li>
 | 
						||
                            <li>Enter a phone number and select caller ID</li>
 | 
						||
                            <li>Click <strong>"Call"</strong> to test outbound calling</li>
 | 
						||
                        </ol>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <div class="setup-info">
 | 
						||
                    <h4>How It Works</h4>
 | 
						||
                    <ul>
 | 
						||
                        <li><strong>Outbound Calls:</strong> Click "Call" to dial any phone number from your browser</li>
 | 
						||
                        <li><strong>Incoming Calls:</strong> Calls can be routed to your browser instead of cell phone</li>
 | 
						||
                        <li><strong>Call Quality:</strong> Uses your internet connection for high-quality VoIP calls</li>
 | 
						||
                        <li><strong>No Cell Phone:</strong> Agents can work entirely from their computer</li>
 | 
						||
                    </ul>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <div class="troubleshooting">
 | 
						||
                    <h4>Troubleshooting</h4>
 | 
						||
                    <ul>
 | 
						||
                        <li><strong>"valid callerId must be provided":</strong> 
 | 
						||
                            <ul>
 | 
						||
                                <li>Make sure you select a Caller ID before calling</li>
 | 
						||
                                <li>The Caller ID must be a phone number you own in Twilio</li>
 | 
						||
                                <li>Go to <a href="https://console.twilio.com/us1/develop/phone-numbers/manage/incoming" target="_blank">Twilio Console → Phone Numbers</a> to verify your numbers</li>
 | 
						||
                            </ul>
 | 
						||
                        </li>
 | 
						||
                        <li><strong>Status shows "Error":</strong> Check that TwiML App SID is correctly configured</li>
 | 
						||
                        <li><strong>"Failed to initialize":</strong> Verify Twilio credentials are correct</li>
 | 
						||
                        <li><strong>Browser blocks microphone:</strong> Allow microphone access when prompted</li>
 | 
						||
                        <li><strong>Poor call quality:</strong> Check internet connection and try different browser</li>
 | 
						||
                        <li><strong>"No audio" on calls:</strong> Check browser microphone permissions and refresh the page</li>
 | 
						||
                    </ul>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <style>
 | 
						||
                .setup-steps .step {
 | 
						||
                    margin-bottom: 30px;
 | 
						||
                    padding: 20px;
 | 
						||
                    background: #f9f9f9;
 | 
						||
                    border-left: 4px solid #0073aa;
 | 
						||
                }
 | 
						||
                .setup-steps h4 {
 | 
						||
                    margin-top: 0;
 | 
						||
                    color: #0073aa;
 | 
						||
                }
 | 
						||
                .setup-info {
 | 
						||
                    background: #e7f5e7;
 | 
						||
                    padding: 15px;
 | 
						||
                    border-radius: 4px;
 | 
						||
                    margin: 20px 0;
 | 
						||
                }
 | 
						||
                .troubleshooting {
 | 
						||
                    background: #fff3cd;
 | 
						||
                    padding: 15px;
 | 
						||
                    border-radius: 4px;
 | 
						||
                    margin: 20px 0;
 | 
						||
                }
 | 
						||
                </style>
 | 
						||
            </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 refreshElevenLabsVoices() {
 | 
						||
                var select = document.getElementById('elevenlabs-voice-select');
 | 
						||
                var button = event.target;
 | 
						||
                var currentValue = select.getAttribute('data-current') || select.value;
 | 
						||
                
 | 
						||
                console.log('Refreshing voices, current value:', currentValue);
 | 
						||
                
 | 
						||
                button.textContent = 'Refreshing...';
 | 
						||
                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';
 | 
						||
                    button.disabled = false;
 | 
						||
                    
 | 
						||
                    try {
 | 
						||
                        var response = JSON.parse(xhr.responseText);
 | 
						||
                        
 | 
						||
                        if (response.success) {
 | 
						||
                            var options = '<option value="">Select a voice...</option>';
 | 
						||
                            
 | 
						||
                            if (Array.isArray(response.data)) {
 | 
						||
                                response.data.forEach(function(voice) {
 | 
						||
                                    var selected = voice.voice_id === currentValue ? ' selected' : '';
 | 
						||
                                    var category = voice.category === 'cloned' ? ' (Cloned)' : (voice.category === 'premade' ? ' (Premade)' : '');
 | 
						||
                                    options += '<option value="' + voice.voice_id + '"' + selected + '>' + voice.name + category + '</option>';
 | 
						||
                                });
 | 
						||
                            }
 | 
						||
                            
 | 
						||
                            select.innerHTML = options;
 | 
						||
                            select.setAttribute('data-current', currentValue);
 | 
						||
                            
 | 
						||
                            // Re-add preview buttons
 | 
						||
                            addVoicePreviewButtons(select, response.data);
 | 
						||
                            
 | 
						||
                            // Show success message
 | 
						||
                            var statusMsg = document.createElement('div');
 | 
						||
                            statusMsg.style.color = 'green';
 | 
						||
                            statusMsg.style.fontSize = '12px';
 | 
						||
                            statusMsg.style.marginTop = '5px';
 | 
						||
                            statusMsg.textContent = 'Voices refreshed successfully! Found ' + response.data.length + ' voices.';
 | 
						||
                            button.parentNode.appendChild(statusMsg);
 | 
						||
                            
 | 
						||
                            setTimeout(function() {
 | 
						||
                                if (statusMsg.parentNode) {
 | 
						||
                                    statusMsg.parentNode.removeChild(statusMsg);
 | 
						||
                                }
 | 
						||
                            }, 3000);
 | 
						||
                            
 | 
						||
                        } else {
 | 
						||
                            alert('Error refreshing voices: ' + (response.data || 'Unknown error'));
 | 
						||
                        }
 | 
						||
                    } catch (e) {
 | 
						||
                        console.error('Refresh voices error:', e);
 | 
						||
                        alert('Failed to refresh voices. Please try again.');
 | 
						||
                    }
 | 
						||
                };
 | 
						||
                
 | 
						||
                xhr.send('action=twp_refresh_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();
 | 
						||
                }
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Auto-configure TwiML App functionality
 | 
						||
            document.addEventListener('DOMContentLoaded', function() {
 | 
						||
                var autoConfigBtn = document.getElementById('auto-configure-btn');
 | 
						||
                var configureNumbersBtn = document.getElementById('configure-numbers-btn');
 | 
						||
                var resultDiv = document.getElementById('auto-configure-result');
 | 
						||
                var smartRoutingCheckbox = document.getElementById('enable-smart-routing');
 | 
						||
                var phoneNumbersDiv = document.getElementById('phone-numbers-selection');
 | 
						||
                
 | 
						||
                // Load phone numbers for selection
 | 
						||
                function loadPhoneNumbersForSelection() {
 | 
						||
                    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) {
 | 
						||
                                var numbers = response.data; // The data is the array directly
 | 
						||
                                if (numbers.length === 0) {
 | 
						||
                                    phoneNumbersDiv.innerHTML = '<p style="color: #666;">No phone numbers found in your Twilio account.</p>';
 | 
						||
                                    return;
 | 
						||
                                }
 | 
						||
                                
 | 
						||
                                var html = '<div style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; border-radius: 4px;">';
 | 
						||
                                numbers.forEach(function(number) {
 | 
						||
                                    html += '<label style="display: block; padding: 5px 0;">';
 | 
						||
                                    html += '<input type="checkbox" class="phone-number-checkbox" value="' + number.sid + '" data-number="' + number.phone_number + '" checked> ';
 | 
						||
                                    html += '<strong>' + number.phone_number + '</strong>';
 | 
						||
                                    if (number.friendly_name && number.friendly_name !== number.phone_number) {
 | 
						||
                                        html += ' - ' + number.friendly_name;
 | 
						||
                                    }
 | 
						||
                                    if (number.voice_url) {
 | 
						||
                                        html += '<br><small style="margin-left: 25px; color: #666;">Current: ' + number.voice_url.replace(/^https?:\/\/[^\/]+/, '') + '</small>';
 | 
						||
                                    }
 | 
						||
                                    html += '</label>';
 | 
						||
                                });
 | 
						||
                                html += '</div>';
 | 
						||
                                phoneNumbersDiv.innerHTML = html;
 | 
						||
                            } else {
 | 
						||
                                phoneNumbersDiv.innerHTML = '<p style="color: #dc3545;">Failed to load phone numbers</p>';
 | 
						||
                            }
 | 
						||
                        } catch(e) {
 | 
						||
                            console.error('Error parsing phone numbers response:', e, xhr.responseText);
 | 
						||
                            phoneNumbersDiv.innerHTML = '<p style="color: #dc3545;">Error loading phone numbers: ' + e.message + '</p>';
 | 
						||
                        }
 | 
						||
                    };
 | 
						||
                    
 | 
						||
                    xhr.onerror = function() {
 | 
						||
                        phoneNumbersDiv.innerHTML = '<p style="color: #dc3545;">Network error loading phone numbers</p>';
 | 
						||
                    };
 | 
						||
                    
 | 
						||
                    xhr.send('action=twp_get_phone_numbers&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>');
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Load phone numbers on page load
 | 
						||
                loadPhoneNumbersForSelection();
 | 
						||
                
 | 
						||
                // Select/Deselect all buttons
 | 
						||
                var selectAllBtn = document.getElementById('select-all-numbers');
 | 
						||
                var deselectAllBtn = document.getElementById('deselect-all-numbers');
 | 
						||
                
 | 
						||
                if (selectAllBtn) {
 | 
						||
                    selectAllBtn.addEventListener('click', function() {
 | 
						||
                        document.querySelectorAll('.phone-number-checkbox').forEach(function(cb) {
 | 
						||
                            cb.checked = true;
 | 
						||
                        });
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                if (deselectAllBtn) {
 | 
						||
                    deselectAllBtn.addEventListener('click', function() {
 | 
						||
                        document.querySelectorAll('.phone-number-checkbox').forEach(function(cb) {
 | 
						||
                            cb.checked = false;
 | 
						||
                        });
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function performConfiguration(action, buttonText, loadingText) {
 | 
						||
                    return function() {
 | 
						||
                        var button = this;
 | 
						||
                        var originalText = button.textContent;
 | 
						||
                        
 | 
						||
                        // Get selected phone numbers
 | 
						||
                        var selectedNumbers = [];
 | 
						||
                        document.querySelectorAll('.phone-number-checkbox:checked').forEach(function(cb) {
 | 
						||
                            selectedNumbers.push({
 | 
						||
                                sid: cb.value,
 | 
						||
                                number: cb.dataset.number
 | 
						||
                            });
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        if (selectedNumbers.length === 0) {
 | 
						||
                            alert('Please select at least one phone number to configure.');
 | 
						||
                            return;
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        button.disabled = true;
 | 
						||
                        button.textContent = loadingText;
 | 
						||
                        
 | 
						||
                        var actionType = action === 'twp_auto_configure_twiml_app' ? 'full' : 'numbers only';
 | 
						||
                        resultDiv.innerHTML = '<div style="background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 4px;">Setting up ' + actionType + ' configuration for ' + selectedNumbers.length + ' number(s)...</div>';
 | 
						||
                        
 | 
						||
                        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 = originalText;
 | 
						||
                            
 | 
						||
                            try {
 | 
						||
                                var response = JSON.parse(xhr.responseText);
 | 
						||
                                var html;
 | 
						||
                                
 | 
						||
                                if (response.success) {
 | 
						||
                                    var data = response.data;
 | 
						||
                                    html = '<div style="background: #d4edda; border: 1px solid #c3e6cb; padding: 15px; border-radius: 4px;">';
 | 
						||
                                    html += '<h4 style="color: #155724; margin-top: 0;">✅ Configuration Successful!</h4>';
 | 
						||
                                    
 | 
						||
                                    if (data.steps_completed && data.steps_completed.length > 0) {
 | 
						||
                                        html += '<h5>Steps Completed:</h5><ul>';
 | 
						||
                                        data.steps_completed.forEach(function(step) {
 | 
						||
                                            html += '<li style="color: #155724;">' + step + '</li>';
 | 
						||
                                        });
 | 
						||
                                        html += '</ul>';
 | 
						||
                                    }
 | 
						||
                                    
 | 
						||
                                    if (data.warnings && data.warnings.length > 0) {
 | 
						||
                                        html += '<h5>Warnings:</h5><ul>';
 | 
						||
                                        data.warnings.forEach(function(warning) {
 | 
						||
                                            html += '<li style="color: #856404;">' + warning + '</li>';
 | 
						||
                                        });
 | 
						||
                                        html += '</ul>';
 | 
						||
                                    }
 | 
						||
                                    
 | 
						||
                                    if (data.app_sid) {
 | 
						||
                                        html += '<p><strong>TwiML App SID:</strong> <code>' + data.app_sid + '</code></p>';
 | 
						||
                                    }
 | 
						||
                                    if (data.voice_url) {
 | 
						||
                                        html += '<p><strong>Voice URL:</strong> <code>' + data.voice_url + '</code></p>';
 | 
						||
                                    }
 | 
						||
                                    if (data.webhook_url) {
 | 
						||
                                        html += '<p><strong>Webhook URL:</strong> <code>' + data.webhook_url + '</code></p>';
 | 
						||
                                    }
 | 
						||
                                    if (data.routing_type) {
 | 
						||
                                        html += '<p><strong>Routing Type:</strong> ' + data.routing_type + '</p>';
 | 
						||
                                    }
 | 
						||
                                    
 | 
						||
                                    var successMessage = action === 'twp_auto_configure_twiml_app' ? 
 | 
						||
                                        '🎉 Your browser phone is now ready! Go to <strong>Twilio WP Plugin → Browser Phone</strong> to start making calls.' :
 | 
						||
                                        '📞 Phone numbers configured successfully!';
 | 
						||
                                    html += '<p style="margin-bottom: 0;">' + successMessage + '</p>';
 | 
						||
                                    html += '</div>';
 | 
						||
                                    
 | 
						||
                                    // Update the TwiML App SID field if it exists
 | 
						||
                                    if (data.app_sid) {
 | 
						||
                                        var appSidField = document.querySelector('input[name="twp_twiml_app_sid"]');
 | 
						||
                                        if (appSidField) {
 | 
						||
                                            appSidField.value = data.app_sid;
 | 
						||
                                        }
 | 
						||
                                    }
 | 
						||
                                    
 | 
						||
                                } else {
 | 
						||
                                    html = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px;">';
 | 
						||
                                    html += '<h4 style="color: #721c24; margin-top: 0;">❌ Configuration Failed</h4>';
 | 
						||
                                    html += '<p style="color: #721c24; margin-bottom: 0;">' + response.data + '</p>';
 | 
						||
                                    html += '</div>';
 | 
						||
                                }
 | 
						||
                                
 | 
						||
                                resultDiv.innerHTML = html;
 | 
						||
                                
 | 
						||
                            } catch(e) {
 | 
						||
                                resultDiv.innerHTML = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px;">' +
 | 
						||
                                    '<h4 style="color: #721c24; margin-top: 0;">❌ Error</h4>' +
 | 
						||
                                    '<p style="color: #721c24; margin-bottom: 0;">Failed to parse response: ' + e.message + '</p></div>';
 | 
						||
                            }
 | 
						||
                        };
 | 
						||
                        
 | 
						||
                        xhr.onerror = function() {
 | 
						||
                            button.disabled = false;
 | 
						||
                            button.textContent = originalText;
 | 
						||
                            resultDiv.innerHTML = '<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px;">' +
 | 
						||
                                '<h4 style="color: #721c24; margin-top: 0;">❌ Network Error</h4>' +
 | 
						||
                                '<p style="color: #721c24; margin-bottom: 0;">Failed to connect to server. Please try again.</p></div>';
 | 
						||
                        };
 | 
						||
                        
 | 
						||
                        var enableSmartRouting = smartRoutingCheckbox ? smartRoutingCheckbox.checked : true;
 | 
						||
                        var params = 'action=' + action + '&nonce=' + '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>' + 
 | 
						||
                            '&enable_smart_routing=' + enableSmartRouting +
 | 
						||
                            '&selected_numbers=' + encodeURIComponent(JSON.stringify(selectedNumbers));
 | 
						||
                        xhr.send(params);
 | 
						||
                    };
 | 
						||
                }
 | 
						||
                
 | 
						||
                if (autoConfigBtn) {
 | 
						||
                    autoConfigBtn.addEventListener('click', performConfiguration(
 | 
						||
                        'twp_auto_configure_twiml_app', 
 | 
						||
                        '🔧 Auto-Configure Browser Phone',
 | 
						||
                        '⏳ Configuring...'
 | 
						||
                    ));
 | 
						||
                }
 | 
						||
                
 | 
						||
                if (configureNumbersBtn) {
 | 
						||
                    configureNumbersBtn.addEventListener('click', performConfiguration(
 | 
						||
                        'twp_configure_phone_numbers_only',
 | 
						||
                        '📞 Configure Phone Numbers Only',
 | 
						||
                        '⏳ Configuring Numbers...'
 | 
						||
                    ));
 | 
						||
                }
 | 
						||
            });
 | 
						||
            </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;
 | 
						||
                        
 | 
						||
                        // Get phone numbers for this workflow
 | 
						||
                        $phone_numbers = TWP_Workflow::get_workflow_phone_numbers($workflow->id);
 | 
						||
                        $phone_display = !empty($phone_numbers) ? implode(', ', $phone_numbers) : $workflow->phone_number;
 | 
						||
                        ?>
 | 
						||
                        <tr>
 | 
						||
                            <td><?php echo esc_html($workflow->workflow_name); ?></td>
 | 
						||
                            <td><?php echo esc_html($phone_display); ?></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="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 Numbers:</label>
 | 
						||
                            <div id="workflow-phone-numbers">
 | 
						||
                                <div class="phone-number-row">
 | 
						||
                                    <select name="phone_numbers[]" class="workflow-phone-select" required>
 | 
						||
                                        <option value="">Select a phone number...</option>
 | 
						||
                                        <!-- Will be populated via AJAX -->
 | 
						||
                                    </select>
 | 
						||
                                    <button type="button" class="button add-phone-number" style="margin-left: 10px;">Add Number</button>
 | 
						||
                                </div>
 | 
						||
                            </div>
 | 
						||
                            <p class="description">You can assign multiple phone numbers to this workflow. All selected numbers will trigger this workflow when called.</p>
 | 
						||
                        </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">Notification Number:</span>
 | 
						||
                                <span class="value"><?php echo esc_html($queue->notification_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>SMS Notification Number:</label>
 | 
						||
                    <select name="notification_number" id="queue-notification-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() {
 | 
						||
        // Get the active tab
 | 
						||
        $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'voicemails';
 | 
						||
        ?>
 | 
						||
        <div class="wrap">
 | 
						||
            <h1>Voicemails & Recordings</h1>
 | 
						||
            
 | 
						||
            <h2 class="nav-tab-wrapper">
 | 
						||
                <a href="?page=twilio-wp-voicemails&tab=voicemails" class="nav-tab <?php echo $active_tab == 'voicemails' ? 'nav-tab-active' : ''; ?>">Voicemails</a>
 | 
						||
                <a href="?page=twilio-wp-voicemails&tab=recordings" class="nav-tab <?php echo $active_tab == 'recordings' ? 'nav-tab-active' : ''; ?>">Call Recordings</a>
 | 
						||
            </h2>
 | 
						||
            
 | 
						||
            <?php if ($active_tab == 'voicemails'): ?>
 | 
						||
            <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 elseif ($active_tab == 'recordings'): ?>
 | 
						||
        <!-- Call Recordings Tab -->
 | 
						||
        <div class="twp-recordings-section">
 | 
						||
            <div class="twp-recordings-filters">
 | 
						||
                <label>Filter by agent:</label>
 | 
						||
                <select id="recording-agent-filter">
 | 
						||
                    <option value="">All agents</option>
 | 
						||
                    <?php
 | 
						||
                    $users = get_users(['role__in' => ['administrator', 'twp_agent']]);
 | 
						||
                    foreach ($users as $user) {
 | 
						||
                        echo '<option value="' . $user->ID . '">' . esc_html($user->display_name) . '</option>';
 | 
						||
                    }
 | 
						||
                    ?>
 | 
						||
                </select>
 | 
						||
                
 | 
						||
                <label>Date range:</label>
 | 
						||
                <input type="date" id="recording-date-from" />
 | 
						||
                <input type="date" id="recording-date-to" />
 | 
						||
                
 | 
						||
                <button class="button" onclick="filterRecordings()">Filter</button>
 | 
						||
                <button class="button" onclick="refreshRecordings()">Refresh</button>
 | 
						||
            </div>
 | 
						||
            
 | 
						||
            <div class="twp-recordings-stats">
 | 
						||
                <div class="stat-card">
 | 
						||
                    <h3>Total Recordings</h3>
 | 
						||
                    <div class="stat-value" id="total-recordings">
 | 
						||
                        <?php
 | 
						||
                        global $wpdb;
 | 
						||
                        $recordings_table = $wpdb->prefix . 'twp_call_recordings';
 | 
						||
                        echo $wpdb->get_var("SELECT COUNT(*) FROM $recordings_table WHERE status = 'completed'");
 | 
						||
                        ?>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <div class="stat-card">
 | 
						||
                    <h3>Today</h3>
 | 
						||
                    <div class="stat-value" id="today-recordings">
 | 
						||
                        <?php
 | 
						||
                        echo $wpdb->get_var("SELECT COUNT(*) FROM $recordings_table WHERE DATE(started_at) = CURDATE()");
 | 
						||
                        ?>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <div class="stat-card">
 | 
						||
                    <h3>Total Duration</h3>
 | 
						||
                    <div class="stat-value" id="total-duration">
 | 
						||
                        <?php
 | 
						||
                        $total_seconds = $wpdb->get_var("SELECT SUM(duration) FROM $recordings_table");
 | 
						||
                        echo $total_seconds ? round($total_seconds / 60) . ' min' : '0 min';
 | 
						||
                        ?>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
            
 | 
						||
            <table class="wp-list-table widefat fixed striped">
 | 
						||
                <thead>
 | 
						||
                    <tr>
 | 
						||
                        <th>Date/Time</th>
 | 
						||
                        <th>From</th>
 | 
						||
                        <th>To</th>
 | 
						||
                        <th>Agent</th>
 | 
						||
                        <th>Duration</th>
 | 
						||
                        <th>Actions</th>
 | 
						||
                    </tr>
 | 
						||
                </thead>
 | 
						||
                <tbody id="recordings-table-body">
 | 
						||
                    <tr>
 | 
						||
                        <td colspan="6">Loading recordings...</td>
 | 
						||
                    </tr>
 | 
						||
                </tbody>
 | 
						||
            </table>
 | 
						||
        </div>
 | 
						||
        
 | 
						||
        <script>
 | 
						||
        jQuery(document).ready(function($) {
 | 
						||
            <?php if ($active_tab == 'recordings'): ?>
 | 
						||
            loadRecordings();
 | 
						||
            <?php endif; ?>
 | 
						||
        });
 | 
						||
        
 | 
						||
        function loadRecordings() {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_get_call_recordings',
 | 
						||
                    nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        displayRecordings(response.data);
 | 
						||
                    } else {
 | 
						||
                        jQuery('#recordings-table-body').html('<tr><td colspan="6">Failed to load recordings</td></tr>');
 | 
						||
                    }
 | 
						||
                },
 | 
						||
                error: function() {
 | 
						||
                    jQuery('#recordings-table-body').html('<tr><td colspan="6">Error loading recordings</td></tr>');
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function displayRecordings(recordings) {
 | 
						||
            var tbody = jQuery('#recordings-table-body');
 | 
						||
            
 | 
						||
            if (recordings.length === 0) {
 | 
						||
                tbody.html('<tr><td colspan="6">No recordings found</td></tr>');
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            var html = '';
 | 
						||
            recordings.forEach(function(recording) {
 | 
						||
                html += '<tr>';
 | 
						||
                html += '<td>' + recording.started_at + '</td>';
 | 
						||
                html += '<td>' + recording.from_number + '</td>';
 | 
						||
                html += '<td>' + recording.to_number + '</td>';
 | 
						||
                html += '<td>' + (recording.agent_name || 'Unknown') + '</td>';
 | 
						||
                html += '<td>' + formatDuration(recording.duration) + '</td>';
 | 
						||
                html += '<td>';
 | 
						||
                if (recording.has_recording) {
 | 
						||
                    var proxyUrl = '<?php echo home_url('/wp-json/twilio-webhook/v1/recording-audio/'); ?>' + recording.id;
 | 
						||
                    html += '<button class="button button-small" onclick="playRecording(\'' + proxyUrl + '\')">Play</button> ';
 | 
						||
                    html += '<a href="' + proxyUrl + '" class="button button-small" download>Download</a>';
 | 
						||
                    <?php if (current_user_can('manage_options')): ?>
 | 
						||
                    html += ' <button class="button button-small button-link-delete" onclick="deleteRecording(' + recording.id + ')">Delete</button>';
 | 
						||
                    <?php endif; ?>
 | 
						||
                } else {
 | 
						||
                    html += 'Processing...';
 | 
						||
                }
 | 
						||
                html += '</td>';
 | 
						||
                html += '</tr>';
 | 
						||
            });
 | 
						||
            
 | 
						||
            tbody.html(html);
 | 
						||
        }
 | 
						||
        
 | 
						||
        function formatDuration(seconds) {
 | 
						||
            if (!seconds) return '0:00';
 | 
						||
            var minutes = Math.floor(seconds / 60);
 | 
						||
            var remainingSeconds = seconds % 60;
 | 
						||
            return minutes + ':' + String(remainingSeconds).padStart(2, '0');
 | 
						||
        }
 | 
						||
        
 | 
						||
        function playRecording(url) {
 | 
						||
            var audio = new Audio(url);
 | 
						||
            audio.play();
 | 
						||
        }
 | 
						||
        
 | 
						||
        function refreshRecordings() {
 | 
						||
            loadRecordings();
 | 
						||
        }
 | 
						||
        
 | 
						||
        function filterRecordings() {
 | 
						||
            // TODO: Implement filtering logic
 | 
						||
            loadRecordings();
 | 
						||
        }
 | 
						||
        
 | 
						||
        function deleteRecording(recordingId) {
 | 
						||
            if (!confirm('Are you sure you want to delete this recording? This action cannot be undone.')) {
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_delete_recording',
 | 
						||
                    recording_id: recordingId,
 | 
						||
                    nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        alert('Recording deleted successfully');
 | 
						||
                        loadRecordings();
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to delete recording: ' + (response.data || 'Unknown error'));
 | 
						||
                    }
 | 
						||
                },
 | 
						||
                error: function() {
 | 
						||
                    alert('Error deleting recording');
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        </script>
 | 
						||
        <?php endif; ?>
 | 
						||
        <?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);
 | 
						||
        
 | 
						||
        // Ensure database tables exist
 | 
						||
        TWP_Activator::ensure_tables_exist();
 | 
						||
        
 | 
						||
        // Get user's extension and assigned queues - create if they don't exist
 | 
						||
        $extension_data = TWP_User_Queue_Manager::get_user_extension_data($current_user_id);
 | 
						||
        
 | 
						||
        // If user doesn't have queues yet, create them
 | 
						||
        if (!$extension_data) {
 | 
						||
            $user_phone = get_user_meta($current_user_id, 'twp_phone_number', true);
 | 
						||
            if ($user_phone) {
 | 
						||
                $creation_result = TWP_User_Queue_Manager::create_user_queues($current_user_id);
 | 
						||
                if ($creation_result['success']) {
 | 
						||
                    $extension_data = TWP_User_Queue_Manager::get_user_extension_data($current_user_id);
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        $assigned_queues = TWP_User_Queue_Manager::get_user_assigned_queues($current_user_id);
 | 
						||
        
 | 
						||
        // Check login status
 | 
						||
        $is_logged_in = TWP_Agent_Manager::is_agent_logged_in($current_user_id);
 | 
						||
        ?>
 | 
						||
        <div class="wrap">
 | 
						||
            <h1>Agent Queue Dashboard</h1>
 | 
						||
            
 | 
						||
            <div class="agent-status-bar">
 | 
						||
                <div class="status-info">
 | 
						||
                    <strong>Extension:</strong> 
 | 
						||
                    <span class="extension-badge"><?php echo $extension_data ? esc_html($extension_data['extension']) : 'Not Assigned'; ?></span>
 | 
						||
                    
 | 
						||
                    <strong>Login Status:</strong>
 | 
						||
                    <button id="login-toggle-btn" class="button <?php echo $is_logged_in ? 'button-secondary' : 'button-primary'; ?>" onclick="toggleAgentLogin()">
 | 
						||
                        <?php echo $is_logged_in ? 'Log Out' : 'Log In'; ?>
 | 
						||
                    </button>
 | 
						||
                    
 | 
						||
                    <strong>Your Status:</strong>
 | 
						||
                    <select id="agent-status-select" onchange="updateAgentStatus(this.value)" <?php echo !$is_logged_in ? 'disabled' : ''; ?>>
 | 
						||
                        <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="assigned-queues-section">
 | 
						||
                <h2>My Assigned Queues</h2>
 | 
						||
                <?php if (empty($assigned_queues)): ?>
 | 
						||
                    <div class="notice notice-info">
 | 
						||
                        <p>
 | 
						||
                            <strong>No queues assigned.</strong> 
 | 
						||
                            <?php if (!$extension_data): ?>
 | 
						||
                                Please configure your phone number in your user profile to get assigned queues automatically.
 | 
						||
                                <br><br>
 | 
						||
                                <button class="button button-primary" onclick="initializeUserQueues()">Initialize My Queues</button>
 | 
						||
                            <?php else: ?>
 | 
						||
                                Your personal queue is being set up. Please refresh the page.
 | 
						||
                            <?php endif; ?>
 | 
						||
                        </p>
 | 
						||
                    </div>
 | 
						||
                <?php else: ?>
 | 
						||
                    <div class="queue-tabs">
 | 
						||
                        <?php foreach ($assigned_queues as $index => $queue): ?>
 | 
						||
                            <button class="queue-tab <?php echo $index === 0 ? 'active' : ''; ?>" 
 | 
						||
                                    data-queue-id="<?php echo esc_attr($queue['id']); ?>"
 | 
						||
                                    onclick="switchQueueView(<?php echo esc_attr($queue['id']); ?>)">
 | 
						||
                                <?php echo esc_html($queue['queue_name']); ?>
 | 
						||
                                <?php if ($queue['is_hold_queue']): ?>
 | 
						||
                                    <span class="hold-indicator">(Hold)</span>
 | 
						||
                                <?php endif; ?>
 | 
						||
                                <span class="queue-count" id="queue-count-<?php echo esc_attr($queue['id']); ?>">
 | 
						||
                                    (<?php echo intval($queue['waiting_calls']); ?>)
 | 
						||
                                </span>
 | 
						||
                            </button>
 | 
						||
                        <?php endforeach; ?>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div id="queue-calls-container">
 | 
						||
                        <?php foreach ($assigned_queues as $index => $queue): ?>
 | 
						||
                            <div class="queue-content" id="queue-content-<?php echo esc_attr($queue['id']); ?>" 
 | 
						||
                                 style="<?php echo $index > 0 ? 'display:none;' : ''; ?>">
 | 
						||
                                <table class="wp-list-table widefat fixed striped">
 | 
						||
                                    <thead>
 | 
						||
                                        <tr>
 | 
						||
                                            <th>Position</th>
 | 
						||
                                            <th>Caller Number</th>
 | 
						||
                                            <th>Wait Time</th>
 | 
						||
                                            <th>Status</th>
 | 
						||
                                            <th>Actions</th>
 | 
						||
                                        </tr>
 | 
						||
                                    </thead>
 | 
						||
                                    <tbody id="queue-calls-<?php echo esc_attr($queue['id']); ?>">
 | 
						||
                                        <tr><td colspan="5">Loading...</td></tr>
 | 
						||
                                    </tbody>
 | 
						||
                                </table>
 | 
						||
                            </div>
 | 
						||
                        <?php endforeach; ?>
 | 
						||
                    </div>
 | 
						||
                <?php endif; ?>
 | 
						||
            </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;
 | 
						||
        }
 | 
						||
        .extension-badge {
 | 
						||
            background: #2271b1;
 | 
						||
            color: white;
 | 
						||
            padding: 2px 8px;
 | 
						||
            border-radius: 3px;
 | 
						||
            margin: 0 10px;
 | 
						||
        }
 | 
						||
        .assigned-queues-section, .my-groups-section {
 | 
						||
            background: #fff;
 | 
						||
            padding: 20px;
 | 
						||
            margin-bottom: 20px;
 | 
						||
            border: 1px solid #ccc;
 | 
						||
        }
 | 
						||
        .queue-tabs {
 | 
						||
            display: flex;
 | 
						||
            gap: 10px;
 | 
						||
            margin-bottom: 20px;
 | 
						||
            border-bottom: 2px solid #ddd;
 | 
						||
        }
 | 
						||
        .queue-tab {
 | 
						||
            padding: 10px 20px;
 | 
						||
            background: #f1f1f1;
 | 
						||
            border: 1px solid #ddd;
 | 
						||
            border-bottom: none;
 | 
						||
            cursor: pointer;
 | 
						||
            position: relative;
 | 
						||
            bottom: -2px;
 | 
						||
        }
 | 
						||
        .queue-tab.active {
 | 
						||
            background: white;
 | 
						||
            border-bottom: 2px solid white;
 | 
						||
        }
 | 
						||
        .queue-tab .queue-count {
 | 
						||
            background: #e74c3c;
 | 
						||
            color: white;
 | 
						||
            padding: 2px 6px;
 | 
						||
            border-radius: 10px;
 | 
						||
            font-size: 12px;
 | 
						||
            margin-left: 5px;
 | 
						||
        }
 | 
						||
        .queue-tab .hold-indicator {
 | 
						||
            color: #f39c12;
 | 
						||
            font-weight: bold;
 | 
						||
        }
 | 
						||
        .action-buttons {
 | 
						||
            display: flex;
 | 
						||
            gap: 5px;
 | 
						||
            flex-wrap: wrap;
 | 
						||
        }
 | 
						||
        .action-buttons button {
 | 
						||
            padding: 5px 10px;
 | 
						||
            font-size: 12px;
 | 
						||
            cursor: pointer;
 | 
						||
            border: none;
 | 
						||
            border-radius: 3px;
 | 
						||
            color: white;
 | 
						||
        }
 | 
						||
        .btn-answer {
 | 
						||
            background: #27ae60;
 | 
						||
        }
 | 
						||
        .btn-answer:hover {
 | 
						||
            background: #229954;
 | 
						||
        }
 | 
						||
        .btn-listen {
 | 
						||
            background: #3498db;
 | 
						||
        }
 | 
						||
        .btn-listen:hover {
 | 
						||
            background: #2980b9;
 | 
						||
        }
 | 
						||
        .btn-record {
 | 
						||
            background: #e74c3c;
 | 
						||
        }
 | 
						||
        .btn-record:hover {
 | 
						||
            background: #c0392b;
 | 
						||
        }
 | 
						||
        .btn-transfer {
 | 
						||
            background: #f39c12;
 | 
						||
        }
 | 
						||
        .btn-transfer:hover {
 | 
						||
            background: #e67e22;
 | 
						||
        }
 | 
						||
        .btn-voicemail {
 | 
						||
            background: #9b59b6;
 | 
						||
        }
 | 
						||
        .btn-voicemail:hover {
 | 
						||
            background: #8e44ad;
 | 
						||
        }
 | 
						||
        .btn-disconnect {
 | 
						||
            background: #95a5a6;
 | 
						||
        }
 | 
						||
        .btn-disconnect:hover {
 | 
						||
            background: #7f8c8d;
 | 
						||
        }
 | 
						||
        .action-buttons button:disabled {
 | 
						||
            background: #ccc;
 | 
						||
            cursor: not-allowed;
 | 
						||
        }
 | 
						||
        </style>
 | 
						||
        
 | 
						||
        <script>
 | 
						||
        // Refresh queue data every 5 seconds
 | 
						||
        let refreshInterval;
 | 
						||
        let currentUser = <?php echo $current_user_id; ?>;
 | 
						||
        let assignedQueues = <?php echo json_encode(!empty($assigned_queues) ? array_column($assigned_queues, 'id') : []); ?>;
 | 
						||
        
 | 
						||
        // Get the appropriate nonce based on context
 | 
						||
        let ajaxNonce = '<?php echo wp_create_nonce(is_admin() ? 'twp_ajax_nonce' : 'twp_frontend_nonce'); ?>';
 | 
						||
        
 | 
						||
        function startQueueRefresh() {
 | 
						||
            refreshQueues();
 | 
						||
            refreshInterval = setInterval(refreshQueues, 5000);
 | 
						||
        }
 | 
						||
        
 | 
						||
        function initializeUserQueues() {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_initialize_user_queues',
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        alert('Queues initialized successfully! The page will now refresh.');
 | 
						||
                        location.reload();
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to initialize queues: ' + response.data);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function refreshQueues() {
 | 
						||
            assignedQueues.forEach(queueId => {
 | 
						||
                jQuery.ajax({
 | 
						||
                    url: ajaxurl,
 | 
						||
                    method: 'POST',
 | 
						||
                    data: {
 | 
						||
                        action: 'twp_get_queue_calls',
 | 
						||
                        queue_id: queueId,
 | 
						||
                        nonce: ajaxNonce
 | 
						||
                    },
 | 
						||
                    success: function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            updateQueueDisplay(queueId, response.data);
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                });
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function updateQueueDisplay(queueId, calls) {
 | 
						||
            const tbody = document.getElementById('queue-calls-' + queueId);
 | 
						||
            const countBadge = document.getElementById('queue-count-' + queueId);
 | 
						||
            
 | 
						||
            if (countBadge) {
 | 
						||
                countBadge.textContent = '(' + calls.length + ')';
 | 
						||
            }
 | 
						||
            
 | 
						||
            if (calls.length === 0) {
 | 
						||
                tbody.innerHTML = '<tr><td colspan="5">No calls in queue</td></tr>';
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            let html = '';
 | 
						||
            calls.forEach(call => {
 | 
						||
                const waitTime = Math.floor((Date.now() - new Date(call.joined_at).getTime()) / 1000);
 | 
						||
                const waitMinutes = Math.floor(waitTime / 60);
 | 
						||
                const waitSeconds = waitTime % 60;
 | 
						||
                
 | 
						||
                html += `
 | 
						||
                    <tr>
 | 
						||
                        <td>${call.position}</td>
 | 
						||
                        <td>${call.from_number}</td>
 | 
						||
                        <td>${waitMinutes}:${waitSeconds.toString().padStart(2, '0')}</td>
 | 
						||
                        <td>${call.status}</td>
 | 
						||
                        <td>
 | 
						||
                            <div class="action-buttons">
 | 
						||
                                <button class="btn-answer" onclick="answerCall('${call.call_sid}', ${queueId})">Answer</button>
 | 
						||
                                <button class="btn-listen" onclick="listenToCall('${call.call_sid}')">Listen</button>
 | 
						||
                                <button class="btn-record" onclick="toggleRecording('${call.call_sid}')">Record</button>
 | 
						||
                                <button class="btn-transfer" onclick="showTransferDialog('${call.call_sid}', ${queueId})">Transfer</button>
 | 
						||
                                <button class="btn-voicemail" onclick="sendToVoicemail('${call.call_sid}', ${queueId})">Voicemail</button>
 | 
						||
                                <button class="btn-disconnect" onclick="disconnectCall('${call.call_sid}', ${queueId})">Disconnect</button>
 | 
						||
                            </div>
 | 
						||
                        </td>
 | 
						||
                    </tr>
 | 
						||
                `;
 | 
						||
            });
 | 
						||
            
 | 
						||
            tbody.innerHTML = html;
 | 
						||
        }
 | 
						||
        
 | 
						||
        function switchQueueView(queueId) {
 | 
						||
            // Hide all queue contents
 | 
						||
            document.querySelectorAll('.queue-content').forEach(content => {
 | 
						||
                content.style.display = 'none';
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Remove active class from all tabs
 | 
						||
            document.querySelectorAll('.queue-tab').forEach(tab => {
 | 
						||
                tab.classList.remove('active');
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Show selected queue content
 | 
						||
            document.getElementById('queue-content-' + queueId).style.display = 'block';
 | 
						||
            
 | 
						||
            // Add active class to selected tab
 | 
						||
            document.querySelector('[data-queue-id="' + queueId + '"]').classList.add('active');
 | 
						||
        }
 | 
						||
        
 | 
						||
        function toggleAgentLogin() {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_toggle_agent_login',
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        location.reload();
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to change login status: ' + response.data);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function answerCall(callSid, queueId) {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_answer_queue_call',
 | 
						||
                    call_sid: callSid,
 | 
						||
                    queue_id: queueId,
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        alert('Call connected!');
 | 
						||
                        refreshQueues();
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to answer call: ' + response.data);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function listenToCall(callSid) {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_monitor_call',
 | 
						||
                    call_sid: callSid,
 | 
						||
                    mode: 'listen',
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        alert('Listening to call...');
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to monitor call: ' + response.data);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function toggleRecording(callSid) {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_toggle_call_recording',
 | 
						||
                    call_sid: callSid,
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        alert(response.data.recording ? 'Recording started' : 'Recording stopped');
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to toggle recording: ' + response.data);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function showTransferDialog(callSid, currentQueueId) {
 | 
						||
            // Fetch available agents and their extensions
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_get_transfer_targets',
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        showTransferModal(response.data, callSid, currentQueueId);
 | 
						||
                    } else {
 | 
						||
                        // Fallback to simple prompt
 | 
						||
                        const targetQueueId = prompt('Enter target queue ID or extension:');
 | 
						||
                        if (targetQueueId) {
 | 
						||
                            transferCall(callSid, currentQueueId, targetQueueId);
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function showTransferModal(targets, callSid, currentQueueId) {
 | 
						||
            // Remove existing modal if any
 | 
						||
            jQuery('#transfer-modal').remove();
 | 
						||
            
 | 
						||
            let optionsHtml = '';
 | 
						||
            
 | 
						||
            // Add user extensions section
 | 
						||
            if (targets.users && targets.users.length > 0) {
 | 
						||
                optionsHtml += '<div class="transfer-section"><h4>Transfer to Agent</h4>';
 | 
						||
                targets.users.forEach(user => {
 | 
						||
                    const statusClass = user.is_logged_in ? 'online' : 'offline';
 | 
						||
                    const statusText = user.is_logged_in ? '🟢' : '🔴';
 | 
						||
                    optionsHtml += `
 | 
						||
                        <div class="transfer-option ${statusClass}" data-target="${user.extension}">
 | 
						||
                            <span class="status-indicator">${statusText}</span>
 | 
						||
                            <strong>${user.extension}</strong> - ${user.display_name}
 | 
						||
                            <span class="user-status">(${user.status})</span>
 | 
						||
                        </div>
 | 
						||
                    `;
 | 
						||
                });
 | 
						||
                optionsHtml += '</div>';
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Add general queues section
 | 
						||
            if (targets.queues && targets.queues.length > 0) {
 | 
						||
                optionsHtml += '<div class="transfer-section"><h4>Transfer to Queue</h4>';
 | 
						||
                targets.queues.forEach(queue => {
 | 
						||
                    optionsHtml += `
 | 
						||
                        <div class="transfer-option" data-target="${queue.id}">
 | 
						||
                            <strong>${queue.queue_name}</strong>
 | 
						||
                            <span class="queue-info">(${queue.waiting_calls} waiting)</span>
 | 
						||
                        </div>
 | 
						||
                    `;
 | 
						||
                });
 | 
						||
                optionsHtml += '</div>';
 | 
						||
            }
 | 
						||
            
 | 
						||
            const modalHtml = `
 | 
						||
                <div id="transfer-modal" class="twp-modal">
 | 
						||
                    <div class="twp-modal-content">
 | 
						||
                        <div class="twp-modal-header">
 | 
						||
                            <h3>Transfer Call</h3>
 | 
						||
                            <span class="twp-modal-close">×</span>
 | 
						||
                        </div>
 | 
						||
                        <div class="twp-modal-body">
 | 
						||
                            ${optionsHtml}
 | 
						||
                        </div>
 | 
						||
                        <div class="twp-modal-footer">
 | 
						||
                            <button class="button button-secondary" id="cancel-transfer">Cancel</button>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            `;
 | 
						||
            
 | 
						||
            jQuery('body').append(modalHtml);
 | 
						||
            
 | 
						||
            // Add modal styles if not already added
 | 
						||
            if (!jQuery('#transfer-modal-styles').length) {
 | 
						||
                jQuery('head').append(`
 | 
						||
                    <style id="transfer-modal-styles">
 | 
						||
                    .twp-modal {
 | 
						||
                        display: block;
 | 
						||
                        position: fixed;
 | 
						||
                        z-index: 100000;
 | 
						||
                        left: 0;
 | 
						||
                        top: 0;
 | 
						||
                        width: 100%;
 | 
						||
                        height: 100%;
 | 
						||
                        background-color: rgba(0,0,0,0.4);
 | 
						||
                    }
 | 
						||
                    .twp-modal-content {
 | 
						||
                        background-color: #fefefe;
 | 
						||
                        margin: 10% auto;
 | 
						||
                        padding: 0;
 | 
						||
                        border: 1px solid #888;
 | 
						||
                        width: 500px;
 | 
						||
                        max-width: 90%;
 | 
						||
                        border-radius: 4px;
 | 
						||
                        max-height: 70vh;
 | 
						||
                        display: flex;
 | 
						||
                        flex-direction: column;
 | 
						||
                    }
 | 
						||
                    .twp-modal-header {
 | 
						||
                        padding: 15px 20px;
 | 
						||
                        background: #f1f1f1;
 | 
						||
                        border-bottom: 1px solid #ddd;
 | 
						||
                        display: flex;
 | 
						||
                        justify-content: space-between;
 | 
						||
                        align-items: center;
 | 
						||
                    }
 | 
						||
                    .twp-modal-header h3 {
 | 
						||
                        margin: 0;
 | 
						||
                    }
 | 
						||
                    .twp-modal-close {
 | 
						||
                        color: #aaa;
 | 
						||
                        font-size: 28px;
 | 
						||
                        font-weight: bold;
 | 
						||
                        cursor: pointer;
 | 
						||
                        line-height: 20px;
 | 
						||
                    }
 | 
						||
                    .twp-modal-close:hover {
 | 
						||
                        color: #000;
 | 
						||
                    }
 | 
						||
                    .twp-modal-body {
 | 
						||
                        padding: 20px;
 | 
						||
                        overflow-y: auto;
 | 
						||
                        flex: 1;
 | 
						||
                    }
 | 
						||
                    .twp-modal-footer {
 | 
						||
                        padding: 15px 20px;
 | 
						||
                        background: #f1f1f1;
 | 
						||
                        border-top: 1px solid #ddd;
 | 
						||
                        text-align: right;
 | 
						||
                    }
 | 
						||
                    .transfer-section {
 | 
						||
                        margin-bottom: 20px;
 | 
						||
                    }
 | 
						||
                    .transfer-section h4 {
 | 
						||
                        margin: 0 0 10px 0;
 | 
						||
                        color: #23282d;
 | 
						||
                    }
 | 
						||
                    .transfer-option {
 | 
						||
                        padding: 10px 15px;
 | 
						||
                        margin: 5px 0;
 | 
						||
                        border: 1px solid #ddd;
 | 
						||
                        border-radius: 3px;
 | 
						||
                        cursor: pointer;
 | 
						||
                        display: flex;
 | 
						||
                        align-items: center;
 | 
						||
                        gap: 10px;
 | 
						||
                        background: white;
 | 
						||
                        transition: all 0.2s;
 | 
						||
                    }
 | 
						||
                    .transfer-option:hover {
 | 
						||
                        background: #f0f8ff;
 | 
						||
                        border-color: #2271b1;
 | 
						||
                    }
 | 
						||
                    .transfer-option.offline {
 | 
						||
                        opacity: 0.6;
 | 
						||
                    }
 | 
						||
                    .transfer-option .status-indicator {
 | 
						||
                        font-size: 12px;
 | 
						||
                    }
 | 
						||
                    .transfer-option .user-status {
 | 
						||
                        margin-left: auto;
 | 
						||
                        color: #666;
 | 
						||
                        font-size: 12px;
 | 
						||
                    }
 | 
						||
                    .transfer-option .queue-info {
 | 
						||
                        margin-left: auto;
 | 
						||
                        color: #666;
 | 
						||
                        font-size: 12px;
 | 
						||
                    }
 | 
						||
                    </style>
 | 
						||
                `);
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Event handlers
 | 
						||
            jQuery('#transfer-modal .transfer-option').on('click', function() {
 | 
						||
                const target = jQuery(this).data('target');
 | 
						||
                jQuery('#transfer-modal').remove();
 | 
						||
                transferCall(callSid, currentQueueId, target);
 | 
						||
            });
 | 
						||
            
 | 
						||
            jQuery('#transfer-modal .twp-modal-close, #cancel-transfer').on('click', function() {
 | 
						||
                jQuery('#transfer-modal').remove();
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Close modal on outside click
 | 
						||
            jQuery('#transfer-modal').on('click', function(e) {
 | 
						||
                if (e.target === this) {
 | 
						||
                    jQuery(this).remove();
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function transferCall(callSid, currentQueueId, targetQueueId) {
 | 
						||
            jQuery.ajax({
 | 
						||
                url: ajaxurl,
 | 
						||
                method: 'POST',
 | 
						||
                data: {
 | 
						||
                    action: 'twp_transfer_call',
 | 
						||
                    call_sid: callSid,
 | 
						||
                    current_queue_id: currentQueueId,
 | 
						||
                    target_queue_id: targetQueueId,
 | 
						||
                    nonce: ajaxNonce
 | 
						||
                },
 | 
						||
                success: function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        alert('Call transferred successfully');
 | 
						||
                        refreshQueues();
 | 
						||
                    } else {
 | 
						||
                        alert('Failed to transfer call: ' + response.data);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
        
 | 
						||
        function sendToVoicemail(callSid, queueId) {
 | 
						||
            if (confirm('Send this call to voicemail?')) {
 | 
						||
                jQuery.ajax({
 | 
						||
                    url: ajaxurl,
 | 
						||
                    method: 'POST',
 | 
						||
                    data: {
 | 
						||
                        action: 'twp_send_to_voicemail',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        queue_id: queueId,
 | 
						||
                        nonce: ajaxNonce
 | 
						||
                    },
 | 
						||
                    success: function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            alert('Call sent to voicemail');
 | 
						||
                            refreshQueues();
 | 
						||
                        } else {
 | 
						||
                            alert('Failed to send to voicemail: ' + response.data);
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                });
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        function disconnectCall(callSid, queueId) {
 | 
						||
            if (confirm('Disconnect this call?')) {
 | 
						||
                jQuery.ajax({
 | 
						||
                    url: ajaxurl,
 | 
						||
                    method: 'POST',
 | 
						||
                    data: {
 | 
						||
                        action: 'twp_disconnect_call',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        queue_id: queueId,
 | 
						||
                        nonce: ajaxNonce
 | 
						||
                    },
 | 
						||
                    success: function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            alert('Call disconnected');
 | 
						||
                            refreshQueues();
 | 
						||
                        } else {
 | 
						||
                            alert('Failed to disconnect call: ' + response.data);
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                });
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Start refresh when page loads
 | 
						||
        jQuery(document).ready(function() {
 | 
						||
            startQueueRefresh();
 | 
						||
        });
 | 
						||
        
 | 
						||
        // Clean up interval when page unloads
 | 
						||
        window.addEventListener('beforeunload', function() {
 | 
						||
            if (refreshInterval) {
 | 
						||
                clearInterval(refreshInterval);
 | 
						||
            }
 | 
						||
        });
 | 
						||
        </script>
 | 
						||
        <?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($this->format_timestamp_with_timezone($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($this->format_timestamp_with_timezone($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($this->format_timestamp_with_timezone($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_twiml_app_sid');
 | 
						||
        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_music_url');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_hold_music_url');
 | 
						||
        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');
 | 
						||
 | 
						||
        // SMS Provider settings
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_sms_provider');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_aws_access_key');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_aws_secret_key');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_aws_region');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_aws_sns_sender_id');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_queue_timeout_action');
 | 
						||
 | 
						||
        // Discord/Slack notification settings
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_discord_webhook_url');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_slack_webhook_url');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_notify_on_incoming_calls');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_notify_on_queue_timeout');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_notify_on_missed_calls');
 | 
						||
        register_setting('twilio-wp-settings-group', 'twp_queue_timeout_threshold');
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * 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;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Handle phone numbers - can be a single number (legacy) or array (new)
 | 
						||
        $phone_numbers = array();
 | 
						||
        if (isset($_POST['phone_numbers']) && is_array($_POST['phone_numbers'])) {
 | 
						||
            // New multi-number format
 | 
						||
            foreach ($_POST['phone_numbers'] as $number) {
 | 
						||
                $number = sanitize_text_field($number);
 | 
						||
                if (!empty($number)) {
 | 
						||
                    $phone_numbers[] = $number;
 | 
						||
                }
 | 
						||
            }
 | 
						||
        } elseif (isset($_POST['phone_number'])) {
 | 
						||
            // Legacy single number format
 | 
						||
            $number = sanitize_text_field($_POST['phone_number']);
 | 
						||
            if (!empty($number)) {
 | 
						||
                $phone_numbers[] = $number;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        $data = array(
 | 
						||
            'workflow_name' => sanitize_text_field($_POST['workflow_name']),
 | 
						||
            'phone_number' => isset($phone_numbers[0]) ? $phone_numbers[0] : '', // Keep first number for backward compatibility
 | 
						||
            '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) {
 | 
						||
                global $wpdb;
 | 
						||
                $workflow_id = $wpdb->insert_id;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Save phone numbers to junction table
 | 
						||
        if ($result !== false && !empty($phone_numbers)) {
 | 
						||
            TWP_Workflow::set_workflow_phone_numbers($workflow_id, $phone_numbers);
 | 
						||
        }
 | 
						||
        
 | 
						||
        if ($result === false) {
 | 
						||
            wp_send_json_error('Failed to save workflow to database');
 | 
						||
        } else {
 | 
						||
            wp_send_json_success(array('success' => true, 'workflow_id' => $workflow_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 getting workflow phone numbers
 | 
						||
     */
 | 
						||
    public function ajax_get_workflow_phone_numbers() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Unauthorized');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $workflow_id = intval($_POST['workflow_id']);
 | 
						||
        $phone_numbers = TWP_Workflow::get_workflow_phone_numbers($workflow_id);
 | 
						||
        
 | 
						||
        wp_send_json_success($phone_numbers);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * 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 getting phone numbers
 | 
						||
     */
 | 
						||
    public function ajax_get_phone_numbers() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_phone_numbers')) {
 | 
						||
            wp_send_json_error('Unauthorized - Phone number access required');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $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']),
 | 
						||
            'notification_number' => sanitize_text_field($_POST['notification_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 with phone numbers
 | 
						||
                $recent_calls = $wpdb->get_results(
 | 
						||
                    "SELECT call_sid, from_number, to_number, 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) {
 | 
						||
            // Format phone numbers for display
 | 
						||
            $from_display = $call->from_number ?: 'Unknown';
 | 
						||
            $to_display = $call->to_number ?: 'Unknown';
 | 
						||
            
 | 
						||
            $formatted_calls[] = array(
 | 
						||
                'time' => $this->format_timestamp_with_timezone($call->updated_at, 'H:i'),
 | 
						||
                'from' => $from_display,
 | 
						||
                'to' => $to_display,
 | 
						||
                '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 queue calls
 | 
						||
     */
 | 
						||
    public function ajax_get_queue_calls() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $queue_id = intval($_POST['queue_id']);
 | 
						||
        
 | 
						||
        if (!$queue_id) {
 | 
						||
            wp_send_json_error('Queue ID required');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $calls = $wpdb->get_results($wpdb->prepare(
 | 
						||
            "SELECT * FROM {$wpdb->prefix}twp_queued_calls 
 | 
						||
             WHERE queue_id = %d AND status = 'waiting'
 | 
						||
             ORDER BY position ASC",
 | 
						||
            $queue_id
 | 
						||
        ), ARRAY_A);
 | 
						||
        
 | 
						||
        wp_send_json_success($calls);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for toggling agent login status
 | 
						||
     */
 | 
						||
    public function ajax_toggle_agent_login() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        $is_logged_in = TWP_Agent_Manager::is_agent_logged_in($user_id);
 | 
						||
        
 | 
						||
        // Toggle the status
 | 
						||
        TWP_Agent_Manager::set_agent_login_status($user_id, !$is_logged_in);
 | 
						||
        
 | 
						||
        wp_send_json_success(array(
 | 
						||
            'logged_in' => !$is_logged_in
 | 
						||
        ));
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for answering a queue call
 | 
						||
     */
 | 
						||
    public function ajax_answer_queue_call() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $queue_id = intval($_POST['queue_id']);
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        // Get agent's phone number
 | 
						||
        $agent_phone = get_user_meta($user_id, 'twp_phone_number', true);
 | 
						||
        
 | 
						||
        if (!$agent_phone) {
 | 
						||
            wp_send_json_error('Agent phone number not configured');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Connect the call to the agent
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $result = $twilio->update_call($call_sid, array(
 | 
						||
            'url' => site_url('/wp-json/twilio-webhook/v1/agent-connect?agent_phone=' . urlencode($agent_phone))
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            // Update queue status
 | 
						||
            global $wpdb;
 | 
						||
            $wpdb->update(
 | 
						||
                $wpdb->prefix . 'twp_queued_calls',
 | 
						||
                array(
 | 
						||
                    'status' => 'answered',
 | 
						||
                    'agent_phone' => $agent_phone,
 | 
						||
                    'answered_at' => current_time('mysql')
 | 
						||
                ),
 | 
						||
                array('call_sid' => $call_sid),
 | 
						||
                array('%s', '%s', '%s'),
 | 
						||
                array('%s')
 | 
						||
            );
 | 
						||
            
 | 
						||
            wp_send_json_success();
 | 
						||
        } else {
 | 
						||
            wp_send_json_error($result['error']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for monitoring a call
 | 
						||
     */
 | 
						||
    public function ajax_monitor_call() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $mode = sanitize_text_field($_POST['mode']); // 'listen', 'whisper', or 'barge'
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        // Get agent's phone number
 | 
						||
        $agent_phone = get_user_meta($user_id, 'twp_phone_number', true);
 | 
						||
        
 | 
						||
        if (!$agent_phone) {
 | 
						||
            wp_send_json_error('Agent phone number not configured');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        
 | 
						||
        // Create a conference for monitoring
 | 
						||
        $conference_name = 'monitor_' . $call_sid;
 | 
						||
        
 | 
						||
        // Update the call to join a conference with monitoring settings
 | 
						||
        $result = $twilio->create_call(array(
 | 
						||
            'to' => $agent_phone,
 | 
						||
            'from' => get_option('twp_default_sms_number'),
 | 
						||
            'url' => site_url('/wp-json/twilio-webhook/v1/monitor-conference?conference=' . $conference_name . '&mode=' . $mode)
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            wp_send_json_success(array(
 | 
						||
                'conference' => $conference_name,
 | 
						||
                'monitor_call_sid' => $result['data']['sid']
 | 
						||
            ));
 | 
						||
        } else {
 | 
						||
            wp_send_json_error($result['error']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for toggling call recording
 | 
						||
     */
 | 
						||
    public function ajax_toggle_call_recording() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        
 | 
						||
        // Check if recording exists
 | 
						||
        global $wpdb;
 | 
						||
        $recording = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT * FROM {$wpdb->prefix}twp_call_recordings 
 | 
						||
             WHERE call_sid = %s AND status = 'recording'",
 | 
						||
            $call_sid
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if ($recording) {
 | 
						||
            // Stop recording
 | 
						||
            $result = $twilio->update_recording($call_sid, $recording->recording_sid, 'stopped');
 | 
						||
            
 | 
						||
            if ($result['success']) {
 | 
						||
                $wpdb->update(
 | 
						||
                    $wpdb->prefix . 'twp_call_recordings',
 | 
						||
                    array('status' => 'completed', 'ended_at' => current_time('mysql')),
 | 
						||
                    array('id' => $recording->id),
 | 
						||
                    array('%s', '%s'),
 | 
						||
                    array('%d')
 | 
						||
                );
 | 
						||
                
 | 
						||
                wp_send_json_success(array('recording' => false));
 | 
						||
            } else {
 | 
						||
                wp_send_json_error($result['error']);
 | 
						||
            }
 | 
						||
        } else {
 | 
						||
            // Start recording
 | 
						||
            $result = $twilio->start_call_recording($call_sid);
 | 
						||
            
 | 
						||
            if ($result['success']) {
 | 
						||
                $wpdb->insert(
 | 
						||
                    $wpdb->prefix . 'twp_call_recordings',
 | 
						||
                    array(
 | 
						||
                        'call_sid' => $call_sid,
 | 
						||
                        'recording_sid' => $result['data']['sid'],
 | 
						||
                        'agent_id' => get_current_user_id(),
 | 
						||
                        'status' => 'recording',
 | 
						||
                        'started_at' => current_time('mysql')
 | 
						||
                    ),
 | 
						||
                    array('%s', '%s', '%d', '%s', '%s')
 | 
						||
                );
 | 
						||
                
 | 
						||
                wp_send_json_success(array('recording' => true));
 | 
						||
            } else {
 | 
						||
                wp_send_json_error($result['error']);
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for sending call to voicemail
 | 
						||
     */
 | 
						||
    public function ajax_send_to_voicemail() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $queue_id = intval($_POST['queue_id']);
 | 
						||
        
 | 
						||
        // Get queue info for voicemail prompt
 | 
						||
        global $wpdb;
 | 
						||
        $queue = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT * FROM {$wpdb->prefix}twp_call_queues WHERE id = %d",
 | 
						||
            $queue_id
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$queue) {
 | 
						||
            wp_send_json_error('Queue not found');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $prompt = $queue->voicemail_prompt ?: 'Please leave a message after the tone.';
 | 
						||
        
 | 
						||
        // Update call to voicemail
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $result = $twilio->update_call($call_sid, array(
 | 
						||
            'url' => site_url('/wp-json/twilio-webhook/v1/voicemail?prompt=' . urlencode($prompt) . '&queue_id=' . $queue_id)
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            // Remove from queue
 | 
						||
            $wpdb->update(
 | 
						||
                $wpdb->prefix . 'twp_queued_calls',
 | 
						||
                array(
 | 
						||
                    'status' => 'voicemail',
 | 
						||
                    'ended_at' => current_time('mysql')
 | 
						||
                ),
 | 
						||
                array('call_sid' => $call_sid),
 | 
						||
                array('%s', '%s'),
 | 
						||
                array('%s')
 | 
						||
            );
 | 
						||
            
 | 
						||
            wp_send_json_success();
 | 
						||
        } else {
 | 
						||
            wp_send_json_error($result['error']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for disconnecting a call
 | 
						||
     */
 | 
						||
    public function ajax_disconnect_call() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $result = $twilio->update_call($call_sid, array('status' => 'completed'));
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            // Update queue status
 | 
						||
            global $wpdb;
 | 
						||
            $wpdb->update(
 | 
						||
                $wpdb->prefix . 'twp_queued_calls',
 | 
						||
                array(
 | 
						||
                    'status' => 'disconnected',
 | 
						||
                    'ended_at' => current_time('mysql')
 | 
						||
                ),
 | 
						||
                array('call_sid' => $call_sid),
 | 
						||
                array('%s', '%s'),
 | 
						||
                array('%s')
 | 
						||
            );
 | 
						||
            
 | 
						||
            wp_send_json_success();
 | 
						||
        } else {
 | 
						||
            wp_send_json_error($result['error']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting transfer targets (agents with extensions and queues)
 | 
						||
     */
 | 
						||
    public function ajax_get_transfer_targets() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        
 | 
						||
        // Get all users with extensions
 | 
						||
        $users_query = "
 | 
						||
            SELECT 
 | 
						||
                ue.user_id,
 | 
						||
                ue.extension,
 | 
						||
                u.display_name,
 | 
						||
                u.user_login,
 | 
						||
                ast.status,
 | 
						||
                ast.is_logged_in
 | 
						||
            FROM {$wpdb->prefix}twp_user_extensions ue
 | 
						||
            INNER JOIN {$wpdb->users} u ON ue.user_id = u.ID
 | 
						||
            LEFT JOIN {$wpdb->prefix}twp_agent_status ast ON ue.user_id = ast.user_id
 | 
						||
            ORDER BY ue.extension ASC
 | 
						||
        ";
 | 
						||
        
 | 
						||
        $users = $wpdb->get_results($users_query, ARRAY_A);
 | 
						||
        
 | 
						||
        // Format user data
 | 
						||
        $formatted_users = array();
 | 
						||
        foreach ($users as $user) {
 | 
						||
            $formatted_users[] = array(
 | 
						||
                'user_id' => $user['user_id'],
 | 
						||
                'extension' => $user['extension'],
 | 
						||
                'display_name' => $user['display_name'],
 | 
						||
                'user_login' => $user['user_login'],
 | 
						||
                'status' => $user['status'] ?: 'offline',
 | 
						||
                'is_logged_in' => $user['is_logged_in'] == 1
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Get general queues (not user-specific)
 | 
						||
        $queues_query = "
 | 
						||
            SELECT 
 | 
						||
                q.id,
 | 
						||
                q.queue_name,
 | 
						||
                q.queue_type,
 | 
						||
                COUNT(qc.id) as waiting_calls
 | 
						||
            FROM {$wpdb->prefix}twp_call_queues q
 | 
						||
            LEFT JOIN {$wpdb->prefix}twp_queued_calls qc ON q.id = qc.queue_id AND qc.status = 'waiting'
 | 
						||
            WHERE q.queue_type = 'general'
 | 
						||
            GROUP BY q.id
 | 
						||
            ORDER BY q.queue_name ASC
 | 
						||
        ";
 | 
						||
        
 | 
						||
        $queues = $wpdb->get_results($queues_query, ARRAY_A);
 | 
						||
        
 | 
						||
        wp_send_json_success(array(
 | 
						||
            'users' => $formatted_users,
 | 
						||
            'queues' => $queues
 | 
						||
        ));
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for initializing user queues
 | 
						||
     */
 | 
						||
    public function ajax_initialize_user_queues() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check permissions - allow both admin and agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        $user_phone = get_user_meta($user_id, 'twp_phone_number', true);
 | 
						||
        
 | 
						||
        if (!$user_phone) {
 | 
						||
            wp_send_json_error('Please configure your phone number in your user profile first');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Create user queues
 | 
						||
        $result = TWP_User_Queue_Manager::create_user_queues($user_id);
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            wp_send_json_success(array(
 | 
						||
                'message' => 'User queues created successfully',
 | 
						||
                'extension' => $result['extension'],
 | 
						||
                'personal_queue_id' => $result['personal_queue_id'],
 | 
						||
                'hold_queue_id' => $result['hold_queue_id']
 | 
						||
            ));
 | 
						||
        } else {
 | 
						||
            wp_send_json_error($result['error']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * 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 refreshing ElevenLabs voices (clears cache)
 | 
						||
     */
 | 
						||
    public function ajax_refresh_elevenlabs_voices() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_die('Unauthorized');
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Clear the cached voices
 | 
						||
        delete_transient('twp_elevenlabs_voices');
 | 
						||
        
 | 
						||
        // Now fetch fresh voices
 | 
						||
        $elevenlabs = new TWP_ElevenLabs_API();
 | 
						||
        $result = $elevenlabs->get_voices(); // This will fetch from API and re-cache
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            wp_send_json_success($result['data']['voices']);
 | 
						||
        } else {
 | 
						||
            $error_message = 'Failed to refresh voices';
 | 
						||
            if (is_string($result['error'])) {
 | 
						||
                $error_message = $result['error'];
 | 
						||
            } elseif (is_array($result['error']) && isset($result['error']['detail'])) {
 | 
						||
                $error_message = $result['error']['detail'];
 | 
						||
            }
 | 
						||
            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 = isset($_POST['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');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_voicemails')) {
 | 
						||
            wp_send_json_error('Unauthorized');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $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') && !current_user_can('twp_access_voicemails')) {
 | 
						||
            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');
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check if voicemail already has a transcription
 | 
						||
        if (!empty($voicemail->transcription) && $voicemail->transcription !== 'Transcription pending...') {
 | 
						||
            wp_send_json_success(array(
 | 
						||
                'message' => 'Transcription already exists',
 | 
						||
                'transcription' => $voicemail->transcription
 | 
						||
            ));
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Try to request transcription from Twilio
 | 
						||
        if (!empty($voicemail->recording_url)) {
 | 
						||
            try {
 | 
						||
                $api = new TWP_Twilio_API();
 | 
						||
                $client = $api->get_client();
 | 
						||
                
 | 
						||
                // Extract recording SID from URL
 | 
						||
                preg_match('/Recordings\/([A-Za-z0-9]+)/', $voicemail->recording_url, $matches);
 | 
						||
                $recording_sid = $matches[1] ?? '';
 | 
						||
                
 | 
						||
                if ($recording_sid) {
 | 
						||
                    // Create transcription request
 | 
						||
                    $transcription = $client->transcriptions->create($recording_sid);
 | 
						||
                    
 | 
						||
                    // Update status to pending
 | 
						||
                    $wpdb->update(
 | 
						||
                        $table_name,
 | 
						||
                        array('transcription' => 'Transcription in progress...'),
 | 
						||
                        array('id' => $voicemail_id),
 | 
						||
                        array('%s'),
 | 
						||
                        array('%d')
 | 
						||
                    );
 | 
						||
                    
 | 
						||
                    wp_send_json_success(array(
 | 
						||
                        'message' => 'Transcription requested successfully',
 | 
						||
                        'transcription' => 'Transcription in progress...'
 | 
						||
                    ));
 | 
						||
                    return;
 | 
						||
                }
 | 
						||
            } catch (Exception $e) {
 | 
						||
                error_log('TWP Transcription Error: ' . $e->getMessage());
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Fallback - manual transcription not available
 | 
						||
        wp_send_json_error(array(
 | 
						||
            'message' => 'Unable to request transcription. Automatic transcription should occur when voicemails are recorded.'
 | 
						||
        ));
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting user's recent voicemails
 | 
						||
     */
 | 
						||
    public function ajax_get_user_voicemails() {
 | 
						||
        check_ajax_referer('twp_frontend_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_voicemails')) {
 | 
						||
            wp_send_json_error('Unauthorized');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $table_name = $wpdb->prefix . 'twp_voicemails';
 | 
						||
        
 | 
						||
        // Get recent voicemails (last 10)
 | 
						||
        $voicemails = $wpdb->get_results($wpdb->prepare("
 | 
						||
            SELECT id, from_number, duration, transcription, created_at, recording_url
 | 
						||
            FROM $table_name 
 | 
						||
            ORDER BY created_at DESC 
 | 
						||
            LIMIT %d
 | 
						||
        ", 10));
 | 
						||
        
 | 
						||
        // Format data for frontend
 | 
						||
        $formatted_voicemails = array();
 | 
						||
        foreach ($voicemails as $vm) {
 | 
						||
            $formatted_voicemails[] = array(
 | 
						||
                'id' => $vm->id,
 | 
						||
                'from_number' => $vm->from_number,
 | 
						||
                'duration' => $vm->duration,
 | 
						||
                'transcription' => $vm->transcription ? substr($vm->transcription, 0, 100) . '...' : 'No transcription',
 | 
						||
                'created_at' => $vm->created_at,
 | 
						||
                'time_ago' => human_time_diff(strtotime($vm->created_at), current_time('timestamp')) . ' ago',
 | 
						||
                'has_recording' => !empty($vm->recording_url)
 | 
						||
            );
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Get voicemail counts
 | 
						||
        $total_count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
 | 
						||
        $today_count = $wpdb->get_var($wpdb->prepare("
 | 
						||
            SELECT COUNT(*) FROM $table_name 
 | 
						||
            WHERE DATE(created_at) = %s
 | 
						||
        ", current_time('Y-m-d')));
 | 
						||
        
 | 
						||
        wp_send_json_success(array(
 | 
						||
            'voicemails' => $formatted_voicemails,
 | 
						||
            'total_count' => $total_count,
 | 
						||
            'today_count' => $today_count
 | 
						||
        ));
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * 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 accepting next call from a queue
 | 
						||
     */
 | 
						||
    public function ajax_accept_next_queue_call() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $queue_id = intval($_POST['queue_id']);
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
        $groups_table = $wpdb->prefix . 'twp_group_members';
 | 
						||
        $queues_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        
 | 
						||
        // Check if this is a user's personal or hold queue first
 | 
						||
        $queue_info = $wpdb->get_row($wpdb->prepare("
 | 
						||
            SELECT * FROM $queues_table WHERE id = %d
 | 
						||
        ", $queue_id));
 | 
						||
        
 | 
						||
        $is_authorized = false;
 | 
						||
        
 | 
						||
        // Check if it's the user's own personal or hold queue
 | 
						||
        if ($queue_info && $queue_info->user_id == $user_id && 
 | 
						||
            ($queue_info->queue_type == 'personal' || $queue_info->queue_type == 'hold')) {
 | 
						||
            $is_authorized = true;
 | 
						||
            error_log("TWP: User {$user_id} authorized for their own {$queue_info->queue_type} queue {$queue_id}");
 | 
						||
        } else {
 | 
						||
            // For regular queues, verify user is a member of this queue's agent group
 | 
						||
            $is_member = $wpdb->get_var($wpdb->prepare("
 | 
						||
                SELECT COUNT(*)
 | 
						||
                FROM $groups_table gm
 | 
						||
                JOIN $queues_table q ON gm.group_id = q.agent_group_id
 | 
						||
                WHERE gm.user_id = %d AND q.id = %d
 | 
						||
            ", $user_id, $queue_id));
 | 
						||
            
 | 
						||
            if ($is_member) {
 | 
						||
                $is_authorized = true;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (!$is_authorized) {
 | 
						||
            wp_send_json_error('You are not authorized to accept calls from this queue');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Get the next waiting call from this queue (lowest position number)
 | 
						||
        $next_call = $wpdb->get_row($wpdb->prepare("
 | 
						||
            SELECT * FROM $calls_table 
 | 
						||
            WHERE queue_id = %d AND status = 'waiting'
 | 
						||
            ORDER BY position ASC
 | 
						||
            LIMIT 1
 | 
						||
        ", $queue_id));
 | 
						||
        
 | 
						||
        if (!$next_call) {
 | 
						||
            wp_send_json_error('No calls waiting in this queue');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $result = TWP_Agent_Manager::accept_queued_call($next_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 for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
        $queues_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        $groups_table = $wpdb->prefix . 'twp_group_members';
 | 
						||
        
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        // Get waiting calls only from queues the user is a member of
 | 
						||
        $waiting_calls = $wpdb->get_results($wpdb->prepare("
 | 
						||
            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
 | 
						||
            JOIN $groups_table gm ON gm.group_id = q.agent_group_id
 | 
						||
            WHERE c.status = 'waiting' AND gm.user_id = %d
 | 
						||
            ORDER BY c.position ASC
 | 
						||
        ", $user_id));
 | 
						||
        
 | 
						||
        wp_send_json_success($waiting_calls);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting agent's assigned queues
 | 
						||
     */
 | 
						||
    public function ajax_get_agent_queues() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Unauthorized - Agent queue access required');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        $queues_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        $groups_table = $wpdb->prefix . 'twp_group_members';
 | 
						||
        $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
        
 | 
						||
        // Auto-create personal queues if they don't exist
 | 
						||
        $extensions_table = $wpdb->prefix . 'twp_user_extensions';
 | 
						||
        $existing_extension = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT extension FROM $extensions_table WHERE user_id = %d",
 | 
						||
            $user_id
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$existing_extension) {
 | 
						||
            TWP_User_Queue_Manager::create_user_queues($user_id);
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Get queues where user is a member of the assigned agent group OR personal/hold queues
 | 
						||
        $user_queues = $wpdb->get_results($wpdb->prepare("
 | 
						||
            SELECT DISTINCT q.*, 
 | 
						||
                   COUNT(c.id) as waiting_count,
 | 
						||
                   COALESCE(SUM(CASE WHEN c.status = 'waiting' THEN 1 ELSE 0 END), 0) as current_waiting
 | 
						||
            FROM $queues_table q
 | 
						||
            LEFT JOIN $groups_table gm ON gm.group_id = q.agent_group_id
 | 
						||
            LEFT JOIN $calls_table c ON c.queue_id = q.id AND c.status = 'waiting'
 | 
						||
            WHERE (gm.user_id = %d AND gm.is_active = 1) 
 | 
						||
               OR (q.user_id = %d AND q.queue_type IN ('personal', 'hold'))
 | 
						||
            GROUP BY q.id
 | 
						||
            ORDER BY 
 | 
						||
                CASE 
 | 
						||
                    WHEN q.queue_type = 'personal' THEN 1
 | 
						||
                    WHEN q.queue_type = 'hold' THEN 2 
 | 
						||
                    ELSE 3 
 | 
						||
                END, 
 | 
						||
                q.queue_name ASC
 | 
						||
        ", $user_id, $user_id));
 | 
						||
        
 | 
						||
        wp_send_json_success($user_queues);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting all queues for requeue operations (frontend-safe)
 | 
						||
     */
 | 
						||
    public function ajax_get_requeue_queues() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Only require user to be logged in, not specific capabilities
 | 
						||
        if (!is_user_logged_in()) {
 | 
						||
            wp_send_json_error('Must be logged in');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Get all queues (same as ajax_get_all_queues but with relaxed permissions)
 | 
						||
        $queues = TWP_Call_Queue::get_all_queues();
 | 
						||
        
 | 
						||
        wp_send_json_success($queues);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * 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 generating capability tokens for Browser Phone
 | 
						||
     */
 | 
						||
    public function ajax_generate_capability_token() {
 | 
						||
        // Check for either admin or frontend nonce
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_browser_phone')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $result = $twilio->generate_capability_token();
 | 
						||
            
 | 
						||
            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 generate capability token: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for saving user's call mode preference
 | 
						||
     */
 | 
						||
    public function ajax_save_call_mode() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('read')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $mode = isset($_POST['mode']) ? sanitize_text_field($_POST['mode']) : '';
 | 
						||
        
 | 
						||
        if (!in_array($mode, ['browser', 'cell'])) {
 | 
						||
            wp_send_json_error('Invalid mode');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        $updated = update_user_meta($user_id, 'twp_call_mode', $mode);
 | 
						||
        
 | 
						||
        if ($updated !== false) {
 | 
						||
            wp_send_json_success([
 | 
						||
                'mode' => $mode,
 | 
						||
                'message' => 'Call mode updated successfully'
 | 
						||
            ]);
 | 
						||
        } else {
 | 
						||
            wp_send_json_error('Failed to update call mode');
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for auto-configuring TwiML App for browser phone
 | 
						||
     */
 | 
						||
    public function ajax_auto_configure_twiml_app() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $enable_smart_routing = isset($_POST['enable_smart_routing']) && $_POST['enable_smart_routing'] === 'true';
 | 
						||
        $selected_numbers = isset($_POST['selected_numbers']) ? json_decode(stripslashes($_POST['selected_numbers']), true) : [];
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $result = $this->auto_configure_browser_phone($enable_smart_routing, $selected_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 auto-configure: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Auto-configure browser phone by creating TwiML App and setting up webhooks
 | 
						||
     */
 | 
						||
    private function auto_configure_browser_phone($enable_smart_routing = true, $selected_numbers = []) {
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $client = $twilio->get_client();
 | 
						||
        
 | 
						||
        if (!$client) {
 | 
						||
            return [
 | 
						||
                'success' => false,
 | 
						||
                'error' => 'Twilio client not initialized. Please check your credentials.'
 | 
						||
            ];
 | 
						||
        }
 | 
						||
        
 | 
						||
        $steps_completed = [];
 | 
						||
        $warnings = [];
 | 
						||
        
 | 
						||
        try {
 | 
						||
            // Step 1: Check if TwiML App already exists
 | 
						||
            $current_app_sid = get_option('twp_twiml_app_sid');
 | 
						||
            $app_sid = null;
 | 
						||
            
 | 
						||
            if ($current_app_sid) {
 | 
						||
                // Try to fetch existing app to verify it exists
 | 
						||
                try {
 | 
						||
                    $existing_app = $client->applications($current_app_sid)->fetch();
 | 
						||
                    $app_sid = $existing_app->sid;
 | 
						||
                    $steps_completed[] = 'Found existing TwiML App: ' . $existing_app->friendlyName;
 | 
						||
                } catch (Exception $e) {
 | 
						||
                    $warnings[] = 'Existing TwiML App SID is invalid, creating new one';
 | 
						||
                    $current_app_sid = null;
 | 
						||
                }
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Step 2: Create TwiML App if needed
 | 
						||
            if (!$app_sid) {
 | 
						||
                $voice_url = home_url('/wp-json/twilio-webhook/v1/browser-voice');
 | 
						||
                $fallback_url = home_url('/wp-json/twilio-webhook/v1/browser-fallback');
 | 
						||
                
 | 
						||
                $app = $client->applications->create([
 | 
						||
                    'friendlyName' => 'Browser Phone App - ' . get_bloginfo('name'),
 | 
						||
                    'voiceUrl' => $voice_url,
 | 
						||
                    'voiceMethod' => 'POST',
 | 
						||
                    'voiceFallbackUrl' => $fallback_url,
 | 
						||
                    'voiceFallbackMethod' => 'POST'
 | 
						||
                ]);
 | 
						||
                
 | 
						||
                $app_sid = $app->sid;
 | 
						||
                $steps_completed[] = 'Created new TwiML App: ' . $app->friendlyName;
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Step 3: Save TwiML App SID to WordPress
 | 
						||
            update_option('twp_twiml_app_sid', $app_sid);
 | 
						||
            $steps_completed[] = 'Saved TwiML App SID to WordPress settings';
 | 
						||
            
 | 
						||
            // Step 4: Test capability token generation
 | 
						||
            $token_result = $twilio->generate_capability_token();
 | 
						||
            if ($token_result['success']) {
 | 
						||
                $steps_completed[] = 'Successfully generated test capability token';
 | 
						||
            } else {
 | 
						||
                $warnings[] = 'Capability token generation failed: ' . $token_result['error'];
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Step 5: Update phone numbers with appropriate webhook URLs
 | 
						||
            $phone_result = $this->auto_configure_phone_numbers_for_browser($enable_smart_routing, $selected_numbers);
 | 
						||
            if ($phone_result['updated_count'] > 0) {
 | 
						||
                $webhook_type = $enable_smart_routing ? 'smart routing' : 'browser voice';
 | 
						||
                $steps_completed[] = 'Updated ' . $phone_result['updated_count'] . ' phone numbers with ' . $webhook_type . ' webhooks';
 | 
						||
            }
 | 
						||
            if ($phone_result['skipped_count'] > 0) {
 | 
						||
                $steps_completed[] = 'Skipped ' . $phone_result['skipped_count'] . ' phone numbers (not selected)';
 | 
						||
            }
 | 
						||
            if (!empty($phone_result['warnings'])) {
 | 
						||
                $warnings = array_merge($warnings, $phone_result['warnings']);
 | 
						||
            }
 | 
						||
            
 | 
						||
            return [
 | 
						||
                'success' => true,
 | 
						||
                'data' => [
 | 
						||
                    'app_sid' => $app_sid,
 | 
						||
                    'steps_completed' => $steps_completed,
 | 
						||
                    'warnings' => $warnings,
 | 
						||
                    'voice_url' => home_url('/wp-json/twilio-webhook/v1/browser-voice'),
 | 
						||
                    'message' => 'Browser phone auto-configuration completed successfully!'
 | 
						||
                ]
 | 
						||
            ];
 | 
						||
            
 | 
						||
        } catch (Exception $e) {
 | 
						||
            return [
 | 
						||
                'success' => false,
 | 
						||
                'error' => 'Auto-configuration failed: ' . $e->getMessage()
 | 
						||
            ];
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Auto-configure phone numbers with browser webhooks (optional)
 | 
						||
     */
 | 
						||
    private function auto_configure_phone_numbers_for_browser($enable_smart_routing = true, $selected_numbers = []) {
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $phone_numbers = $twilio->get_phone_numbers();
 | 
						||
        
 | 
						||
        $updated_count = 0;
 | 
						||
        $skipped_count = 0;
 | 
						||
        $warnings = [];
 | 
						||
        
 | 
						||
        if (!$phone_numbers['success']) {
 | 
						||
            return [
 | 
						||
                'updated_count' => 0,
 | 
						||
                'skipped_count' => 0,
 | 
						||
                'warnings' => ['Could not retrieve phone numbers: ' . $phone_numbers['error']]
 | 
						||
            ];
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Create a map of selected number SIDs for quick lookup
 | 
						||
        $selected_sids = [];
 | 
						||
        if (!empty($selected_numbers)) {
 | 
						||
            foreach ($selected_numbers as $selected) {
 | 
						||
                $selected_sids[$selected['sid']] = true;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        $smart_routing_url = home_url('/wp-json/twilio-webhook/v1/smart-routing');
 | 
						||
        $browser_voice_url = home_url('/wp-json/twilio-webhook/v1/browser-voice');
 | 
						||
        $target_url = $enable_smart_routing ? $smart_routing_url : $browser_voice_url;
 | 
						||
        
 | 
						||
        foreach ($phone_numbers['data']['incoming_phone_numbers'] as $number) {
 | 
						||
            // Skip if number is not selected (when selection is provided)
 | 
						||
            if (!empty($selected_numbers) && !isset($selected_sids[$number['sid']])) {
 | 
						||
                $skipped_count++;
 | 
						||
                error_log('TWP: Skipping phone number ' . $number['phone_number'] . ' (not selected)');
 | 
						||
                continue;
 | 
						||
            }
 | 
						||
            
 | 
						||
            try {
 | 
						||
                // Only update if not already using the target URL
 | 
						||
                if ($number['voice_url'] !== $target_url) {
 | 
						||
                    $client = $twilio->get_client();
 | 
						||
                    $client->incomingPhoneNumbers($number['sid'])->update([
 | 
						||
                        'voiceUrl' => $target_url,
 | 
						||
                        'voiceMethod' => 'POST'
 | 
						||
                    ]);
 | 
						||
                    $updated_count++;
 | 
						||
                    error_log('TWP: Updated phone number ' . $number['phone_number'] . ' to use ' . $target_url);
 | 
						||
                }
 | 
						||
            } catch (Exception $e) {
 | 
						||
                $warnings[] = 'Failed to update ' . $number['phone_number'] . ': ' . $e->getMessage();
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        return [
 | 
						||
            'updated_count' => $updated_count,
 | 
						||
            'skipped_count' => $skipped_count,
 | 
						||
            'warnings' => $warnings
 | 
						||
        ];
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for configuring phone numbers only
 | 
						||
     */
 | 
						||
    public function ajax_configure_phone_numbers_only() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $enable_smart_routing = isset($_POST['enable_smart_routing']) && $_POST['enable_smart_routing'] === 'true';
 | 
						||
        $selected_numbers = isset($_POST['selected_numbers']) ? json_decode(stripslashes($_POST['selected_numbers']), true) : [];
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $result = $this->configure_phone_numbers_only($enable_smart_routing, $selected_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 configure phone numbers: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Configure phone numbers only (no TwiML App creation)
 | 
						||
     */
 | 
						||
    private function configure_phone_numbers_only($enable_smart_routing = true, $selected_numbers = []) {
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $client = $twilio->get_client();
 | 
						||
        
 | 
						||
        if (!$client) {
 | 
						||
            return [
 | 
						||
                'success' => false,
 | 
						||
                'error' => 'Twilio client not initialized. Please check your credentials.'
 | 
						||
            ];
 | 
						||
        }
 | 
						||
        
 | 
						||
        $steps_completed = [];
 | 
						||
        $warnings = [];
 | 
						||
        
 | 
						||
        try {
 | 
						||
            // Configure phone numbers
 | 
						||
            $phone_result = $this->auto_configure_phone_numbers_for_browser($enable_smart_routing, $selected_numbers);
 | 
						||
            
 | 
						||
            if ($phone_result['updated_count'] > 0) {
 | 
						||
                $webhook_type = $enable_smart_routing ? 'smart routing' : 'browser voice';
 | 
						||
                $steps_completed[] = 'Updated ' . $phone_result['updated_count'] . ' phone numbers with ' . $webhook_type . ' webhooks';
 | 
						||
            } else {
 | 
						||
                $steps_completed[] = 'All selected phone numbers already configured correctly';
 | 
						||
            }
 | 
						||
            
 | 
						||
            if ($phone_result['skipped_count'] > 0) {
 | 
						||
                $steps_completed[] = 'Skipped ' . $phone_result['skipped_count'] . ' phone numbers (not selected)';
 | 
						||
            }
 | 
						||
            
 | 
						||
            if (!empty($phone_result['warnings'])) {
 | 
						||
                $warnings = array_merge($warnings, $phone_result['warnings']);
 | 
						||
            }
 | 
						||
            
 | 
						||
            // If smart routing is enabled, verify TwiML App exists
 | 
						||
            if ($enable_smart_routing) {
 | 
						||
                $app_sid = get_option('twp_twiml_app_sid');
 | 
						||
                if (empty($app_sid)) {
 | 
						||
                    $warnings[] = 'Smart routing enabled but no TwiML App SID configured. You may need to run full auto-configuration.';
 | 
						||
                } else {
 | 
						||
                    // Test if the app exists
 | 
						||
                    try {
 | 
						||
                        $client->applications($app_sid)->fetch();
 | 
						||
                        $steps_completed[] = 'Verified TwiML App exists for smart routing';
 | 
						||
                    } catch (Exception $e) {
 | 
						||
                        $warnings[] = 'TwiML App SID is invalid. Smart routing may not work properly.';
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            }
 | 
						||
            
 | 
						||
            $webhook_url = $enable_smart_routing ? 
 | 
						||
                home_url('/wp-json/twilio-webhook/v1/smart-routing') : 
 | 
						||
                home_url('/wp-json/twilio-webhook/v1/browser-voice');
 | 
						||
            
 | 
						||
            return [
 | 
						||
                'success' => true,
 | 
						||
                'data' => [
 | 
						||
                    'steps_completed' => $steps_completed,
 | 
						||
                    'warnings' => $warnings,
 | 
						||
                    'webhook_url' => $webhook_url,
 | 
						||
                    'routing_type' => $enable_smart_routing ? 'Smart Routing' : 'Direct Browser',
 | 
						||
                    'message' => 'Phone number configuration completed successfully!'
 | 
						||
                ]
 | 
						||
            ];
 | 
						||
            
 | 
						||
        } catch (Exception $e) {
 | 
						||
            return [
 | 
						||
                'success' => false,
 | 
						||
                'error' => 'Phone number configuration failed: ' . $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, true);
 | 
						||
            
 | 
						||
            // 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']);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Display SMS Inbox page
 | 
						||
     */
 | 
						||
    public function display_sms_inbox_page() {
 | 
						||
        global $wpdb;
 | 
						||
        $table_name = $wpdb->prefix . 'twp_sms_log';
 | 
						||
        
 | 
						||
        // Get our Twilio numbers first
 | 
						||
        $twilio_numbers = [];
 | 
						||
        try {
 | 
						||
            $twilio_api = new TWP_Twilio_API();
 | 
						||
            $numbers_result = $twilio_api->get_phone_numbers();
 | 
						||
            if ($numbers_result['success'] && !empty($numbers_result['data']['incoming_phone_numbers'])) {
 | 
						||
                foreach ($numbers_result['data']['incoming_phone_numbers'] as $number) {
 | 
						||
                    $twilio_numbers[] = $number['phone_number'];
 | 
						||
                }
 | 
						||
            }
 | 
						||
        } catch (Exception $e) {
 | 
						||
            error_log('Failed to get Twilio numbers: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Build the NOT IN clause for Twilio numbers
 | 
						||
        $twilio_numbers_placeholders = !empty($twilio_numbers) ? 
 | 
						||
            implode(',', array_fill(0, count($twilio_numbers), '%s')) : 
 | 
						||
            "'dummy_number_that_wont_match'";
 | 
						||
        
 | 
						||
        // Get unique conversations (group by customer phone number)
 | 
						||
        // Customer number is the one that's NOT in our Twilio numbers list
 | 
						||
        $query = $wpdb->prepare(
 | 
						||
            "SELECT 
 | 
						||
                customer_number,
 | 
						||
                business_number,
 | 
						||
                MAX(last_message_time) as last_message_time,
 | 
						||
                SUM(message_count) as message_count,
 | 
						||
                MAX(last_message) as last_message,
 | 
						||
                MAX(last_direction) as last_message_direction
 | 
						||
             FROM (
 | 
						||
                SELECT 
 | 
						||
                    from_number as customer_number,
 | 
						||
                    to_number as business_number,
 | 
						||
                    MAX(received_at) as last_message_time,
 | 
						||
                    COUNT(*) as message_count,
 | 
						||
                    (SELECT body FROM $table_name t2 
 | 
						||
                     WHERE t2.from_number = t1.from_number AND t2.to_number = t1.to_number
 | 
						||
                     ORDER BY t2.received_at DESC LIMIT 1) as last_message,
 | 
						||
                    'incoming' as last_direction
 | 
						||
                FROM $table_name t1
 | 
						||
                WHERE from_number NOT IN ($twilio_numbers_placeholders)
 | 
						||
                  AND body NOT IN ('1', 'status', 'help')
 | 
						||
                GROUP BY from_number, to_number
 | 
						||
                
 | 
						||
                UNION ALL
 | 
						||
                
 | 
						||
                SELECT 
 | 
						||
                    to_number as customer_number,
 | 
						||
                    from_number as business_number,
 | 
						||
                    MAX(received_at) as last_message_time,
 | 
						||
                    COUNT(*) as message_count,
 | 
						||
                    (SELECT body FROM $table_name t3 
 | 
						||
                     WHERE t3.to_number = t1.to_number AND t3.from_number = t1.from_number
 | 
						||
                     ORDER BY t3.received_at DESC LIMIT 1) as last_message,
 | 
						||
                    'outgoing' as last_direction
 | 
						||
                FROM $table_name t1
 | 
						||
                WHERE to_number NOT IN ($twilio_numbers_placeholders)
 | 
						||
                  AND from_number IN ($twilio_numbers_placeholders)
 | 
						||
                GROUP BY to_number, from_number
 | 
						||
             ) as conversations
 | 
						||
             GROUP BY customer_number
 | 
						||
             ORDER BY last_message_time DESC
 | 
						||
             LIMIT 50",
 | 
						||
            ...$twilio_numbers,
 | 
						||
            ...$twilio_numbers,
 | 
						||
            ...$twilio_numbers
 | 
						||
        );
 | 
						||
        
 | 
						||
        $conversations = $wpdb->get_results($query);
 | 
						||
        ?>
 | 
						||
        <div class="wrap">
 | 
						||
            <h1>SMS Inbox</h1>
 | 
						||
            <p>View conversations and respond to customer SMS messages. Click on a conversation to view the full thread.</p>
 | 
						||
            
 | 
						||
            <div class="sms-inbox-container">
 | 
						||
                <table class="wp-list-table widefat fixed striped">
 | 
						||
                    <thead>
 | 
						||
                        <tr>
 | 
						||
                            <th style="width: 180px;">Customer</th>
 | 
						||
                            <th style="width: 180px;">Business Line</th>
 | 
						||
                            <th style="width: 120px;">Last Message</th>
 | 
						||
                            <th>Preview</th>
 | 
						||
                            <th style="width: 80px;">Messages</th>
 | 
						||
                            <th style="width: 150px;">Actions</th>
 | 
						||
                        </tr>
 | 
						||
                    </thead>
 | 
						||
                    <tbody>
 | 
						||
                        <?php if (empty($conversations)): ?>
 | 
						||
                            <tr>
 | 
						||
                                <td colspan="6" style="text-align: center; padding: 20px;">
 | 
						||
                                    No customer conversations yet
 | 
						||
                                </td>
 | 
						||
                            </tr>
 | 
						||
                        <?php else: ?>
 | 
						||
                            <?php foreach ($conversations as $conversation): ?>
 | 
						||
                                <tr data-customer="<?php echo esc_attr($conversation->customer_number); ?>" 
 | 
						||
                                    data-business="<?php echo esc_attr($conversation->business_number); ?>">
 | 
						||
                                    <td>
 | 
						||
                                        <strong><?php echo esc_html($conversation->customer_number); ?></strong>
 | 
						||
                                        <br><small style="color: #666;">Customer</small>
 | 
						||
                                    </td>
 | 
						||
                                    <td>
 | 
						||
                                        <strong><?php echo esc_html($conversation->business_number); ?></strong>
 | 
						||
                                        <br><small style="color: #666;">Received on</small>
 | 
						||
                                    </td>
 | 
						||
                                    <td>
 | 
						||
                                        <?php echo esc_html($this->format_timestamp_with_timezone($conversation->last_message_time, 'M j, H:i')); ?>
 | 
						||
                                        <br>
 | 
						||
                                        <small style="color: <?php echo $conversation->last_message_direction === 'incoming' ? '#d63384' : '#0f5132'; ?>;">
 | 
						||
                                            <?php echo $conversation->last_message_direction === 'incoming' ? '← Received' : '→ Sent'; ?>
 | 
						||
                                        </small>
 | 
						||
                                    </td>
 | 
						||
                                    <td>
 | 
						||
                                        <div style="max-width: 300px; word-wrap: break-word;">
 | 
						||
                                            <?php 
 | 
						||
                                            $preview = strlen($conversation->last_message) > 100 ? 
 | 
						||
                                                substr($conversation->last_message, 0, 100) . '...' : 
 | 
						||
                                                $conversation->last_message;
 | 
						||
                                            echo esc_html($preview); 
 | 
						||
                                            ?>
 | 
						||
                                        </div>
 | 
						||
                                    </td>
 | 
						||
                                    <td style="text-align: center;">
 | 
						||
                                        <span class="message-count-badge"><?php echo intval($conversation->message_count); ?></span>
 | 
						||
                                    </td>
 | 
						||
                                    <td>
 | 
						||
                                        <button class="button button-small view-conversation-btn" 
 | 
						||
                                                data-customer="<?php echo esc_attr($conversation->customer_number); ?>"
 | 
						||
                                                data-business="<?php echo esc_attr($conversation->business_number); ?>">
 | 
						||
                                            💬 View Thread
 | 
						||
                                        </button>
 | 
						||
                                        <button class="button button-small delete-conversation-btn" 
 | 
						||
                                                data-customer="<?php echo esc_attr($conversation->customer_number); ?>"
 | 
						||
                                                style="margin-left: 5px;">
 | 
						||
                                            🗑️ Delete
 | 
						||
                                        </button>
 | 
						||
                                    </td>
 | 
						||
                                </tr>
 | 
						||
                            <?php endforeach; ?>
 | 
						||
                        <?php endif; ?>
 | 
						||
                    </tbody>
 | 
						||
                </table>
 | 
						||
            </div>
 | 
						||
            
 | 
						||
            <!-- Conversation Modal -->
 | 
						||
            <div id="conversation-modal" style="display: none;">
 | 
						||
                <div class="modal-backdrop" onclick="closeConversationModal()"></div>
 | 
						||
                <div class="modal-content">
 | 
						||
                    <div class="modal-header">
 | 
						||
                        <h2 id="conversation-title">Conversation</h2>
 | 
						||
                        <button type="button" class="modal-close" onclick="closeConversationModal()">×</button>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="conversation-messages" id="conversation-messages">
 | 
						||
                        <div id="loading-messages">Loading conversation...</div>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="reply-form">
 | 
						||
                        <div class="reply-inputs">
 | 
						||
                            <textarea id="reply-message" placeholder="Type your message..." rows="3"></textarea>
 | 
						||
                            <div class="reply-actions">
 | 
						||
                                <button type="button" id="send-reply-btn" class="button button-primary">Send</button>
 | 
						||
                                <button type="button" onclick="closeConversationModal()" class="button">Cancel</button>
 | 
						||
                            </div>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
            
 | 
						||
            <style>
 | 
						||
            .sms-inbox-container {
 | 
						||
                margin-top: 20px;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message-count-badge {
 | 
						||
                background: #0073aa;
 | 
						||
                color: white;
 | 
						||
                padding: 4px 8px;
 | 
						||
                border-radius: 12px;
 | 
						||
                font-size: 11px;
 | 
						||
                font-weight: bold;
 | 
						||
            }
 | 
						||
            
 | 
						||
            #conversation-modal {
 | 
						||
                position: fixed;
 | 
						||
                top: 0;
 | 
						||
                left: 0;
 | 
						||
                right: 0;
 | 
						||
                bottom: 0;
 | 
						||
                z-index: 100000;
 | 
						||
            }
 | 
						||
            
 | 
						||
            #conversation-modal .modal-backdrop {
 | 
						||
                position: absolute;
 | 
						||
                top: 0;
 | 
						||
                left: 0;
 | 
						||
                right: 0;
 | 
						||
                bottom: 0;
 | 
						||
                background: rgba(0, 0, 0, 0.5);
 | 
						||
            }
 | 
						||
            
 | 
						||
            #conversation-modal .modal-content {
 | 
						||
                position: absolute;
 | 
						||
                top: 50%;
 | 
						||
                left: 50%;
 | 
						||
                transform: translate(-50%, -50%);
 | 
						||
                background: white;
 | 
						||
                border-radius: 8px;
 | 
						||
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
 | 
						||
                max-width: 600px;
 | 
						||
                width: 90%;
 | 
						||
                max-height: 80vh;
 | 
						||
                display: flex;
 | 
						||
                flex-direction: column;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .modal-header {
 | 
						||
                padding: 20px;
 | 
						||
                border-bottom: 1px solid #ddd;
 | 
						||
                display: flex;
 | 
						||
                justify-content: space-between;
 | 
						||
                align-items: center;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .modal-header h2 {
 | 
						||
                margin: 0;
 | 
						||
                font-size: 18px;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .modal-close {
 | 
						||
                background: none;
 | 
						||
                border: none;
 | 
						||
                font-size: 24px;
 | 
						||
                cursor: pointer;
 | 
						||
                color: #666;
 | 
						||
                padding: 0;
 | 
						||
                width: 30px;
 | 
						||
                height: 30px;
 | 
						||
                display: flex;
 | 
						||
                align-items: center;
 | 
						||
                justify-content: center;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .conversation-messages {
 | 
						||
                flex: 1;
 | 
						||
                overflow-y: auto;
 | 
						||
                padding: 20px;
 | 
						||
                min-height: 300px;
 | 
						||
                max-height: 400px;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message {
 | 
						||
                margin-bottom: 15px;
 | 
						||
                display: flex;
 | 
						||
                align-items: flex-start;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message.incoming {
 | 
						||
                justify-content: flex-start;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message.outgoing {
 | 
						||
                justify-content: flex-end;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message-bubble {
 | 
						||
                max-width: 70%;
 | 
						||
                padding: 10px 15px;
 | 
						||
                border-radius: 18px;
 | 
						||
                word-wrap: break-word;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message.incoming .message-bubble {
 | 
						||
                background: #f1f1f1;
 | 
						||
                color: #333;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message.outgoing .message-bubble {
 | 
						||
                background: #0073aa;
 | 
						||
                color: white;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message-time {
 | 
						||
                font-size: 11px;
 | 
						||
                color: #666;
 | 
						||
                margin-top: 5px;
 | 
						||
                display: block;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message.incoming .message-time {
 | 
						||
                text-align: left;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .message.outgoing .message-time {
 | 
						||
                text-align: right;
 | 
						||
                color: rgba(255,255,255,0.8);
 | 
						||
            }
 | 
						||
            
 | 
						||
            .reply-form {
 | 
						||
                padding: 20px;
 | 
						||
                border-top: 1px solid #ddd;
 | 
						||
                background: #f9f9f9;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .reply-inputs textarea {
 | 
						||
                width: 100%;
 | 
						||
                resize: vertical;
 | 
						||
                border: 1px solid #ddd;
 | 
						||
                border-radius: 4px;
 | 
						||
                padding: 10px;
 | 
						||
                margin-bottom: 10px;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .reply-actions {
 | 
						||
                text-align: right;
 | 
						||
            }
 | 
						||
            
 | 
						||
            .reply-actions .button {
 | 
						||
                margin-left: 10px;
 | 
						||
            }
 | 
						||
            
 | 
						||
            #loading-messages {
 | 
						||
                text-align: center;
 | 
						||
                color: #666;
 | 
						||
                padding: 40px;
 | 
						||
            }
 | 
						||
            </style>
 | 
						||
            
 | 
						||
            <script>
 | 
						||
            jQuery(document).ready(function($) {
 | 
						||
                var currentCustomerPhone = '';
 | 
						||
                var currentBusinessPhone = '';
 | 
						||
                
 | 
						||
                // View conversation
 | 
						||
                $('.view-conversation-btn').on('click', function() {
 | 
						||
                    currentCustomerPhone = $(this).data('customer');
 | 
						||
                    currentBusinessPhone = $(this).data('business');
 | 
						||
                    loadConversation(currentCustomerPhone, currentBusinessPhone);
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Delete conversation
 | 
						||
                $('.delete-conversation-btn').on('click', function() {
 | 
						||
                    var customerPhone = $(this).data('customer');
 | 
						||
                    if (confirm('Are you sure you want to delete all messages from ' + customerPhone + '?')) {
 | 
						||
                        deleteConversation(customerPhone);
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Send reply
 | 
						||
                $('#send-reply-btn').on('click', function() {
 | 
						||
                    var message = $('#reply-message').val().trim();
 | 
						||
                    if (message && currentCustomerPhone && currentBusinessPhone) {
 | 
						||
                        sendReply(currentCustomerPhone, currentBusinessPhone, message);
 | 
						||
                    } else {
 | 
						||
                        alert('Please enter a message');
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Enter key to send
 | 
						||
                $('#reply-message').on('keydown', function(e) {
 | 
						||
                    if (e.key === 'Enter' && !e.shiftKey) {
 | 
						||
                        e.preventDefault();
 | 
						||
                        $('#send-reply-btn').click();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                function loadConversation(customerPhone, businessPhone) {
 | 
						||
                    $('#conversation-title').html('Conversation: ' + customerPhone + '<br><small style="font-weight: normal;">via ' + businessPhone + '</small>');
 | 
						||
                    $('#conversation-messages').html('<div id="loading-messages">Loading conversation...</div>');
 | 
						||
                    $('#conversation-modal').show();
 | 
						||
                    
 | 
						||
                    $.ajax({
 | 
						||
                        url: ajaxurl,
 | 
						||
                        type: 'POST',
 | 
						||
                        data: {
 | 
						||
                            action: 'twp_get_conversation',
 | 
						||
                            phone_number: customerPhone,
 | 
						||
                            nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                        },
 | 
						||
                        success: function(response) {
 | 
						||
                            if (response.success) {
 | 
						||
                                displayConversation(response.data.messages, customerPhone);
 | 
						||
                            } else {
 | 
						||
                                $('#conversation-messages').html('<div style="text-align: center; color: #d63384; padding: 40px;">Error: ' + response.data + '</div>');
 | 
						||
                            }
 | 
						||
                        },
 | 
						||
                        error: function() {
 | 
						||
                            $('#conversation-messages').html('<div style="text-align: center; color: #d63384; padding: 40px;">Failed to load conversation</div>');
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function displayConversation(messages, customerPhone) {
 | 
						||
                    var html = '';
 | 
						||
                    messages.forEach(function(message) {
 | 
						||
                        // Determine direction based on whether from_number is the customer
 | 
						||
                        var messageClass = (message.from_number === customerPhone) ? 'incoming' : 'outgoing';
 | 
						||
                        var messageTime = new Date(message.received_at).toLocaleString();
 | 
						||
                        
 | 
						||
                        html += '<div class="message ' + messageClass + '">';
 | 
						||
                        html += '<div class="message-bubble">';
 | 
						||
                        html += '<div>' + escapeHtml(message.body) + '</div>';
 | 
						||
                        html += '<small class="message-time">' + messageTime;
 | 
						||
                        if (messageClass === 'incoming') {
 | 
						||
                            html += ' • From: ' + message.from_number + ' → ' + message.to_number;
 | 
						||
                        } else {
 | 
						||
                            html += ' • Sent: ' + message.from_number + ' → ' + message.to_number;
 | 
						||
                        }
 | 
						||
                        html += '</small>';
 | 
						||
                        html += '</div>';
 | 
						||
                        html += '</div>';
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    if (html === '') {
 | 
						||
                        html = '<div style="text-align: center; color: #666; padding: 40px;">No messages found</div>';
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    $('#conversation-messages').html(html);
 | 
						||
                    
 | 
						||
                    // Scroll to bottom
 | 
						||
                    var messagesContainer = document.getElementById('conversation-messages');
 | 
						||
                    messagesContainer.scrollTop = messagesContainer.scrollHeight;
 | 
						||
                }
 | 
						||
                
 | 
						||
                function sendReply(toNumber, fromNumber, message) {
 | 
						||
                    var $button = $('#send-reply-btn');
 | 
						||
                    $button.prop('disabled', true).text('Sending...');
 | 
						||
                    
 | 
						||
                    $.ajax({
 | 
						||
                        url: ajaxurl,
 | 
						||
                        type: 'POST',
 | 
						||
                        data: {
 | 
						||
                            action: 'twp_send_sms_reply',
 | 
						||
                            to_number: toNumber,
 | 
						||
                            from_number: fromNumber,
 | 
						||
                            message: message,
 | 
						||
                            nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                        },
 | 
						||
                        success: function(response) {
 | 
						||
                            $button.prop('disabled', false).text('Send');
 | 
						||
                            
 | 
						||
                            if (response.success) {
 | 
						||
                                $('#reply-message').val('');
 | 
						||
                                // Reload conversation to show the new message
 | 
						||
                                loadConversation(currentCustomerPhone, currentBusinessPhone);
 | 
						||
                            } else {
 | 
						||
                                alert('Failed to send message: ' + response.data);
 | 
						||
                            }
 | 
						||
                        },
 | 
						||
                        error: function() {
 | 
						||
                            $button.prop('disabled', false).text('Send');
 | 
						||
                            alert('Failed to send message. Please try again.');
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function deleteConversation(phoneNumber) {
 | 
						||
                    $.ajax({
 | 
						||
                        url: ajaxurl,
 | 
						||
                        type: 'POST',
 | 
						||
                        data: {
 | 
						||
                            action: 'twp_delete_conversation',
 | 
						||
                            phone_number: phoneNumber,
 | 
						||
                            nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                        },
 | 
						||
                        success: function(response) {
 | 
						||
                            if (response.success) {
 | 
						||
                                // Remove the conversation row from the table
 | 
						||
                                $('tr[data-customer="' + phoneNumber + '"]').fadeOut(function() {
 | 
						||
                                    $(this).remove();
 | 
						||
                                    
 | 
						||
                                    // Check if table is now empty
 | 
						||
                                    if ($('.sms-inbox-container tbody tr').length === 0) {
 | 
						||
                                        $('.sms-inbox-container tbody').html(
 | 
						||
                                            '<tr><td colspan="6" style="text-align: center; padding: 20px;">No customer conversations yet</td></tr>'
 | 
						||
                                        );
 | 
						||
                                    }
 | 
						||
                                });
 | 
						||
                                
 | 
						||
                                // Close modal if it's open for this conversation
 | 
						||
                                if (currentCustomerPhone === phoneNumber) {
 | 
						||
                                    closeConversationModal();
 | 
						||
                                }
 | 
						||
                                
 | 
						||
                                // Show success message
 | 
						||
                                var deletedCount = response.data.deleted_count || 0;
 | 
						||
                                alert('Conversation deleted successfully! (' + deletedCount + ' messages removed)');
 | 
						||
                            } else {
 | 
						||
                                alert('Failed to delete conversation: ' + response.data);
 | 
						||
                            }
 | 
						||
                        },
 | 
						||
                        error: function() {
 | 
						||
                            alert('Failed to delete conversation. Please try again.');
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function escapeHtml(text) {
 | 
						||
                    var div = document.createElement('div');
 | 
						||
                    div.textContent = text;
 | 
						||
                    return div.innerHTML;
 | 
						||
                }
 | 
						||
            });
 | 
						||
            
 | 
						||
            function closeConversationModal() {
 | 
						||
                document.getElementById('conversation-modal').style.display = 'none';
 | 
						||
            }
 | 
						||
            </script>
 | 
						||
        </div>
 | 
						||
        <?php
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for deleting SMS messages
 | 
						||
     */
 | 
						||
    public function ajax_delete_sms() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $message_id = isset($_POST['message_id']) ? intval($_POST['message_id']) : 0;
 | 
						||
        
 | 
						||
        if (empty($message_id)) {
 | 
						||
            wp_send_json_error('Message ID is required');
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $table_name = $wpdb->prefix . 'twp_sms_log';
 | 
						||
        
 | 
						||
        $deleted = $wpdb->delete(
 | 
						||
            $table_name,
 | 
						||
            array('id' => $message_id),
 | 
						||
            array('%d')
 | 
						||
        );
 | 
						||
        
 | 
						||
        if ($deleted) {
 | 
						||
            wp_send_json_success('Message deleted successfully');
 | 
						||
        } else {
 | 
						||
            wp_send_json_error('Failed to delete message');
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for deleting entire SMS conversations
 | 
						||
     */
 | 
						||
    public function ajax_delete_conversation() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $phone_number = isset($_POST['phone_number']) ? sanitize_text_field($_POST['phone_number']) : '';
 | 
						||
        
 | 
						||
        if (empty($phone_number)) {
 | 
						||
            wp_send_json_error('Phone number is required');
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $table_name = $wpdb->prefix . 'twp_sms_log';
 | 
						||
        
 | 
						||
        // Delete all messages involving this phone number
 | 
						||
        $deleted = $wpdb->query($wpdb->prepare(
 | 
						||
            "DELETE FROM $table_name WHERE from_number = %s OR to_number = %s",
 | 
						||
            $phone_number, $phone_number
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if ($deleted !== false) {
 | 
						||
            wp_send_json_success([
 | 
						||
                'message' => 'Conversation deleted successfully',
 | 
						||
                'deleted_count' => $deleted
 | 
						||
            ]);
 | 
						||
        } else {
 | 
						||
            wp_send_json_error('Failed to delete conversation');
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting conversation history
 | 
						||
     */
 | 
						||
    public function ajax_get_conversation() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $phone_number = isset($_POST['phone_number']) ? sanitize_text_field($_POST['phone_number']) : '';
 | 
						||
        
 | 
						||
        if (empty($phone_number)) {
 | 
						||
            wp_send_json_error('Phone number is required');
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $table_name = $wpdb->prefix . 'twp_sms_log';
 | 
						||
        
 | 
						||
        // Get all messages involving this phone number (both incoming and outgoing)
 | 
						||
        $messages = $wpdb->get_results($wpdb->prepare(
 | 
						||
            "SELECT *, 
 | 
						||
             CASE 
 | 
						||
                WHEN from_number = %s THEN 'incoming'
 | 
						||
                ELSE 'outgoing'
 | 
						||
             END as direction
 | 
						||
             FROM $table_name 
 | 
						||
             WHERE from_number = %s OR to_number = %s
 | 
						||
             ORDER BY received_at ASC",
 | 
						||
            $phone_number, $phone_number, $phone_number
 | 
						||
        ));
 | 
						||
        
 | 
						||
        wp_send_json_success([
 | 
						||
            'messages' => $messages,
 | 
						||
            'phone_number' => $phone_number
 | 
						||
        ]);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for sending SMS replies
 | 
						||
     */
 | 
						||
    public function ajax_send_sms_reply() {
 | 
						||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
						||
        
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('Insufficient permissions');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $to_number = isset($_POST['to_number']) ? sanitize_text_field($_POST['to_number']) : '';
 | 
						||
        $from_number = isset($_POST['from_number']) ? sanitize_text_field($_POST['from_number']) : '';
 | 
						||
        $message = isset($_POST['message']) ? sanitize_textarea_field($_POST['message']) : '';
 | 
						||
        
 | 
						||
        if (empty($to_number) || empty($message)) {
 | 
						||
            wp_send_json_error('Phone number and message are required');
 | 
						||
        }
 | 
						||
        
 | 
						||
        $twilio = new TWP_Twilio_API();
 | 
						||
        $result = $twilio->send_sms($to_number, $message, $from_number);
 | 
						||
        
 | 
						||
        if ($result['success']) {
 | 
						||
            // Log the outgoing message to the database
 | 
						||
            global $wpdb;
 | 
						||
            $table_name = $wpdb->prefix . 'twp_sms_log';
 | 
						||
            
 | 
						||
            $wpdb->insert(
 | 
						||
                $table_name,
 | 
						||
                array(
 | 
						||
                    'message_sid' => $result['data']['sid'],
 | 
						||
                    'from_number' => $from_number,
 | 
						||
                    'to_number' => $to_number,
 | 
						||
                    'body' => $message,
 | 
						||
                    'received_at' => current_time('mysql')
 | 
						||
                ),
 | 
						||
                array('%s', '%s', '%s', '%s', '%s')
 | 
						||
            );
 | 
						||
            
 | 
						||
            wp_send_json_success([
 | 
						||
                'message' => 'SMS sent successfully',
 | 
						||
                'data' => $result['data']
 | 
						||
            ]);
 | 
						||
        } else {
 | 
						||
            wp_send_json_error('Failed to send SMS: ' . $result['error']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Display Browser Phone page
 | 
						||
     */
 | 
						||
    public function display_browser_phone_page() {
 | 
						||
        // Check if smart routing is configured on any phone numbers
 | 
						||
        $smart_routing_configured = $this->check_smart_routing_status();
 | 
						||
        
 | 
						||
        // Get user extension data and create personal queues if needed
 | 
						||
        $current_user_id = get_current_user_id();
 | 
						||
        global $wpdb;
 | 
						||
        $extensions_table = $wpdb->prefix . 'twp_user_extensions';
 | 
						||
        $extension_data = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT extension FROM $extensions_table WHERE user_id = %d",
 | 
						||
            $current_user_id
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$extension_data) {
 | 
						||
            TWP_User_Queue_Manager::create_user_queues($current_user_id);
 | 
						||
            $extension_data = $wpdb->get_row($wpdb->prepare(
 | 
						||
                "SELECT extension FROM $extensions_table WHERE user_id = %d",
 | 
						||
                $current_user_id
 | 
						||
            ));
 | 
						||
        }
 | 
						||
        // Get agent status and stats
 | 
						||
        $agent_status = TWP_Agent_Manager::get_agent_status($current_user_id);
 | 
						||
        $agent_stats = TWP_Agent_Manager::get_agent_stats($current_user_id);
 | 
						||
        $is_logged_in = TWP_Agent_Manager::is_agent_logged_in($current_user_id);
 | 
						||
        
 | 
						||
        ?>
 | 
						||
        <div class="wrap">
 | 
						||
            <h1>Browser Phone</h1>
 | 
						||
            <p>Make and receive calls directly from your browser using Twilio Client.</p>
 | 
						||
            
 | 
						||
            <!-- Agent Status Bar -->
 | 
						||
            <div class="agent-status-bar">
 | 
						||
                <div class="status-info">
 | 
						||
                    <strong>Extension:</strong> 
 | 
						||
                    <span class="extension-badge"><?php echo $extension_data ? esc_html($extension_data->extension) : 'Not Assigned'; ?></span>
 | 
						||
                    
 | 
						||
                    <strong>Login Status:</strong>
 | 
						||
                    <button id="login-toggle-btn" class="button <?php echo $is_logged_in ? 'button-secondary' : 'button-primary'; ?>" onclick="toggleAgentLogin()">
 | 
						||
                        <?php echo $is_logged_in ? 'Log Out' : 'Log In'; ?>
 | 
						||
                    </button>
 | 
						||
                    
 | 
						||
                    <strong>Your Status:</strong>
 | 
						||
                    <select id="agent-status-select" onchange="updateAgentStatus(this.value)" <?php echo !$is_logged_in ? 'disabled' : ''; ?>>
 | 
						||
                        <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="browser-phone-container">
 | 
						||
                <div class="phone-interface">
 | 
						||
                    <div class="phone-display">
 | 
						||
                        <div id="phone-status">Ready</div>
 | 
						||
                        <div id="phone-number-display"></div>
 | 
						||
                        <div id="call-timer" style="display: none;">00:00</div>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="phone-dialpad">
 | 
						||
                        <input type="tel" id="phone-number-input" placeholder="Enter phone number" />
 | 
						||
                        
 | 
						||
                        <div class="dialpad-grid">
 | 
						||
                            <button class="dialpad-btn" data-digit="1">1</button>
 | 
						||
                            <button class="dialpad-btn" data-digit="2">2<span>ABC</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="3">3<span>DEF</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="4">4<span>GHI</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="5">5<span>JKL</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="6">6<span>MNO</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="7">7<span>PQRS</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="8">8<span>TUV</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="9">9<span>WXYZ</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="*">*</button>
 | 
						||
                            <button class="dialpad-btn" data-digit="0">0<span>+</span></button>
 | 
						||
                            <button class="dialpad-btn" data-digit="#">#</button>
 | 
						||
                        </div>
 | 
						||
                        
 | 
						||
                        <div class="phone-controls">
 | 
						||
                            <button id="call-btn" class="button button-primary button-large">
 | 
						||
                                <span class="dashicons dashicons-phone"></span> Call
 | 
						||
                            </button>
 | 
						||
                            <button id="hangup-btn" class="button button-secondary button-large" style="display: none;">
 | 
						||
                                <span class="dashicons dashicons-no"></span> Hang Up
 | 
						||
                            </button>
 | 
						||
                            <button id="answer-btn" class="button button-primary button-large" style="display: none;">
 | 
						||
                                <span class="dashicons dashicons-phone"></span> Answer
 | 
						||
                            </button>
 | 
						||
                        </div>
 | 
						||
                        
 | 
						||
                        <!-- Call Control Panel (shown during active calls) -->
 | 
						||
                        <div class="phone-controls-extra" id="admin-call-controls-panel" style="display: none;">
 | 
						||
                            <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 15px 0;">
 | 
						||
                                <button id="admin-hold-btn" class="button" title="Put call on hold">
 | 
						||
                                    <span class="dashicons dashicons-controls-pause"></span> Hold
 | 
						||
                                </button>
 | 
						||
                                <button id="admin-transfer-btn" class="button" title="Transfer to another agent">
 | 
						||
                                    <span class="dashicons dashicons-share-alt"></span> Transfer
 | 
						||
                                </button>
 | 
						||
                                <button id="admin-requeue-btn" class="button" title="Put call back in queue">
 | 
						||
                                    <span class="dashicons dashicons-backup"></span> Requeue
 | 
						||
                                </button>
 | 
						||
                                <button id="admin-record-btn" class="button" title="Start/stop recording">
 | 
						||
                                    <span class="dashicons dashicons-controls-volumeon"></span> Record
 | 
						||
                                </button>
 | 
						||
                            </div>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <div class="phone-settings">
 | 
						||
                    <h3>Settings</h3>
 | 
						||
                    <p>
 | 
						||
                        <label for="caller-id-select">Outbound Caller ID:</label>
 | 
						||
                        <select id="caller-id-select">
 | 
						||
                            <option value="">Loading numbers...</option>
 | 
						||
                        </select>
 | 
						||
                    </p>
 | 
						||
                    <p>
 | 
						||
                        <label>
 | 
						||
                            <input type="checkbox" id="auto-answer" /> Auto-answer incoming calls
 | 
						||
                        </label>
 | 
						||
                    </p>
 | 
						||
                    <div id="browser-phone-error" class="notice notice-error" style="display: none;"></div>
 | 
						||
                    
 | 
						||
                    <div class="call-mode-toggle">
 | 
						||
                        <h4>📞 Call Reception Mode</h4>
 | 
						||
                        <p>Choose how you want to receive incoming calls:</p>
 | 
						||
                        
 | 
						||
                        <div class="mode-selection">
 | 
						||
                            <?php
 | 
						||
                            $current_user_id = get_current_user_id();
 | 
						||
                            $current_mode = get_user_meta($current_user_id, 'twp_call_mode', true);
 | 
						||
                            if (empty($current_mode)) {
 | 
						||
                                $current_mode = 'cell'; // Default to cell phone
 | 
						||
                            }
 | 
						||
                            ?>
 | 
						||
                            
 | 
						||
                            <label class="mode-option <?php echo $current_mode === 'browser' ? 'active' : ''; ?>">
 | 
						||
                                <input type="radio" name="call_mode" value="browser" <?php checked($current_mode, 'browser'); ?>>
 | 
						||
                                <div class="mode-icon">💻</div>
 | 
						||
                                <div class="mode-details">
 | 
						||
                                    <strong>Browser Phone</strong>
 | 
						||
                                    <small>Calls ring in this browser</small>
 | 
						||
                                </div>
 | 
						||
                            </label>
 | 
						||
                            
 | 
						||
                            <label class="mode-option <?php echo $current_mode === 'cell' ? 'active' : ''; ?>">
 | 
						||
                                <input type="radio" name="call_mode" value="cell" <?php checked($current_mode, 'cell'); ?>>
 | 
						||
                                <div class="mode-icon">📱</div>
 | 
						||
                                <div class="mode-details">
 | 
						||
                                    <strong>Cell Phone</strong>
 | 
						||
                                    <small>Forward to your mobile</small>
 | 
						||
                                </div>
 | 
						||
                            </label>
 | 
						||
                        </div>
 | 
						||
                        
 | 
						||
                        <div class="mode-status">
 | 
						||
                            <div id="current-mode-display">
 | 
						||
                                <strong>Current Mode:</strong> 
 | 
						||
                                <span id="mode-text"><?php echo $current_mode === 'browser' ? '💻 Browser Phone' : '📱 Cell Phone'; ?></span>
 | 
						||
                            </div>
 | 
						||
                            <button type="button" id="save-mode-btn" class="button button-primary" style="display: none;">
 | 
						||
                                Save Changes
 | 
						||
                            </button>
 | 
						||
                        </div>
 | 
						||
                        
 | 
						||
                        <div class="mode-info">
 | 
						||
                            <div class="browser-mode-info" style="display: <?php echo $current_mode === 'browser' ? 'block' : 'none'; ?>;">
 | 
						||
                                <p><strong>Browser Mode:</strong> Keep this page open to receive calls. High-quality VoIP calling.</p>
 | 
						||
                            </div>
 | 
						||
                            <div class="cell-mode-info" style="display: <?php echo $current_mode === 'cell' ? 'block' : 'none'; ?>;">
 | 
						||
                                <p><strong>Cell Mode:</strong> Calls forwarded to your mobile phone: 
 | 
						||
                                <?php 
 | 
						||
                                $user_phone = get_user_meta($current_user_id, 'twp_phone_number', true);
 | 
						||
                                echo $user_phone ? esc_html($user_phone) : '<em>Not configured</em>';
 | 
						||
                                ?>
 | 
						||
                                </p>
 | 
						||
                            </div>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <?php if (!$smart_routing_configured && current_user_can('manage_options')): ?>
 | 
						||
                    <div class="setup-info">
 | 
						||
                        <h4>📋 Setup Required</h4>
 | 
						||
                        <p>To enable mode switching, update your phone number webhook to:</p>
 | 
						||
                        <code><?php echo home_url('/wp-json/twilio-webhook/v1/smart-routing'); ?></code>
 | 
						||
                        <button type="button" class="button button-small" onclick="copyToClipboard('<?php echo home_url('/wp-json/twilio-webhook/v1/smart-routing'); ?>')">Copy</button>
 | 
						||
                        <p><small>This smart routing URL will automatically route calls based on your current mode preference.</small></p>
 | 
						||
                        <p><a href="<?php echo admin_url('admin.php?page=twilio-wp-plugin'); ?>#twiml-app-instructions" class="button button-primary">Auto-Configure</a></p>
 | 
						||
                    </div>
 | 
						||
                    <?php endif; ?>
 | 
						||
                    
 | 
						||
                    <!-- Enhanced Queue Management Section -->
 | 
						||
                    <div class="queue-management">
 | 
						||
                        <div class="queue-header">
 | 
						||
                            <h4>📞 Your Queues</h4>
 | 
						||
                            <?php if ($extension_data): ?>
 | 
						||
                                <div class="user-extension-admin">
 | 
						||
                                    📞 Your Extension: <strong><?php echo esc_html($extension_data->extension); ?></strong>
 | 
						||
                                </div>
 | 
						||
                            <?php endif; ?>
 | 
						||
                        </div>
 | 
						||
                        <div id="admin-queue-list">
 | 
						||
                            <div class="queue-loading">Loading your queues...</div>
 | 
						||
                        </div>
 | 
						||
                        <div class="queue-actions">
 | 
						||
                            <button type="button" id="admin-refresh-queues" class="button button-secondary">
 | 
						||
                                Refresh Queues
 | 
						||
                            </button>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
            
 | 
						||
            <style>
 | 
						||
            .browser-phone-container {
 | 
						||
                display: flex;
 | 
						||
                gap: 30px;
 | 
						||
                margin-top: 20px;
 | 
						||
            }
 | 
						||
            .phone-interface {
 | 
						||
                background: #f5f5f5;
 | 
						||
                border-radius: 10px;
 | 
						||
                padding: 20px;
 | 
						||
                width: 320px;
 | 
						||
            }
 | 
						||
            .phone-display {
 | 
						||
                background: #333;
 | 
						||
                color: white;
 | 
						||
                padding: 20px;
 | 
						||
                border-radius: 5px;
 | 
						||
                text-align: center;
 | 
						||
                margin-bottom: 20px;
 | 
						||
            }
 | 
						||
            #phone-status {
 | 
						||
                font-size: 14px;
 | 
						||
                color: #4CAF50;
 | 
						||
                margin-bottom: 10px;
 | 
						||
            }
 | 
						||
            #phone-number-display {
 | 
						||
                font-size: 18px;
 | 
						||
                min-height: 25px;
 | 
						||
            }
 | 
						||
            #call-timer {
 | 
						||
                font-size: 16px;
 | 
						||
                margin-top: 10px;
 | 
						||
            }
 | 
						||
            #phone-number-input {
 | 
						||
                width: 100%;
 | 
						||
                padding: 10px;
 | 
						||
                font-size: 18px;
 | 
						||
                text-align: center;
 | 
						||
                margin-bottom: 20px;
 | 
						||
            }
 | 
						||
            .dialpad-grid {
 | 
						||
                display: grid;
 | 
						||
                grid-template-columns: repeat(3, 1fr);
 | 
						||
                gap: 10px;
 | 
						||
                margin-bottom: 20px;
 | 
						||
            }
 | 
						||
            .dialpad-btn {
 | 
						||
                padding: 15px;
 | 
						||
                font-size: 20px;
 | 
						||
                border: 1px solid #ddd;
 | 
						||
                background: white;
 | 
						||
                border-radius: 5px;
 | 
						||
                cursor: pointer;
 | 
						||
                position: relative;
 | 
						||
            }
 | 
						||
            .dialpad-btn:hover {
 | 
						||
                background: #f0f0f0;
 | 
						||
            }
 | 
						||
            .dialpad-btn span {
 | 
						||
                display: block;
 | 
						||
                font-size: 10px;
 | 
						||
                color: #666;
 | 
						||
                margin-top: 2px;
 | 
						||
            }
 | 
						||
            .phone-controls {
 | 
						||
                text-align: center;
 | 
						||
                margin-bottom: 10px;
 | 
						||
            }
 | 
						||
            .phone-controls .button-large {
 | 
						||
                width: 100%;
 | 
						||
                height: 50px;
 | 
						||
                font-size: 16px;
 | 
						||
            }
 | 
						||
            .phone-controls-extra {
 | 
						||
                display: flex;
 | 
						||
                gap: 10px;
 | 
						||
                justify-content: center;
 | 
						||
            }
 | 
						||
            .phone-settings {
 | 
						||
                flex: 1;
 | 
						||
                max-width: 400px;
 | 
						||
            }
 | 
						||
            .incoming-calls-info {
 | 
						||
                background: #e7f3ff;
 | 
						||
                padding: 15px;
 | 
						||
                border-radius: 4px;
 | 
						||
                border-left: 4px solid #0073aa;
 | 
						||
                margin-top: 20px;
 | 
						||
            }
 | 
						||
            .incoming-calls-info h4 {
 | 
						||
                margin-top: 0;
 | 
						||
                color: #0073aa;
 | 
						||
            }
 | 
						||
            .call-mode-toggle {
 | 
						||
                background: #f0f8ff;
 | 
						||
                padding: 20px;
 | 
						||
                border-radius: 8px;
 | 
						||
                border-left: 4px solid #2196F3;
 | 
						||
                margin-top: 20px;
 | 
						||
            }
 | 
						||
            .call-mode-toggle h4 {
 | 
						||
                margin-top: 0;
 | 
						||
                color: #1976D2;
 | 
						||
            }
 | 
						||
            .mode-selection {
 | 
						||
                display: flex;
 | 
						||
                gap: 15px;
 | 
						||
                margin: 15px 0;
 | 
						||
            }
 | 
						||
            .mode-option {
 | 
						||
                display: flex;
 | 
						||
                align-items: center;
 | 
						||
                padding: 15px;
 | 
						||
                border: 2px solid #ddd;
 | 
						||
                border-radius: 8px;
 | 
						||
                cursor: pointer;
 | 
						||
                transition: all 0.3s ease;
 | 
						||
                flex: 1;
 | 
						||
                background: white;
 | 
						||
            }
 | 
						||
            .mode-option:hover {
 | 
						||
                border-color: #2196F3;
 | 
						||
                background: #f5f9ff;
 | 
						||
            }
 | 
						||
            .mode-option.active {
 | 
						||
                border-color: #2196F3;
 | 
						||
                background: #e3f2fd;
 | 
						||
                box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
 | 
						||
            }
 | 
						||
            .mode-option input[type="radio"] {
 | 
						||
                margin: 0;
 | 
						||
                margin-right: 12px;
 | 
						||
            }
 | 
						||
            .mode-icon {
 | 
						||
                font-size: 24px;
 | 
						||
                margin-right: 12px;
 | 
						||
            }
 | 
						||
            .mode-details {
 | 
						||
                flex: 1;
 | 
						||
            }
 | 
						||
            .mode-details strong {
 | 
						||
                display: block;
 | 
						||
                margin-bottom: 2px;
 | 
						||
            }
 | 
						||
            .mode-details small {
 | 
						||
                color: #666;
 | 
						||
                font-size: 12px;
 | 
						||
            }
 | 
						||
            .mode-status {
 | 
						||
                display: flex;
 | 
						||
                align-items: center;
 | 
						||
                justify-content: space-between;
 | 
						||
                margin: 15px 0;
 | 
						||
                padding: 10px;
 | 
						||
                background: white;
 | 
						||
                border-radius: 4px;
 | 
						||
            }
 | 
						||
            .mode-info {
 | 
						||
                margin-top: 10px;
 | 
						||
            }
 | 
						||
            .setup-info {
 | 
						||
                background: #fff3cd;
 | 
						||
                padding: 15px;
 | 
						||
                border-radius: 4px;
 | 
						||
                border-left: 4px solid #ffc107;
 | 
						||
                margin-top: 20px;
 | 
						||
            }
 | 
						||
            .setup-info h4 {
 | 
						||
                margin-top: 0;
 | 
						||
                color: #856404;
 | 
						||
            }
 | 
						||
            .queue-management {
 | 
						||
                background: #f0f8ff;
 | 
						||
                padding: 20px;
 | 
						||
                border-radius: 8px;
 | 
						||
                border-left: 4px solid #2196F3;
 | 
						||
                margin-top: 20px;
 | 
						||
            }
 | 
						||
            .queue-management h4 {
 | 
						||
                margin-top: 0;
 | 
						||
                color: #1976D2;
 | 
						||
            }
 | 
						||
            .queue-header {
 | 
						||
                display: flex;
 | 
						||
                justify-content: space-between;
 | 
						||
                align-items: center;
 | 
						||
                margin-bottom: 15px;
 | 
						||
            }
 | 
						||
            .user-extension-admin {
 | 
						||
                background: #e8f4f8;
 | 
						||
                padding: 6px 12px;
 | 
						||
                border-radius: 4px;
 | 
						||
                font-size: 13px;
 | 
						||
                color: #2c5282;
 | 
						||
            }
 | 
						||
            .queue-item {
 | 
						||
                display: flex;
 | 
						||
                justify-content: space-between;
 | 
						||
                align-items: center;
 | 
						||
                padding: 12px;
 | 
						||
                background: white;
 | 
						||
                border: 1px solid #ddd;
 | 
						||
                border-radius: 4px;
 | 
						||
                margin-bottom: 10px;
 | 
						||
                position: relative;
 | 
						||
            }
 | 
						||
            .queue-item.queue-type-personal {
 | 
						||
                border-left: 4px solid #28a745;
 | 
						||
            }
 | 
						||
            .queue-item.queue-type-hold {
 | 
						||
                border-left: 4px solid #ffc107;
 | 
						||
            }
 | 
						||
            .queue-item.queue-type-general {
 | 
						||
                border-left: 4px solid #007bff;
 | 
						||
            }
 | 
						||
            .queue-item.has-calls {
 | 
						||
                background: #fff3cd;
 | 
						||
                border-color: #ffeaa7;
 | 
						||
            }
 | 
						||
            .queue-name {
 | 
						||
                display: flex;
 | 
						||
                align-items: center;
 | 
						||
                gap: 8px;
 | 
						||
                font-weight: 600;
 | 
						||
                color: #333;
 | 
						||
            }
 | 
						||
            .queue-type-icon {
 | 
						||
                font-size: 16px;
 | 
						||
            }
 | 
						||
            .queue-type-personal .queue-name {
 | 
						||
                color: #155724;
 | 
						||
            }
 | 
						||
            .queue-type-hold .queue-name {
 | 
						||
                color: #856404;
 | 
						||
            }
 | 
						||
            .queue-info {
 | 
						||
                flex: 1;
 | 
						||
            }
 | 
						||
            .queue-details {
 | 
						||
                font-size: 12px;
 | 
						||
                color: #666;
 | 
						||
                margin-top: 4px;
 | 
						||
            }
 | 
						||
            .queue-waiting {
 | 
						||
                display: inline-block;
 | 
						||
                font-size: 12px;
 | 
						||
                color: #666;
 | 
						||
                margin-right: 10px;
 | 
						||
            }
 | 
						||
            .queue-waiting.has-calls {
 | 
						||
                color: #d63384;
 | 
						||
                font-weight: bold;
 | 
						||
                background: #fff;
 | 
						||
                padding: 2px 6px;
 | 
						||
                border-radius: 3px;
 | 
						||
                border: 1px solid #f8d7da;
 | 
						||
            }
 | 
						||
            .queue-loading {
 | 
						||
                text-align: center;
 | 
						||
                color: #666;
 | 
						||
                font-style: italic;
 | 
						||
                padding: 20px;
 | 
						||
            }
 | 
						||
            .queue-actions {
 | 
						||
                margin-top: 15px;
 | 
						||
                text-align: center;
 | 
						||
            }
 | 
						||
            </style>
 | 
						||
            
 | 
						||
            <!-- Twilio Voice SDK v2 from unpkg CDN -->
 | 
						||
            <script src="https://unpkg.com/@twilio/voice-sdk@2.11.0/dist/twilio.min.js"></script>
 | 
						||
            <script>
 | 
						||
            jQuery(document).ready(function($) {
 | 
						||
                var device = null;
 | 
						||
                var currentCall = null;
 | 
						||
                var callTimer = null;
 | 
						||
                var callStartTime = null;
 | 
						||
                var tokenRefreshTimer = null;
 | 
						||
                var tokenExpiry = null;
 | 
						||
                
 | 
						||
                // Wait for SDK to load
 | 
						||
                function waitForTwilioSDK(callback) {
 | 
						||
                    if (typeof Twilio !== 'undefined' && Twilio.Device) {
 | 
						||
                        callback();
 | 
						||
                    } else {
 | 
						||
                        console.log('Waiting for Twilio Voice SDK to load...');
 | 
						||
                        setTimeout(function() {
 | 
						||
                            waitForTwilioSDK(callback);
 | 
						||
                        }, 100);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Initialize the browser phone
 | 
						||
                function initializeBrowserPhone() {
 | 
						||
                    $('#phone-status').text('Initializing...');
 | 
						||
                    
 | 
						||
                    // Wait for SDK before proceeding
 | 
						||
                    waitForTwilioSDK(function() {
 | 
						||
                        // Get capability token (access token for v2)
 | 
						||
                        $.post(ajaxurl, {
 | 
						||
                            action: 'twp_generate_capability_token',
 | 
						||
                            nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                        }, function(response) {
 | 
						||
                            if (response.success) {
 | 
						||
                                $('#browser-phone-error').hide();
 | 
						||
                                setupTwilioDevice(response.data.token);
 | 
						||
                                // Set token expiry and schedule refresh
 | 
						||
                                tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
 | 
						||
                                scheduleTokenRefresh();
 | 
						||
                            } else {
 | 
						||
                                // WordPress wp_send_json_error sends the error message as response.data
 | 
						||
                                var errorMsg = response.data || response.error || 'Unknown error';
 | 
						||
                                showError('Failed to initialize: ' + errorMsg);
 | 
						||
                            }
 | 
						||
                        }).fail(function() {
 | 
						||
                            showError('Failed to connect to server');
 | 
						||
                        });
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Request microphone and speaker permissions
 | 
						||
                async function requestMediaPermissions() {
 | 
						||
                    try {
 | 
						||
                        console.log('Requesting media permissions...');
 | 
						||
                        
 | 
						||
                        // Request microphone permission
 | 
						||
                        const stream = await navigator.mediaDevices.getUserMedia({
 | 
						||
                            audio: true,
 | 
						||
                            video: false
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        // Stop the stream immediately as we just needed permission
 | 
						||
                        stream.getTracks().forEach(track => track.stop());
 | 
						||
                        
 | 
						||
                        console.log('Media permissions granted');
 | 
						||
                        return true;
 | 
						||
                    } catch (error) {
 | 
						||
                        console.error('Media permission denied or not available:', error);
 | 
						||
                        
 | 
						||
                        // Show user-friendly error message
 | 
						||
                        let errorMessage = 'Microphone access is required for browser phone functionality. ';
 | 
						||
                        
 | 
						||
                        if (error.name === 'NotAllowedError') {
 | 
						||
                            errorMessage += 'Please allow microphone access in your browser settings and refresh the page.';
 | 
						||
                        } else if (error.name === 'NotFoundError') {
 | 
						||
                            errorMessage += 'No microphone found. Please connect a microphone and try again.';
 | 
						||
                        } else {
 | 
						||
                            errorMessage += 'Please check your browser settings and try again.';
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        $('#browser-phone-error').show().find('.notice-message').text(errorMessage);
 | 
						||
                        $('#browser-phone-status').text('Permission denied').removeClass('online').addClass('offline');
 | 
						||
                        return false;
 | 
						||
                    }
 | 
						||
                }
 | 
						||
 | 
						||
                async function setupTwilioDevice(token) {
 | 
						||
                    try {
 | 
						||
                        // Check if Twilio SDK is available
 | 
						||
                        if (typeof Twilio === 'undefined' || !Twilio.Device) {
 | 
						||
                            throw new Error('Twilio Voice SDK not loaded');
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        // Request media permissions before setting up device
 | 
						||
                        const hasPermissions = await requestMediaPermissions();
 | 
						||
                        if (!hasPermissions) {
 | 
						||
                            return; // Stop setup if permissions denied
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        // Clean up existing device if any
 | 
						||
                        if (device) {
 | 
						||
                            await device.destroy();
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        // Setup Twilio Voice SDK v2 Device
 | 
						||
                        // Note: Voice SDK v2 uses Twilio.Device directly, not Twilio.Voice.Device
 | 
						||
                        device = new Twilio.Device(token, {
 | 
						||
                            logLevel: 1, // 0 = TRACE, 1 = DEBUG
 | 
						||
                            codecPreferences: ['opus', 'pcmu'],
 | 
						||
                            edge: 'sydney' // Or closest edge location
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        // Set up event handlers BEFORE registering
 | 
						||
                        // Device registered and ready
 | 
						||
                        device.on('registered', function() {
 | 
						||
                            console.log('Device registered successfully');
 | 
						||
                            $('#phone-status').text('Ready').css('color', '#4CAF50');
 | 
						||
                            $('#call-btn').prop('disabled', false);
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        // Handle errors
 | 
						||
                        device.on('error', function(error) {
 | 
						||
                            console.error('Twilio Device Error:', error);
 | 
						||
                            
 | 
						||
                            var errorMsg = error.message || error.toString();
 | 
						||
                            
 | 
						||
                            // Provide specific help for common errors
 | 
						||
                            if (errorMsg.includes('valid callerId must be provided')) {
 | 
						||
                                errorMsg = 'Caller ID error: Make sure you select a verified Twilio phone number as Caller ID. The number must be purchased through your Twilio account.';
 | 
						||
                            } else if (errorMsg.includes('TwiML App')) {
 | 
						||
                                errorMsg = 'TwiML App error: Check that your TwiML App SID is correctly configured in Settings.';
 | 
						||
                            } else if (errorMsg.includes('token') || errorMsg.includes('Token')) {
 | 
						||
                                errorMsg = 'Token error: ' + errorMsg + ' - The page will automatically try to refresh the token.';
 | 
						||
                                // Try to reinitialize after token error
 | 
						||
                                setTimeout(initializeBrowserPhone, 5000);
 | 
						||
                            }
 | 
						||
                            
 | 
						||
                            showError(errorMsg);
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        // Handle incoming calls
 | 
						||
                        device.on('incoming', function(call) {
 | 
						||
                            currentCall = call;
 | 
						||
                            $('#phone-status').text('Incoming Call').css('color', '#FF9800');
 | 
						||
                            $('#phone-number-display').text(call.parameters.From || 'Unknown Number');
 | 
						||
                            $('#call-btn').hide();
 | 
						||
                            $('#answer-btn').show();
 | 
						||
                            
 | 
						||
                            // Setup call event handlers
 | 
						||
                            setupCallHandlers(call);
 | 
						||
                            
 | 
						||
                            if ($('#auto-answer').is(':checked')) {
 | 
						||
                                call.accept();
 | 
						||
                            }
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        // Token about to expire
 | 
						||
                        device.on('tokenWillExpire', function() {
 | 
						||
                            console.log('Token will expire soon, refreshing...');
 | 
						||
                            refreshToken();
 | 
						||
                        });
 | 
						||
                        
 | 
						||
                        // Register device AFTER setting up event handlers
 | 
						||
                        await device.register();
 | 
						||
                        
 | 
						||
                    } catch (error) {
 | 
						||
                        console.error('Error setting up Twilio Device:', error);
 | 
						||
                        showError('Failed to setup device: ' + error.message);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                
 | 
						||
                function setupCallHandlers(call) {
 | 
						||
                    // Call accepted/connected
 | 
						||
                    call.on('accept', function() {
 | 
						||
                        $('#phone-status').text('Connected').css('color', '#2196F3');
 | 
						||
                        $('#call-btn').hide();
 | 
						||
                        $('#answer-btn').hide();
 | 
						||
                        $('#hangup-btn').show();
 | 
						||
                        $('#phone-controls-extra').show();
 | 
						||
                        $('#admin-call-controls-panel').show();
 | 
						||
                        startCallTimer();
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    // Call disconnected
 | 
						||
                    call.on('disconnect', function() {
 | 
						||
                        currentCall = null;
 | 
						||
                        $('#phone-status').text('Ready').css('color', '#4CAF50');
 | 
						||
                        $('#hangup-btn').hide();
 | 
						||
                        $('#answer-btn').hide();
 | 
						||
                        $('#call-btn').show();
 | 
						||
                        $('#phone-controls-extra').hide();
 | 
						||
                        $('#admin-call-controls-panel').hide();
 | 
						||
                        $('#call-timer').hide();
 | 
						||
                        stopCallTimer();
 | 
						||
                        
 | 
						||
                        // Reset button states
 | 
						||
                        $('#admin-hold-btn').text('Hold').removeClass('btn-active');
 | 
						||
                        $('#admin-record-btn').text('Record').removeClass('btn-active');
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    // Call rejected
 | 
						||
                    call.on('reject', function() {
 | 
						||
                        currentCall = null;
 | 
						||
                        $('#phone-status').text('Ready').css('color', '#4CAF50');
 | 
						||
                        $('#answer-btn').hide();
 | 
						||
                        $('#call-btn').show();
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    // Call cancelled (by caller before answer)
 | 
						||
                    call.on('cancel', function() {
 | 
						||
                        currentCall = null;
 | 
						||
                        $('#phone-status').text('Missed Call').css('color', '#FF9800');
 | 
						||
                        $('#answer-btn').hide();
 | 
						||
                        $('#call-btn').show();
 | 
						||
                        setTimeout(function() {
 | 
						||
                            $('#phone-status').text('Ready').css('color', '#4CAF50');
 | 
						||
                        }, 3000);
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function refreshToken() {
 | 
						||
                    console.log('Refreshing capability token...');
 | 
						||
                    
 | 
						||
                    // Don't refresh if currently in a call
 | 
						||
                    if (currentCall) {
 | 
						||
                        console.log('Currently in call, postponing token refresh');
 | 
						||
                        setTimeout(refreshToken, 60000); // Retry in 1 minute
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_generate_capability_token',
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success && device) {
 | 
						||
                            console.log('Token refreshed successfully');
 | 
						||
                            device.updateToken(response.data.token);
 | 
						||
                            // Update token expiry and schedule next refresh
 | 
						||
                            tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
 | 
						||
                            scheduleTokenRefresh();
 | 
						||
                        } else {
 | 
						||
                            console.error('Failed to refresh token:', response.data);
 | 
						||
                            showError('Failed to refresh connection. Please refresh the page.');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        console.error('Failed to refresh token - network error');
 | 
						||
                        // Retry in 30 seconds
 | 
						||
                        setTimeout(refreshToken, 30000);
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                /**
 | 
						||
                 * Schedule token refresh
 | 
						||
                 * Refreshes token 5 minutes before expiry
 | 
						||
                 */
 | 
						||
                function scheduleTokenRefresh() {
 | 
						||
                    // Clear any existing timer
 | 
						||
                    if (tokenRefreshTimer) {
 | 
						||
                        clearTimeout(tokenRefreshTimer);
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    if (!tokenExpiry) {
 | 
						||
                        console.error('Token expiry time not set');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Calculate time until refresh (5 minutes before expiry)
 | 
						||
                    var refreshBuffer = 5 * 60 * 1000; // 5 minutes in milliseconds
 | 
						||
                    var timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer;
 | 
						||
                    
 | 
						||
                    if (timeUntilRefresh <= 0) {
 | 
						||
                        // Token needs refresh immediately
 | 
						||
                        refreshToken();
 | 
						||
                    } else {
 | 
						||
                        // Schedule refresh
 | 
						||
                        console.log('Scheduling token refresh in', Math.round(timeUntilRefresh / 1000), 'seconds');
 | 
						||
                        tokenRefreshTimer = setTimeout(refreshToken, timeUntilRefresh);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                
 | 
						||
                function showError(message) {
 | 
						||
                    $('#browser-phone-error').html('<p><strong>Error:</strong> ' + message + '</p>').show();
 | 
						||
                    $('#phone-status').text('Error').css('color', '#f44336');
 | 
						||
                }
 | 
						||
                
 | 
						||
                function startCallTimer() {
 | 
						||
                    callStartTime = new Date();
 | 
						||
                    $('#call-timer').show();
 | 
						||
                    
 | 
						||
                    callTimer = setInterval(function() {
 | 
						||
                        var elapsed = Math.floor((new Date() - callStartTime) / 1000);
 | 
						||
                        var minutes = Math.floor(elapsed / 60);
 | 
						||
                        var seconds = elapsed % 60;
 | 
						||
                        $('#call-timer').text(
 | 
						||
                            (minutes < 10 ? '0' : '') + minutes + ':' + 
 | 
						||
                            (seconds < 10 ? '0' : '') + seconds
 | 
						||
                        );
 | 
						||
                    }, 1000);
 | 
						||
                }
 | 
						||
                
 | 
						||
                function stopCallTimer() {
 | 
						||
                    if (callTimer) {
 | 
						||
                        clearInterval(callTimer);
 | 
						||
                        callTimer = null;
 | 
						||
                    }
 | 
						||
                    $('#call-timer').text('00:00');
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Load phone numbers for caller ID
 | 
						||
                $.post(ajaxurl, {
 | 
						||
                    action: 'twp_get_phone_numbers',
 | 
						||
                    nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                }, function(response) {
 | 
						||
                    if (response.success) {
 | 
						||
                        var options = '<option value="">Select caller ID...</option>';
 | 
						||
                        response.data.forEach(function(number) {
 | 
						||
                            options += '<option value="' + number.phone_number + '">' + number.phone_number + '</option>';
 | 
						||
                        });
 | 
						||
                        $('#caller-id-select').html(options);
 | 
						||
                    } else {
 | 
						||
                        console.error('Failed to load phone numbers:', response.data || response.error);
 | 
						||
                        $('#caller-id-select').html('<option value="">Error loading numbers</option>');
 | 
						||
                    }
 | 
						||
                }).fail(function(xhr, status, error) {
 | 
						||
                    console.error('Failed to load phone numbers - network error:', error);
 | 
						||
                    $('#caller-id-select').html('<option value="">Error loading numbers</option>');
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Dialpad functionality
 | 
						||
                $('.dialpad-btn').on('click', function() {
 | 
						||
                    var digit = $(this).data('digit');
 | 
						||
                    var currentVal = $('#phone-number-input').val();
 | 
						||
                    $('#phone-number-input').val(currentVal + digit);
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Call button
 | 
						||
                $('#call-btn').on('click', async function() {
 | 
						||
                    var phoneNumber = $('#phone-number-input').val().trim();
 | 
						||
                    var callerId = $('#caller-id-select').val();
 | 
						||
                    
 | 
						||
                    if (!phoneNumber) {
 | 
						||
                        alert('Please enter a phone number');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    if (!callerId) {
 | 
						||
                        alert('Please select a caller ID number. This must be a verified Twilio phone number.');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    if (!device) {
 | 
						||
                        alert('Phone is not initialized. Please refresh the page.');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Format phone number
 | 
						||
                    phoneNumber = phoneNumber.replace(/\D/g, '');
 | 
						||
                    if (phoneNumber.length === 10) {
 | 
						||
                        phoneNumber = '+1' + phoneNumber;
 | 
						||
                    } else if (phoneNumber.length === 11 && phoneNumber.charAt(0) === '1') {
 | 
						||
                        phoneNumber = '+' + phoneNumber;
 | 
						||
                    } else if (!phoneNumber.startsWith('+')) {
 | 
						||
                        phoneNumber = '+' + phoneNumber;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    $('#phone-number-display').text(phoneNumber);
 | 
						||
                    $('#phone-status').text('Calling...').css('color', '#FF9800');
 | 
						||
                    
 | 
						||
                    try {
 | 
						||
                        var params = {
 | 
						||
                            To: phoneNumber,
 | 
						||
                            From: callerId
 | 
						||
                        };
 | 
						||
                        
 | 
						||
                        console.log('Making call with params:', params);
 | 
						||
                        currentCall = await device.connect({params: params});
 | 
						||
                        setupCallHandlers(currentCall);
 | 
						||
                    } catch (error) {
 | 
						||
                        console.error('Call error:', error);
 | 
						||
                        showError('Failed to make call: ' + error.message);
 | 
						||
                        $('#phone-status').text('Ready').css('color', '#4CAF50');
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Hangup button
 | 
						||
                $('#hangup-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        currentCall.disconnect();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Answer button
 | 
						||
                $('#answer-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        currentCall.accept();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Mute button
 | 
						||
                $('#mute-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        var muted = currentCall.isMuted();
 | 
						||
                        currentCall.mute(!muted);
 | 
						||
                        $(this).text(muted ? 'Mute' : 'Unmute');
 | 
						||
                        $(this).find('.dashicons').toggleClass('dashicons-microphone dashicons-microphone');
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Admin call control buttons
 | 
						||
                $('#admin-hold-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        adminToggleHold();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                $('#admin-transfer-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        adminShowTransferDialog();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                $('#admin-requeue-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        adminShowRequeueDialog();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                $('#admin-record-btn').on('click', function() {
 | 
						||
                    if (currentCall) {
 | 
						||
                        adminToggleRecording();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Check if SDK loaded and initialize
 | 
						||
                $(window).on('load', function() {
 | 
						||
                    setTimeout(function() {
 | 
						||
                        if (typeof Twilio === 'undefined') {
 | 
						||
                            showError('Twilio Voice SDK failed to load. Please check your internet connection and try refreshing the page.');
 | 
						||
                            console.error('Twilio SDK not found. Script may be blocked or failed to load.');
 | 
						||
                        } else {
 | 
						||
                            console.log('Twilio SDK loaded successfully');
 | 
						||
                            initializeBrowserPhone();
 | 
						||
                        }
 | 
						||
                    }, 1000);
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Clean up on page unload
 | 
						||
                $(window).on('beforeunload', function() {
 | 
						||
                    if (tokenRefreshTimer) {
 | 
						||
                        clearTimeout(tokenRefreshTimer);
 | 
						||
                    }
 | 
						||
                    if (device) {
 | 
						||
                        device.destroy();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Mode switching functionality
 | 
						||
                $('input[name="call_mode"]').on('change', function() {
 | 
						||
                    var selectedMode = $(this).val();
 | 
						||
                    var currentMode = $('#mode-text').text().includes('Browser') ? 'browser' : 'cell';
 | 
						||
                    
 | 
						||
                    if (selectedMode !== currentMode) {
 | 
						||
                        $('#save-mode-btn').show();
 | 
						||
                        
 | 
						||
                        // Update visual feedback
 | 
						||
                        $('.mode-option').removeClass('active');
 | 
						||
                        $(this).closest('.mode-option').addClass('active');
 | 
						||
                        
 | 
						||
                        // Update mode display
 | 
						||
                        var modeText = selectedMode === 'browser' ? '💻 Browser Phone' : '📱 Cell Phone';
 | 
						||
                        $('#mode-text').text(modeText + ' (unsaved)').css('color', '#ff9800');
 | 
						||
                        
 | 
						||
                        // Show appropriate info
 | 
						||
                        $('.mode-info > div').hide();
 | 
						||
                        $('.' + selectedMode + '-mode-info').show();
 | 
						||
                    }
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Enhanced queue management functionality
 | 
						||
                var adminUserQueues = [];
 | 
						||
                
 | 
						||
                function loadAdminQueues() {
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_get_agent_queues',
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            adminUserQueues = response.data;
 | 
						||
                            displayAdminQueues();
 | 
						||
                        } else {
 | 
						||
                            $('#admin-queue-list').html('<div class="queue-error">Failed to load queues: ' + response.data + '</div>');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        $('#admin-queue-list').html('<div class="queue-error">Failed to load queues</div>');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function displayAdminQueues() {
 | 
						||
                    var $queueList = $('#admin-queue-list');
 | 
						||
                    
 | 
						||
                    if (adminUserQueues.length === 0) {
 | 
						||
                        $queueList.html('<div class="queue-loading">No queues assigned to you.</div>');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    var html = '';
 | 
						||
                    adminUserQueues.forEach(function(queue) {
 | 
						||
                        var hasWaiting = parseInt(queue.current_waiting) > 0;
 | 
						||
                        var waitingCount = queue.current_waiting || 0;
 | 
						||
                        var queueType = queue.queue_type || 'general';
 | 
						||
                        
 | 
						||
                        // Generate queue type indicator
 | 
						||
                        var typeIndicator = '';
 | 
						||
                        var typeDescription = '';
 | 
						||
                        if (queueType === 'personal') {
 | 
						||
                            typeIndicator = '👤';
 | 
						||
                            typeDescription = queue.extension ? ' (Ext: ' + queue.extension + ')' : '';
 | 
						||
                        } else if (queueType === 'hold') {
 | 
						||
                            typeIndicator = '⏸️';
 | 
						||
                            typeDescription = ' (Hold)';
 | 
						||
                        } else {
 | 
						||
                            typeIndicator = '📋';
 | 
						||
                            typeDescription = ' (Team)';
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        html += '<div class="queue-item queue-type-' + queueType + (hasWaiting ? ' has-calls' : '') + '" data-queue-id="' + queue.id + '">';
 | 
						||
                        html += '<div class="queue-info">';
 | 
						||
                        html += '<div class="queue-name">';
 | 
						||
                        html += '<span class="queue-type-icon">' + typeIndicator + '</span>';
 | 
						||
                        html += queue.queue_name + typeDescription;
 | 
						||
                        html += '</div>';
 | 
						||
                        html += '<div class="queue-details">';
 | 
						||
                        html += '<span class="queue-waiting' + (hasWaiting ? ' has-calls' : '') + '">';
 | 
						||
                        html += waitingCount + ' waiting';
 | 
						||
                        html += '</span>';
 | 
						||
                        html += '<span class="queue-capacity">Max: ' + queue.max_size + '</span>';
 | 
						||
                        html += '</div>';
 | 
						||
                        html += '</div>';
 | 
						||
                        html += '<button type="button" class="button button-small accept-queue-call" ';
 | 
						||
                        html += 'data-queue-id="' + queue.id + '"';
 | 
						||
                        html += (hasWaiting ? '' : ' disabled');
 | 
						||
                        html += '>Accept Next Call</button>';
 | 
						||
                        html += '</div>';
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $queueList.html(html);
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Accept queue call functionality (using event delegation)
 | 
						||
                $(document).on('click', '.accept-queue-call', function() {
 | 
						||
                    var queueId = $(this).data('queue-id');
 | 
						||
                    var $button = $(this);
 | 
						||
                    
 | 
						||
                    $button.prop('disabled', true).text('Connecting...');
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_accept_next_queue_call',
 | 
						||
                        queue_id: queueId,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            showNotice('Connecting to next caller...', 'success');
 | 
						||
                            // Refresh queue status after accepting call
 | 
						||
                            setTimeout(loadAdminQueues, 1000);
 | 
						||
                        } else {
 | 
						||
                            showNotice(response.data || 'No calls waiting in this queue', 'info');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        showNotice('Failed to accept queue call', 'error');
 | 
						||
                    }).always(function() {
 | 
						||
                        $button.prop('disabled', false).text('Accept Next Call');
 | 
						||
                    });
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Refresh queues button
 | 
						||
                $('#admin-refresh-queues').on('click', function() {
 | 
						||
                    loadAdminQueues();
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Load queue status on page load and refresh every 5 seconds
 | 
						||
                loadAdminQueues();
 | 
						||
                setInterval(loadAdminQueues, 5000);
 | 
						||
                
 | 
						||
                // Save mode button
 | 
						||
                $('#save-mode-btn').on('click', function() {
 | 
						||
                    var button = $(this);
 | 
						||
                    var selectedMode = $('input[name="call_mode"]:checked').val();
 | 
						||
                    
 | 
						||
                    button.prop('disabled', true).text('Saving...');
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_save_call_mode',
 | 
						||
                        mode: selectedMode,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            var modeText = selectedMode === 'browser' ? '💻 Browser Phone' : '📱 Cell Phone';
 | 
						||
                            $('#mode-text').text(modeText).css('color', '#333');
 | 
						||
                            $('#save-mode-btn').hide();
 | 
						||
                            
 | 
						||
                            // Show success message
 | 
						||
                            var successMsg = $('<div class="notice notice-success" style="margin: 10px 0; padding: 10px;"><p>Call mode updated successfully!</p></div>');
 | 
						||
                            $('.mode-status').after(successMsg);
 | 
						||
                            setTimeout(function() {
 | 
						||
                                successMsg.fadeOut();
 | 
						||
                            }, 3000);
 | 
						||
                        } else {
 | 
						||
                            alert('Failed to save mode: ' + (response.error || 'Unknown error'));
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        alert('Failed to save mode. Please try again.');
 | 
						||
                    }).always(function() {
 | 
						||
                        button.prop('disabled', false).text('Save Changes');
 | 
						||
                    });
 | 
						||
                });
 | 
						||
                
 | 
						||
                // Admin call control functions
 | 
						||
                var adminIsOnHold = false;
 | 
						||
                var adminIsRecording = false;
 | 
						||
                var adminRecordingSid = null;
 | 
						||
                
 | 
						||
                function adminToggleHold() {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    var callSid = currentCall.parameters.CallSid || currentCall.customParameters.CallSid;
 | 
						||
                    var $holdBtn = $('#admin-hold-btn');
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_toggle_hold',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        hold: !adminIsOnHold,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            adminIsOnHold = !adminIsOnHold;
 | 
						||
                            if (adminIsOnHold) {
 | 
						||
                                $holdBtn.html('<span class="dashicons dashicons-controls-play"></span> Unhold').addClass('btn-active');
 | 
						||
                                showNotice('Call placed on hold', 'info');
 | 
						||
                            } else {
 | 
						||
                                $holdBtn.html('<span class="dashicons dashicons-controls-pause"></span> Hold').removeClass('btn-active');
 | 
						||
                                showNotice('Call resumed', 'info');
 | 
						||
                            }
 | 
						||
                        } else {
 | 
						||
                            showNotice('Failed to toggle hold: ' + (response.data || 'Unknown error'), 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        showNotice('Failed to toggle hold', 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminShowTransferDialog() {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    // Try enhanced transfer system first
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_get_transfer_targets',
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success && response.data && (response.data.users || response.data.queues)) {
 | 
						||
                            adminShowEnhancedTransferDialog(response.data);
 | 
						||
                        } else {
 | 
						||
                            // Fallback to legacy system
 | 
						||
                            $.post(ajaxurl, {
 | 
						||
                                action: 'twp_get_online_agents',
 | 
						||
                                nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                            }, function(legacyResponse) {
 | 
						||
                                if (legacyResponse.success && legacyResponse.data.length > 0) {
 | 
						||
                                    adminShowAgentTransferDialog(legacyResponse.data);
 | 
						||
                                } else {
 | 
						||
                                    adminShowManualTransferDialog();
 | 
						||
                                }
 | 
						||
                            }).fail(function() {
 | 
						||
                                adminShowManualTransferDialog();
 | 
						||
                            });
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        adminShowManualTransferDialog();
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminShowEnhancedTransferDialog(data) {
 | 
						||
                    var agentOptions = '<div class="agent-list" style="max-height: 300px; overflow-y: auto; border: 1px solid #ccc; margin: 10px 0; padding: 10px;">';
 | 
						||
                    
 | 
						||
                    // Add users with extensions
 | 
						||
                    if (data.users && data.users.length > 0) {
 | 
						||
                        agentOptions += '<div class="transfer-section" style="margin-bottom: 20px;"><h4 style="margin: 0 0 10px 0; color: #333;">Transfer to Agent</h4>';
 | 
						||
                        data.users.forEach(function(user) {
 | 
						||
                            var statusClass = user.is_logged_in ? 'available' : 'offline';
 | 
						||
                            var statusText = user.is_logged_in ? '🟢 Online' : '🔴 Offline';
 | 
						||
                            var statusColor = user.is_logged_in ? '#28a745' : '#dc3545';
 | 
						||
                            
 | 
						||
                            agentOptions += '<div class="agent-option" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 8px; cursor: pointer; background: white;" data-agent-id="' + user.user_id + '" data-transfer-type="extension" data-transfer-target="' + user.extension + '">';
 | 
						||
                            agentOptions += '<div style="display: flex; justify-content: space-between; align-items: center;">';
 | 
						||
                            agentOptions += '<div><strong>' + user.display_name + '</strong><br><small>Ext: ' + user.extension + '</small></div>';
 | 
						||
                            agentOptions += '<div style="color: ' + statusColor + ';">' + statusText + '</div>';
 | 
						||
                            agentOptions += '</div>';
 | 
						||
                            agentOptions += '</div>';
 | 
						||
                        });
 | 
						||
                        agentOptions += '</div>';
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Add general queues
 | 
						||
                    if (data.queues && data.queues.length > 0) {
 | 
						||
                        agentOptions += '<div class="transfer-section" style="margin-bottom: 20px;"><h4 style="margin: 0 0 10px 0; color: #333;">Transfer to Queue</h4>';
 | 
						||
                        data.queues.forEach(function(queue) {
 | 
						||
                            agentOptions += '<div class="queue-option" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 8px; cursor: pointer; background: white;" data-queue-id="' + queue.id + '" data-transfer-type="queue" data-transfer-target="' + queue.id + '">';
 | 
						||
                            agentOptions += '<div style="display: flex; justify-content: space-between; align-items: center;">';
 | 
						||
                            agentOptions += '<div><strong>' + queue.queue_name + '</strong></div>';
 | 
						||
                            agentOptions += '<div style="color: #666;">' + queue.waiting_calls + ' waiting</div>';
 | 
						||
                            agentOptions += '</div>';
 | 
						||
                            agentOptions += '</div>';
 | 
						||
                        });
 | 
						||
                        agentOptions += '</div>';
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    agentOptions += '</div>';
 | 
						||
                    
 | 
						||
                    var dialogHtml = '<div id="admin-transfer-dialog" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 10000; width: 450px; max-height: 80vh; overflow-y: auto;">';
 | 
						||
                    dialogHtml += '<h3 style="margin: 0 0 15px 0;">Transfer Call</h3>';
 | 
						||
                    dialogHtml += '<p>Select an agent or queue:</p>';
 | 
						||
                    dialogHtml += agentOptions;
 | 
						||
                    dialogHtml += '<div class="manual-section" style="border-top: 1px solid #ddd; padding-top: 15px; margin-top: 15px;">';
 | 
						||
                    dialogHtml += '<h4 style="margin: 0 0 8px 0;">Manual Transfer</h4>';
 | 
						||
                    dialogHtml += '<p style="margin: 0 0 10px 0; font-size: 13px; color: #666;">Or enter extension or phone number:</p>';
 | 
						||
                    dialogHtml += '<input type="text" id="admin-transfer-manual" placeholder="Extension (100) or Phone (+1234567890)" style="width: 100%; margin: 10px 0; padding: 8px; border: 1px solid #ddd; border-radius: 3px;" />';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '<div style="text-align: right; margin-top: 20px;">';
 | 
						||
                    dialogHtml += '<button id="admin-confirm-transfer" class="button button-primary" style="margin-right: 10px;" disabled>Transfer</button>';
 | 
						||
                    dialogHtml += '<button id="admin-cancel-transfer" class="button">Cancel</button>';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '<div id="admin-transfer-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999;"></div>';
 | 
						||
                    
 | 
						||
                    $('body').append(dialogHtml);
 | 
						||
                    
 | 
						||
                    var selectedTransfer = null;
 | 
						||
                    
 | 
						||
                    $('.agent-option, .queue-option').on('click', function() {
 | 
						||
                        $('.agent-option, .queue-option').css('background', 'white');
 | 
						||
                        $(this).css('background', '#e7f3ff');
 | 
						||
                        
 | 
						||
                        selectedTransfer = {
 | 
						||
                            type: $(this).data('transfer-type'),
 | 
						||
                            target: $(this).data('transfer-target'),
 | 
						||
                            agentId: $(this).data('agent-id'),
 | 
						||
                            queueId: $(this).data('queue-id')
 | 
						||
                        };
 | 
						||
                        
 | 
						||
                        $('#admin-transfer-manual').val('');
 | 
						||
                        $('#admin-confirm-transfer').prop('disabled', false);
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-transfer-manual').on('input', function() {
 | 
						||
                        var input = $(this).val().trim();
 | 
						||
                        if (input) {
 | 
						||
                            $('.agent-option, .queue-option').css('background', 'white');
 | 
						||
                            
 | 
						||
                            // Determine if it's an extension or phone number
 | 
						||
                            var transferType, transferTarget;
 | 
						||
                            if (/^\d{3,4}$/.test(input)) {
 | 
						||
                                transferType = 'extension';
 | 
						||
                                transferTarget = input;
 | 
						||
                            } else {
 | 
						||
                                transferType = 'phone';
 | 
						||
                                transferTarget = input;
 | 
						||
                            }
 | 
						||
                            
 | 
						||
                            selectedTransfer = { type: transferType, target: transferTarget };
 | 
						||
                            $('#admin-confirm-transfer').prop('disabled', false);
 | 
						||
                        } else {
 | 
						||
                            $('#admin-confirm-transfer').prop('disabled', !selectedTransfer);
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-confirm-transfer').on('click', function() {
 | 
						||
                        console.log('Transfer button clicked, selectedTransfer:', selectedTransfer);
 | 
						||
                        if (selectedTransfer) {
 | 
						||
                            console.log('Calling adminTransferToTarget with:', selectedTransfer.type, selectedTransfer.target);
 | 
						||
                            adminTransferToTarget(selectedTransfer.type, selectedTransfer.target);
 | 
						||
                        } else {
 | 
						||
                            console.error('No transfer selected');
 | 
						||
                            alert('Please select a transfer target first');
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-cancel-transfer, #admin-transfer-overlay').on('click', function() {
 | 
						||
                        adminHideTransferDialog();
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminShowAgentTransferDialog(agents) {
 | 
						||
                    var agentOptions = '<div class="agent-list" style="max-height: 200px; overflow-y: auto; border: 1px solid #ccc; margin: 10px 0;">';
 | 
						||
                    
 | 
						||
                    agents.forEach(function(agent) {
 | 
						||
                        var statusClass = agent.is_available ? 'available' : 'busy';
 | 
						||
                        var statusText = agent.is_available ? '🟢 Available' : '🔴 Busy';
 | 
						||
                        var methodIcon = agent.has_phone ? '📱' : '💻';
 | 
						||
                        
 | 
						||
                        agentOptions += '<div class="agent-option" style="padding: 10px; border-bottom: 1px solid #eee; cursor: pointer; display: flex; justify-content: space-between;" data-agent-id="' + agent.id + '" data-transfer-method="' + agent.transfer_method + '" data-transfer-value="' + agent.transfer_value + '">';
 | 
						||
                        agentOptions += '<div><strong>' + agent.name + '</strong> ' + methodIcon + '</div>';
 | 
						||
                        agentOptions += '<div>' + statusText + '</div>';
 | 
						||
                        agentOptions += '</div>';
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    agentOptions += '</div>';
 | 
						||
                    
 | 
						||
                    var dialogHtml = '<div id="admin-transfer-dialog" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 10000; width: 400px;">';
 | 
						||
                    dialogHtml += '<h3>Transfer Call to Agent</h3>';
 | 
						||
                    dialogHtml += '<p>Select an agent to transfer this call to:</p>';
 | 
						||
                    dialogHtml += agentOptions;
 | 
						||
                    dialogHtml += '<p>Or enter phone number manually:</p>';
 | 
						||
                    dialogHtml += '<input type="tel" id="admin-transfer-manual" placeholder="+1234567890" style="width: 100%; margin: 10px 0; padding: 8px;" />';
 | 
						||
                    dialogHtml += '<div style="text-align: right; margin-top: 15px;">';
 | 
						||
                    dialogHtml += '<button id="admin-confirm-transfer" class="button button-primary" style="margin-right: 10px;" disabled>Transfer</button>';
 | 
						||
                    dialogHtml += '<button id="admin-cancel-transfer" class="button">Cancel</button>';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '<div id="admin-transfer-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999;"></div>';
 | 
						||
                    
 | 
						||
                    $('body').append(dialogHtml);
 | 
						||
                    
 | 
						||
                    var selectedAgent = null;
 | 
						||
                    
 | 
						||
                    $('.agent-option').on('click', function() {
 | 
						||
                        $('.agent-option').css('background', '');
 | 
						||
                        $(this).css('background', '#e7f3ff');
 | 
						||
                        selectedAgent = {
 | 
						||
                            id: $(this).data('agent-id'),
 | 
						||
                            method: $(this).data('transfer-method'),
 | 
						||
                            value: $(this).data('transfer-value')
 | 
						||
                        };
 | 
						||
                        $('#admin-transfer-manual').val('');
 | 
						||
                        $('#admin-confirm-transfer').prop('disabled', false);
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-transfer-manual').on('input', function() {
 | 
						||
                        var number = $(this).val().trim();
 | 
						||
                        if (number) {
 | 
						||
                            $('.agent-option').css('background', '');
 | 
						||
                            selectedAgent = null;
 | 
						||
                            $('#admin-confirm-transfer').prop('disabled', false);
 | 
						||
                        } else {
 | 
						||
                            $('#admin-confirm-transfer').prop('disabled', !selectedAgent);
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-confirm-transfer').on('click', function() {
 | 
						||
                        var manualNumber = $('#admin-transfer-manual').val().trim();
 | 
						||
                        if (manualNumber) {
 | 
						||
                            adminTransferCall(manualNumber);
 | 
						||
                        } else if (selectedAgent) {
 | 
						||
                            adminTransferToAgent(selectedAgent);
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-cancel-transfer, #admin-transfer-overlay').on('click', function() {
 | 
						||
                        adminHideTransferDialog();
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminShowManualTransferDialog() {
 | 
						||
                    var dialogHtml = '<div id="admin-transfer-dialog" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 10000;">';
 | 
						||
                    dialogHtml += '<h3>Transfer Call</h3>';
 | 
						||
                    dialogHtml += '<p>Enter the phone number to transfer this call:</p>';
 | 
						||
                    dialogHtml += '<input type="tel" id="admin-transfer-number" placeholder="+1234567890" style="width: 100%; margin: 10px 0; padding: 8px;" />';
 | 
						||
                    dialogHtml += '<div style="text-align: right; margin-top: 15px;">';
 | 
						||
                    dialogHtml += '<button id="admin-confirm-transfer" class="button button-primary" style="margin-right: 10px;">Transfer</button>';
 | 
						||
                    dialogHtml += '<button id="admin-cancel-transfer" class="button">Cancel</button>';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '</div>';
 | 
						||
                    dialogHtml += '<div id="admin-transfer-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999;"></div>';
 | 
						||
                    
 | 
						||
                    $('body').append(dialogHtml);
 | 
						||
                    
 | 
						||
                    $('#admin-confirm-transfer').on('click', function() {
 | 
						||
                        var number = $('#admin-transfer-number').val().trim();
 | 
						||
                        if (number) {
 | 
						||
                            adminTransferCall(number);
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                    
 | 
						||
                    $('#admin-cancel-transfer, #admin-transfer-overlay').on('click', function() {
 | 
						||
                        adminHideTransferDialog();
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminTransferToTarget(transferType, transferTarget) {
 | 
						||
                    console.log('adminTransferToTarget called with:', transferType, transferTarget);
 | 
						||
                    
 | 
						||
                    if (!currentCall) {
 | 
						||
                        console.error('No current call for transfer');
 | 
						||
                        alert('No active call to transfer');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    var callSid = currentCall.parameters.CallSid || 
 | 
						||
                                  currentCall.customParameters.CallSid || 
 | 
						||
                                  currentCall.outgoingConnectionId ||
 | 
						||
                                  currentCall.sid;
 | 
						||
                                  
 | 
						||
                    console.log('Transfer call SID:', callSid);
 | 
						||
                                  
 | 
						||
                    if (!callSid) {
 | 
						||
                        alert('Unable to identify call for transfer');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Use the correct parameter format expected by ajax_transfer_call
 | 
						||
                    var requestData = {
 | 
						||
                        action: 'twp_transfer_call',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    };
 | 
						||
                    
 | 
						||
                    // Determine if it's an extension or phone number
 | 
						||
                    if (/^\d{3,4}$/.test(transferTarget)) {
 | 
						||
                        // It's an extension - use new format
 | 
						||
                        requestData.target_queue_id = transferTarget;
 | 
						||
                    } else {
 | 
						||
                        // It's a phone number - use legacy format
 | 
						||
                        requestData.transfer_type = 'phone';
 | 
						||
                        requestData.transfer_target = transferTarget;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    console.log('Sending transfer request:', requestData);
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, requestData, function(response) {
 | 
						||
                        console.log('Transfer response:', response);
 | 
						||
                        if (response.success) {
 | 
						||
                            alert('Call transferred successfully');
 | 
						||
                            adminHideTransferDialog();
 | 
						||
                        } else {
 | 
						||
                            alert('Failed to transfer call: ' + (response.data || response.error || 'Unknown error'));
 | 
						||
                        }
 | 
						||
                    }).fail(function(xhr, status, error) {
 | 
						||
                        console.error('Transfer request failed:', xhr, status, error);
 | 
						||
                        alert('Failed to transfer call - network error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminTransferCall(phoneNumber) {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    var callSid = currentCall.parameters.CallSid || currentCall.customParameters.CallSid;
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_transfer_call',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        agent_number: phoneNumber,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            showNotice('Call transferred successfully', 'success');
 | 
						||
                            adminHideTransferDialog();
 | 
						||
                            if (currentCall) {
 | 
						||
                                currentCall.disconnect();
 | 
						||
                            }
 | 
						||
                        } else {
 | 
						||
                            showNotice('Failed to transfer call: ' + (response.data || 'Unknown error'), 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        showNotice('Failed to transfer call', 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminTransferToAgent(agent) {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    var callSid = currentCall.parameters.CallSid || currentCall.customParameters.CallSid;
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_transfer_to_agent_queue',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        agent_id: agent.id,
 | 
						||
                        transfer_method: agent.method,
 | 
						||
                        transfer_value: agent.value,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            showNotice('Call transferred successfully', 'success');
 | 
						||
                            adminHideTransferDialog();
 | 
						||
                            if (currentCall) {
 | 
						||
                                currentCall.disconnect();
 | 
						||
                            }
 | 
						||
                        } else {
 | 
						||
                            showNotice('Failed to transfer call: ' + (response.data || 'Unknown error'), 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        showNotice('Failed to transfer call', 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminHideTransferDialog() {
 | 
						||
                    $('#admin-transfer-dialog, #admin-transfer-overlay').remove();
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminShowRequeueDialog() {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_get_all_queues',
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success && response.data.length > 0) {
 | 
						||
                            var options = '';
 | 
						||
                            response.data.forEach(function(queue) {
 | 
						||
                                options += '<option value="' + queue.id + '">' + queue.queue_name + '</option>';
 | 
						||
                            });
 | 
						||
                            
 | 
						||
                            var dialogHtml = '<div id="admin-requeue-dialog" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 10000;">';
 | 
						||
                            dialogHtml += '<h3>Requeue Call</h3>';
 | 
						||
                            dialogHtml += '<p>Select a queue to transfer this call to:</p>';
 | 
						||
                            dialogHtml += '<select id="admin-requeue-select" style="width: 100%; margin: 10px 0; padding: 8px;">' + options + '</select>';
 | 
						||
                            dialogHtml += '<div style="text-align: right; margin-top: 15px;">';
 | 
						||
                            dialogHtml += '<button id="admin-confirm-requeue" class="button button-primary" style="margin-right: 10px;">Requeue</button>';
 | 
						||
                            dialogHtml += '<button id="admin-cancel-requeue" class="button">Cancel</button>';
 | 
						||
                            dialogHtml += '</div>';
 | 
						||
                            dialogHtml += '</div>';
 | 
						||
                            dialogHtml += '<div id="admin-requeue-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999;"></div>';
 | 
						||
                            
 | 
						||
                            $('body').append(dialogHtml);
 | 
						||
                            
 | 
						||
                            $('#admin-confirm-requeue').on('click', function() {
 | 
						||
                                var queueId = $('#admin-requeue-select').val();
 | 
						||
                                if (queueId) {
 | 
						||
                                    adminRequeueCall(queueId);
 | 
						||
                                }
 | 
						||
                            });
 | 
						||
                            
 | 
						||
                            $('#admin-cancel-requeue, #admin-requeue-overlay').on('click', function() {
 | 
						||
                                $('#admin-requeue-dialog, #admin-requeue-overlay').remove();
 | 
						||
                            });
 | 
						||
                        } else {
 | 
						||
                            showNotice('No queues available', 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        showNotice('Failed to load queues', 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminRequeueCall(queueId) {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    var callSid = currentCall.parameters.CallSid || currentCall.customParameters.CallSid;
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_requeue_call',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        queue_id: queueId,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        if (response.success) {
 | 
						||
                            showNotice('Call requeued successfully', 'success');
 | 
						||
                            $('#admin-requeue-dialog, #admin-requeue-overlay').remove();
 | 
						||
                            if (currentCall) {
 | 
						||
                                currentCall.disconnect();
 | 
						||
                            }
 | 
						||
                        } else {
 | 
						||
                            showNotice('Failed to requeue call: ' + (response.data || 'Unknown error'), 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function() {
 | 
						||
                        showNotice('Failed to requeue call', 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminToggleRecording() {
 | 
						||
                    if (!currentCall) return;
 | 
						||
                    
 | 
						||
                    if (adminIsRecording) {
 | 
						||
                        adminStopRecording();
 | 
						||
                    } else {
 | 
						||
                        adminStartRecording();
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminStartRecording() {
 | 
						||
                    if (!currentCall) {
 | 
						||
                        showNotice('No active call to record', 'error');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Try multiple ways to get the call SID for browser phone calls
 | 
						||
                    var callSid = currentCall.parameters.CallSid || 
 | 
						||
                                  currentCall.customParameters.CallSid || 
 | 
						||
                                  currentCall.outgoingConnectionId ||
 | 
						||
                                  currentCall.sid;
 | 
						||
                    
 | 
						||
                    console.log('Current call object:', currentCall);
 | 
						||
                    console.log('Attempting to record call SID:', callSid);
 | 
						||
                    
 | 
						||
                    if (!callSid) {
 | 
						||
                        showNotice('Could not determine call SID for recording', 'error');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    var $recordBtn = $('#admin-record-btn');
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_start_recording',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        console.log('Recording start response:', response);
 | 
						||
                        if (response.success) {
 | 
						||
                            adminIsRecording = true;
 | 
						||
                            adminRecordingSid = response.data.recording_sid;
 | 
						||
                            console.log('Recording started - SID:', adminRecordingSid, 'Call SID:', response.data.call_sid);
 | 
						||
                            $recordBtn.html('<span class="dashicons dashicons-controls-volumeoff"></span> Stop Recording').addClass('btn-active');
 | 
						||
                            showNotice('Recording started', 'success');
 | 
						||
                        } else {
 | 
						||
                            console.error('Recording start failed:', response);
 | 
						||
                            showNotice('Failed to start recording: ' + (response.data || 'Unknown error'), 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function(xhr, status, error) {
 | 
						||
                        console.error('Recording start AJAX failed:', xhr.responseText);
 | 
						||
                        showNotice('Failed to start recording: ' + error, 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function adminStopRecording() {
 | 
						||
                    if (!adminRecordingSid) {
 | 
						||
                        console.error('No recording SID to stop');
 | 
						||
                        showNotice('No recording to stop', 'error');
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    var callSid = currentCall ? (currentCall.parameters.CallSid || currentCall.customParameters.CallSid) : '';
 | 
						||
                    var $recordBtn = $('#admin-record-btn');
 | 
						||
                    
 | 
						||
                    console.log('Stopping recording - SID:', adminRecordingSid, 'Call SID:', callSid);
 | 
						||
                    
 | 
						||
                    $.post(ajaxurl, {
 | 
						||
                        action: 'twp_stop_recording',
 | 
						||
                        call_sid: callSid,
 | 
						||
                        recording_sid: adminRecordingSid,
 | 
						||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                    }, function(response) {
 | 
						||
                        console.log('Recording stop response:', response);
 | 
						||
                        if (response.success) {
 | 
						||
                            adminIsRecording = false;
 | 
						||
                            adminRecordingSid = null;
 | 
						||
                            $recordBtn.html('<span class="dashicons dashicons-controls-volumeon"></span> Record').removeClass('btn-active');
 | 
						||
                            showNotice('Recording stopped', 'info');
 | 
						||
                        } else {
 | 
						||
                            console.error('Recording stop failed:', response);
 | 
						||
                            showNotice('Failed to stop recording: ' + (response.data || 'Unknown error'), 'error');
 | 
						||
                        }
 | 
						||
                    }).fail(function(xhr, status, error) {
 | 
						||
                        console.error('Recording stop AJAX failed:', xhr.responseText);
 | 
						||
                        showNotice('Failed to stop recording: ' + error, 'error');
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function showNotice(message, type) {
 | 
						||
                    var noticeClass = type === 'error' ? 'notice-error' : (type === 'success' ? 'notice-success' : 'notice-info');
 | 
						||
                    var notice = $('<div class="notice ' + noticeClass + ' is-dismissible" style="margin: 10px 0;"><p>' + message + '</p></div>');
 | 
						||
                    $('.browser-phone-container').prepend(notice);
 | 
						||
                    setTimeout(function() {
 | 
						||
                        notice.fadeOut();
 | 
						||
                    }, 4000);
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Agent status functions for the status bar
 | 
						||
                function toggleAgentLogin() {
 | 
						||
                    $.ajax({
 | 
						||
                        url: ajaxurl,
 | 
						||
                        method: 'POST',
 | 
						||
                        data: {
 | 
						||
                            action: 'twp_toggle_agent_login',
 | 
						||
                            nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                        },
 | 
						||
                        success: function(response) {
 | 
						||
                            if (response.success) {
 | 
						||
                                location.reload();
 | 
						||
                            } else {
 | 
						||
                                showNotice('Failed to change login status: ' + response.data, 'error');
 | 
						||
                            }
 | 
						||
                        },
 | 
						||
                        error: function() {
 | 
						||
                            showNotice('Failed to change login status', 'error');
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                function updateAgentStatus(status) {
 | 
						||
                    $.ajax({
 | 
						||
                        url: ajaxurl,
 | 
						||
                        method: 'POST',
 | 
						||
                        data: {
 | 
						||
                            action: 'twp_update_agent_status',
 | 
						||
                            status: status,
 | 
						||
                            nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
						||
                        },
 | 
						||
                        success: function(response) {
 | 
						||
                            if (response.success) {
 | 
						||
                                showNotice('Status updated to ' + status, 'success');
 | 
						||
                            } else {
 | 
						||
                                showNotice('Failed to update status: ' + response.data, 'error');
 | 
						||
                            }
 | 
						||
                        },
 | 
						||
                        error: function() {
 | 
						||
                            showNotice('Failed to update status', 'error');
 | 
						||
                        }
 | 
						||
                    });
 | 
						||
                }
 | 
						||
            });
 | 
						||
            </script>
 | 
						||
        </div>
 | 
						||
        <?php
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Check if smart routing is configured on any phone numbers
 | 
						||
     */
 | 
						||
    private function check_smart_routing_status() {
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $phone_numbers = $twilio->get_phone_numbers();
 | 
						||
            
 | 
						||
            if (!$phone_numbers['success']) {
 | 
						||
                return false;
 | 
						||
            }
 | 
						||
            
 | 
						||
            $smart_routing_url = home_url('/wp-json/twilio-webhook/v1/smart-routing');
 | 
						||
            
 | 
						||
            foreach ($phone_numbers['data']['incoming_phone_numbers'] as $number) {
 | 
						||
                if ($number['voice_url'] === $smart_routing_url) {
 | 
						||
                    return true;
 | 
						||
                }
 | 
						||
            }
 | 
						||
            
 | 
						||
            return false;
 | 
						||
        } catch (Exception $e) {
 | 
						||
            error_log('TWP: Error checking smart routing status: ' . $e->getMessage());
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Get user's queue memberships
 | 
						||
     */
 | 
						||
    private function get_user_queue_memberships($user_id) {
 | 
						||
        global $wpdb;
 | 
						||
        
 | 
						||
        // Get agent groups the user belongs to
 | 
						||
        $groups_table = $wpdb->prefix . 'twp_group_members';
 | 
						||
        $queues_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        
 | 
						||
        $user_groups = $wpdb->get_results($wpdb->prepare(
 | 
						||
            "SELECT gm.group_id, q.id as queue_id, q.queue_name
 | 
						||
             FROM $groups_table gm
 | 
						||
             JOIN $queues_table q ON gm.group_id = q.agent_group_id
 | 
						||
             WHERE gm.user_id = %d",
 | 
						||
            $user_id
 | 
						||
        ));
 | 
						||
        
 | 
						||
        $queues = [];
 | 
						||
        foreach ($user_groups as $group) {
 | 
						||
            $queues[$group->queue_id] = [
 | 
						||
                'id' => $group->queue_id,
 | 
						||
                'name' => $group->queue_name
 | 
						||
            ];
 | 
						||
        }
 | 
						||
        
 | 
						||
        return array_values($queues);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Helper function to identify the customer call leg for browser phone calls
 | 
						||
     * 
 | 
						||
     * @param string $call_sid The call SID to analyze
 | 
						||
     * @param TWP_Twilio_API $api Twilio API instance
 | 
						||
     * @return string|null The customer call SID or null if not found
 | 
						||
     */
 | 
						||
    private function find_customer_call_leg($call_sid, $api) {
 | 
						||
        try {
 | 
						||
            $client = $api->get_client();
 | 
						||
            $call = $client->calls($call_sid)->fetch();
 | 
						||
            $target_call_sid = null;
 | 
						||
            
 | 
						||
            error_log("TWP Call Leg Detection: Call SID {$call_sid} - From: {$call->from}, To: {$call->to}, Direction: {$call->direction}, Parent: " . ($call->parentCallSid ?: 'none'));
 | 
						||
            
 | 
						||
            // For browser phone calls (outbound), we need to find the customer leg
 | 
						||
            if (strpos($call->from, 'client:') === 0 || strpos($call->to, 'client:') === 0) {
 | 
						||
                error_log("TWP Call Leg Detection: Browser phone call detected");
 | 
						||
                
 | 
						||
                // This is a browser phone call, find the customer leg
 | 
						||
                if ($call->parentCallSid) {
 | 
						||
                    // Check parent call
 | 
						||
                    try {
 | 
						||
                        $parent_call = $client->calls($call->parentCallSid)->fetch();
 | 
						||
                        if (strpos($parent_call->from, 'client:') === false && strpos($parent_call->to, 'client:') === false) {
 | 
						||
                            $target_call_sid = $parent_call->sid;
 | 
						||
                            error_log("TWP Call Leg Detection: Using parent call as customer leg: {$target_call_sid}");
 | 
						||
                        }
 | 
						||
                    } catch (Exception $e) {
 | 
						||
                        error_log("TWP Call Leg Detection: Could not fetch parent call: " . $e->getMessage());
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                
 | 
						||
                // If no parent or parent is also client, search for related customer call
 | 
						||
                if (!$target_call_sid) {
 | 
						||
                    $active_calls = $client->calls->read(['status' => 'in-progress'], 50);
 | 
						||
                    foreach ($active_calls as $active_call) {
 | 
						||
                        if ($active_call->sid === $call_sid) continue; // Skip current call
 | 
						||
                        
 | 
						||
                        // Check if calls are related and this one doesn't involve a client
 | 
						||
                        $is_related = false;
 | 
						||
                        if ($call->parentCallSid && $active_call->parentCallSid === $call->parentCallSid) {
 | 
						||
                            $is_related = true;
 | 
						||
                        } elseif ($active_call->parentCallSid === $call_sid) {
 | 
						||
                            $is_related = true;
 | 
						||
                        } elseif ($active_call->sid === $call->parentCallSid) {
 | 
						||
                            $is_related = true;
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        if ($is_related && strpos($active_call->from, 'client:') === false && 
 | 
						||
                            strpos($active_call->to, 'client:') === false) {
 | 
						||
                            $target_call_sid = $active_call->sid;
 | 
						||
                            error_log("TWP Call Leg Detection: Found related customer call: {$target_call_sid}");
 | 
						||
                            break;
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Store the relationship for future use
 | 
						||
                if ($target_call_sid) {
 | 
						||
                    error_log("TWP Call Leg Detection: Agent leg {$call_sid} -> Customer leg {$target_call_sid}");
 | 
						||
                }
 | 
						||
                
 | 
						||
            } else {
 | 
						||
                // Regular inbound call - current call IS the customer
 | 
						||
                $target_call_sid = $call_sid;
 | 
						||
                error_log("TWP Call Leg Detection: Regular inbound call, current call is customer");
 | 
						||
            }
 | 
						||
            
 | 
						||
            if (!$target_call_sid) {
 | 
						||
                error_log("TWP Call Leg Detection: Could not determine customer leg, using current call as fallback");
 | 
						||
                $target_call_sid = $call_sid;
 | 
						||
            }
 | 
						||
            
 | 
						||
            return $target_call_sid;
 | 
						||
            
 | 
						||
        } catch (Exception $e) {
 | 
						||
            error_log("TWP Call Leg Detection Error: " . $e->getMessage());
 | 
						||
            return $call_sid; // Fallback to original call
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for toggling call hold
 | 
						||
     */
 | 
						||
    public function ajax_toggle_hold() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $hold = filter_var($_POST['hold'], FILTER_VALIDATE_BOOLEAN);
 | 
						||
        
 | 
						||
        try {
 | 
						||
            // Get Twilio API instance
 | 
						||
            require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-twilio-api.php';
 | 
						||
            $api = new TWP_Twilio_API();
 | 
						||
            
 | 
						||
            if ($hold) {
 | 
						||
                // Put call on hold using Hold Queue system
 | 
						||
                error_log("TWP: Putting call on hold - SID: {$call_sid}");
 | 
						||
                
 | 
						||
                // Use helper function to identify the customer call leg
 | 
						||
                $target_call_sid = $this->find_customer_call_leg($call_sid, $api);
 | 
						||
                
 | 
						||
                // Get current user ID for hold queue management
 | 
						||
                $current_user_id = get_current_user_id();
 | 
						||
                if (!$current_user_id) {
 | 
						||
                    error_log("TWP: Hold failed - no current user");
 | 
						||
                    wp_send_json_error('Failed to hold call: No user context');
 | 
						||
                    return;
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Use the Hold Queue system to properly hold the call
 | 
						||
                require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-user-queue-manager.php';
 | 
						||
                
 | 
						||
                // Check if user has queues, create them if not
 | 
						||
                $extension_data = TWP_User_Queue_Manager::get_user_extension_data($current_user_id);
 | 
						||
                if (!$extension_data || !$extension_data['hold_queue_id']) {
 | 
						||
                    error_log("TWP: User doesn't have queues, creating them now");
 | 
						||
                    $queue_creation = TWP_User_Queue_Manager::create_user_queues($current_user_id);
 | 
						||
                    if (!$queue_creation['success']) {
 | 
						||
                        error_log("TWP: Failed to create user queues - " . $queue_creation['error']);
 | 
						||
                        wp_send_json_error('Failed to create hold queue: ' . $queue_creation['error']);
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    $extension_data = TWP_User_Queue_Manager::get_user_extension_data($current_user_id);
 | 
						||
                }
 | 
						||
                
 | 
						||
                $queue_result = TWP_User_Queue_Manager::transfer_to_hold_queue($current_user_id, $target_call_sid);
 | 
						||
                
 | 
						||
                if ($queue_result['success']) {
 | 
						||
                    // Get the hold queue details
 | 
						||
                    global $wpdb;
 | 
						||
                    $hold_queue = $wpdb->get_row($wpdb->prepare(
 | 
						||
                        "SELECT * FROM {$wpdb->prefix}twp_call_queues WHERE id = %d",
 | 
						||
                        $queue_result['hold_queue_id']
 | 
						||
                    ));
 | 
						||
                    
 | 
						||
                    if ($hold_queue) {
 | 
						||
                        // Create TwiML for hold experience
 | 
						||
                        $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
                        
 | 
						||
                        // Use TTS helper with caching for hold message
 | 
						||
                        $hold_message = $hold_queue->tts_message ?: 'Your call is on hold. Please wait.';
 | 
						||
                        
 | 
						||
                        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
                        $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
                        $tts_helper->add_tts_to_twiml($twiml, $hold_message);
 | 
						||
                        
 | 
						||
                        // Use default hold music URL or custom one from settings
 | 
						||
                        $hold_music_url = get_option('twp_hold_music_url', 'http://com.twilio.sounds.music.s3.amazonaws.com/MARKOVICHAMP-Borghestral.mp3');
 | 
						||
                        $twiml->play($hold_music_url, ['loop' => 0]); // Loop indefinitely
 | 
						||
                        
 | 
						||
                        // Update the customer call leg with hold experience
 | 
						||
                        $result = $api->update_call($target_call_sid, [
 | 
						||
                            'twiml' => $twiml->asXML()
 | 
						||
                        ]);
 | 
						||
                        
 | 
						||
                        if ($result['success']) {
 | 
						||
                            error_log("TWP: Successfully put call on hold in queue - Target: {$target_call_sid} -> Hold Queue: {$queue_result['hold_queue_id']}");
 | 
						||
                            wp_send_json_success(array(
 | 
						||
                                'message' => 'Call placed on hold',
 | 
						||
                                'target_call_sid' => $target_call_sid,
 | 
						||
                                'hold_queue_id' => $queue_result['hold_queue_id']
 | 
						||
                            ));
 | 
						||
                        } else {
 | 
						||
                            error_log("TWP: Failed to update call for hold - " . $result['error']);
 | 
						||
                            wp_send_json_error('Failed to place call on hold: ' . $result['error']);
 | 
						||
                        }
 | 
						||
                    } else {
 | 
						||
                        error_log("TWP: Hold failed - hold queue not found: " . $queue_result['hold_queue_id']);
 | 
						||
                        wp_send_json_error('Failed to hold call: Hold queue not found');
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    error_log("TWP: Failed to transfer to hold queue - " . $queue_result['error']);
 | 
						||
                    wp_send_json_error('Failed to hold call: ' . $queue_result['error']);
 | 
						||
                }
 | 
						||
                
 | 
						||
            } else {
 | 
						||
                // Resume call from hold queue
 | 
						||
                error_log("TWP: Resuming call from hold - SID: {$call_sid}");
 | 
						||
                
 | 
						||
                // Use helper function to identify the customer call leg
 | 
						||
                $target_call_sid = $this->find_customer_call_leg($call_sid, $api);
 | 
						||
                
 | 
						||
                // Get current user ID for hold queue management
 | 
						||
                $current_user_id = get_current_user_id();
 | 
						||
                if (!$current_user_id) {
 | 
						||
                    error_log("TWP: Resume failed - no current user");
 | 
						||
                    wp_send_json_error('Failed to resume call: No user context');
 | 
						||
                    return;
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Use the Hold Queue system to properly resume the call
 | 
						||
                require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-user-queue-manager.php';
 | 
						||
                
 | 
						||
                // Check if user has queues, create them if not  
 | 
						||
                $extension_data = TWP_User_Queue_Manager::get_user_extension_data($current_user_id);
 | 
						||
                if (!$extension_data || !$extension_data['hold_queue_id']) {
 | 
						||
                    error_log("TWP: User doesn't have queues for resume, creating them now");
 | 
						||
                    $queue_creation = TWP_User_Queue_Manager::create_user_queues($current_user_id);
 | 
						||
                    if (!$queue_creation['success']) {
 | 
						||
                        error_log("TWP: Failed to create user queues - " . $queue_creation['error']);
 | 
						||
                        wp_send_json_error('Failed to create queues: ' . $queue_creation['error']);
 | 
						||
                        return;
 | 
						||
                    }
 | 
						||
                    $extension_data = TWP_User_Queue_Manager::get_user_extension_data($current_user_id);
 | 
						||
                }
 | 
						||
                
 | 
						||
                $queue_result = TWP_User_Queue_Manager::resume_from_hold($current_user_id, $target_call_sid);
 | 
						||
                
 | 
						||
                if ($queue_result['success']) {
 | 
						||
                    // Get the target queue details to redirect the call properly
 | 
						||
                    global $wpdb;
 | 
						||
                    $queue = $wpdb->get_row($wpdb->prepare(
 | 
						||
                        "SELECT * FROM {$wpdb->prefix}twp_call_queues WHERE id = %d",
 | 
						||
                        $queue_result['target_queue_id']
 | 
						||
                    ));
 | 
						||
                    
 | 
						||
                    if ($queue) {
 | 
						||
                        // Create TwiML to redirect to the target queue
 | 
						||
                        $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
                        
 | 
						||
                        // If it's a personal queue, try to connect directly to agent
 | 
						||
                        if ($queue->queue_type === 'personal') {
 | 
						||
                            // Use TTS helper for ElevenLabs support
 | 
						||
                            require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
                            $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
                            $tts_helper->add_tts_to_twiml($twiml, 'Resuming your call.');
 | 
						||
                            
 | 
						||
                            // Get the agent's phone number
 | 
						||
                            $agent_number = get_user_meta($current_user_id, 'twp_phone_number', true);
 | 
						||
                            if ($agent_number) {
 | 
						||
                                $dial = $twiml->dial(['timeout' => 30]);
 | 
						||
                                $dial->number($agent_number);
 | 
						||
                            } else {
 | 
						||
                                // Use TTS helper for error message
 | 
						||
                                $tts_helper->add_tts_to_twiml($twiml, 'Unable to locate agent. Please try again.');
 | 
						||
                                $twiml->hangup();
 | 
						||
                            }
 | 
						||
                        } else {
 | 
						||
                            // Regular queue - redirect to queue wait
 | 
						||
                            $queue_wait_url = home_url('/wp-json/twilio-webhook/v1/queue-wait');
 | 
						||
                            $queue_wait_url = add_query_arg(array(
 | 
						||
                                'queue_id' => $queue_result['target_queue_id']
 | 
						||
                            ), $queue_wait_url);
 | 
						||
                            
 | 
						||
                            $twiml->redirect($queue_wait_url, ['method' => 'POST']);
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        // Update the customer call leg with resume TwiML
 | 
						||
                        $result = $api->update_call($target_call_sid, [
 | 
						||
                            'twiml' => $twiml->asXML()
 | 
						||
                        ]);
 | 
						||
                        
 | 
						||
                        if ($result['success']) {
 | 
						||
                            error_log("TWP: Successfully resumed call from hold queue - Target: {$target_call_sid} -> Queue: {$queue_result['target_queue_id']}");
 | 
						||
                            wp_send_json_success(array(
 | 
						||
                                'message' => 'Call resumed from hold',
 | 
						||
                                'target_call_sid' => $target_call_sid,
 | 
						||
                                'target_queue_id' => $queue_result['target_queue_id']
 | 
						||
                            ));
 | 
						||
                        } else {
 | 
						||
                            error_log("TWP: Failed to update call for resume - " . $result['error']);
 | 
						||
                            wp_send_json_error('Failed to resume call: ' . $result['error']);
 | 
						||
                        }
 | 
						||
                    } else {
 | 
						||
                        error_log("TWP: Resume failed - target queue not found: " . $queue_result['target_queue_id']);
 | 
						||
                        wp_send_json_error('Failed to resume call: Target queue not found');
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    error_log("TWP: Failed to resume from hold queue - " . $queue_result['error']);
 | 
						||
                    wp_send_json_error('Failed to resume call: ' . $queue_result['error']);
 | 
						||
                }
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (Exception $e) {
 | 
						||
            error_log("TWP Hold Error: " . $e->getMessage());
 | 
						||
            wp_send_json_error('Hold operation failed: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting available agents for transfer
 | 
						||
     */
 | 
						||
    public function ajax_get_transfer_agents() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $users_table = $wpdb->prefix . 'users';
 | 
						||
        $usermeta_table = $wpdb->prefix . 'usermeta';
 | 
						||
        $status_table = $wpdb->prefix . 'twp_agent_status';
 | 
						||
        
 | 
						||
        // Get all users with the twp_access_browser_phone capability or admins
 | 
						||
        $all_users = get_users([
 | 
						||
            'orderby' => 'display_name',
 | 
						||
            'order' => 'ASC'
 | 
						||
        ]);
 | 
						||
        
 | 
						||
        $agents = [];
 | 
						||
        $current_user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        foreach ($all_users as $user) {
 | 
						||
            // Skip current user
 | 
						||
            if ($user->ID == $current_user_id) {
 | 
						||
                continue;
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Check if user can access browser phone or is admin
 | 
						||
            if (!user_can($user->ID, 'twp_access_browser_phone') && !user_can($user->ID, 'manage_options')) {
 | 
						||
                continue;
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Get user's phone number
 | 
						||
            $phone_number = get_user_meta($user->ID, 'twp_phone_number', true);
 | 
						||
            
 | 
						||
            // Get user's status
 | 
						||
            $status = $wpdb->get_var($wpdb->prepare(
 | 
						||
                "SELECT status FROM $status_table WHERE user_id = %d",
 | 
						||
                $user->ID
 | 
						||
            ));
 | 
						||
            
 | 
						||
            $agents[] = [
 | 
						||
                'id' => $user->ID,
 | 
						||
                'name' => $user->display_name,
 | 
						||
                'phone' => $phone_number,
 | 
						||
                'status' => $status ?: 'offline',
 | 
						||
                'has_phone' => !empty($phone_number),
 | 
						||
                'queue_name' => 'agent_' . $user->ID // Personal queue name
 | 
						||
            ];
 | 
						||
        }
 | 
						||
        
 | 
						||
        wp_send_json_success($agents);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for transferring a call
 | 
						||
     */
 | 
						||
    public function ajax_transfer_call() {
 | 
						||
        // Check nonce - try frontend first, then admin
 | 
						||
        $nonce_valid = wp_verify_nonce($_POST['nonce'] ?? '', 'twp_frontend_nonce') || 
 | 
						||
                       wp_verify_nonce($_POST['nonce'] ?? '', 'twp_ajax_nonce');
 | 
						||
                       
 | 
						||
        if (!$nonce_valid) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check user permissions - require admin access or agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            wp_send_json_error('Unauthorized - Admin or agent access required');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        
 | 
						||
        // Handle both old and new parameter formats
 | 
						||
        if (isset($_POST['target_queue_id'])) {
 | 
						||
            // New format from enhanced queue system
 | 
						||
            $current_queue_id = isset($_POST['current_queue_id']) ? intval($_POST['current_queue_id']) : null;
 | 
						||
            $target = sanitize_text_field($_POST['target_queue_id']); // Can be queue ID or extension
 | 
						||
        } else {
 | 
						||
            // Legacy format
 | 
						||
            $transfer_type = sanitize_text_field($_POST['transfer_type'] ?? 'queue');
 | 
						||
            $target = sanitize_text_field($_POST['transfer_target'] ?? '');
 | 
						||
        }
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            global $wpdb;
 | 
						||
            
 | 
						||
            // Check if target is an extension (3-4 digits)
 | 
						||
            if (is_numeric($target) && strlen($target) <= 4) {
 | 
						||
                // It's an extension, find the user's queue
 | 
						||
                $user_id = TWP_User_Queue_Manager::get_user_by_extension($target);
 | 
						||
                
 | 
						||
                error_log("TWP Transfer: Looking up extension {$target}, found user_id: " . ($user_id ?: 'none'));
 | 
						||
                
 | 
						||
                if (!$user_id) {
 | 
						||
                    wp_send_json_error('Extension not found');
 | 
						||
                    return;
 | 
						||
                }
 | 
						||
                
 | 
						||
                $extension_data = TWP_User_Queue_Manager::get_user_extension_data($user_id);
 | 
						||
                $target_queue_id = $extension_data['personal_queue_id'];
 | 
						||
                
 | 
						||
                // Find customer call leg for transfer FIRST (important for outbound calls)
 | 
						||
                $customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
 | 
						||
                error_log("TWP Transfer: Using customer call leg {$customer_call_sid} for extension transfer (original: {$call_sid})");
 | 
						||
                
 | 
						||
                // Move call to new queue using the CUSTOMER call SID for proper tracking
 | 
						||
                $next_position = $wpdb->get_var($wpdb->prepare(
 | 
						||
                    "SELECT COALESCE(MAX(position), 0) + 1 FROM {$wpdb->prefix}twp_queued_calls 
 | 
						||
                     WHERE queue_id = %d AND status = 'waiting'",
 | 
						||
                    $target_queue_id
 | 
						||
                ));
 | 
						||
                
 | 
						||
                // First check if call already exists in queue table
 | 
						||
                $existing_call = $wpdb->get_row($wpdb->prepare(
 | 
						||
                    "SELECT * FROM {$wpdb->prefix}twp_queued_calls WHERE call_sid = %s",
 | 
						||
                    $customer_call_sid
 | 
						||
                ));
 | 
						||
                
 | 
						||
                if ($existing_call) {
 | 
						||
                    // Update existing call record
 | 
						||
                    $result = $wpdb->update(
 | 
						||
                        $wpdb->prefix . 'twp_queued_calls',
 | 
						||
                        array(
 | 
						||
                            'queue_id' => $target_queue_id,
 | 
						||
                            'position' => $next_position,
 | 
						||
                            'status' => 'waiting'
 | 
						||
                        ),
 | 
						||
                        array('call_sid' => $customer_call_sid),
 | 
						||
                        array('%d', '%d', '%s'),
 | 
						||
                        array('%s')
 | 
						||
                    );
 | 
						||
                } else {
 | 
						||
                    // Get call details from Twilio for new record
 | 
						||
                    $client = $twilio->get_client();
 | 
						||
                    try {
 | 
						||
                        $call = $client->calls($customer_call_sid)->fetch();
 | 
						||
                        $from_number = $call->from;
 | 
						||
                        $to_number = $call->to;
 | 
						||
                    } catch (Exception $e) {
 | 
						||
                        error_log("TWP Transfer: Could not fetch call details: " . $e->getMessage());
 | 
						||
                        $from_number = '';
 | 
						||
                        $to_number = '';
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Insert new call record
 | 
						||
                    $insert_data = array(
 | 
						||
                        'queue_id' => $target_queue_id,
 | 
						||
                        'call_sid' => $customer_call_sid,
 | 
						||
                        'from_number' => $from_number,
 | 
						||
                        'to_number' => $to_number,
 | 
						||
                        'position' => $next_position,
 | 
						||
                        'status' => 'waiting'
 | 
						||
                    );
 | 
						||
                    
 | 
						||
                    // Check if enqueued_at column exists
 | 
						||
                    $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
                    $columns = $wpdb->get_col("DESCRIBE $calls_table");
 | 
						||
                    if (in_array('enqueued_at', $columns)) {
 | 
						||
                        $insert_data['enqueued_at'] = current_time('mysql');
 | 
						||
                    } else {
 | 
						||
                        $insert_data['joined_at'] = current_time('mysql');
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    $result = $wpdb->insert($calls_table, $insert_data);
 | 
						||
                }
 | 
						||
                
 | 
						||
                if ($result !== false) {
 | 
						||
                    
 | 
						||
                    // Check if target user is logged in and available using proper agent manager
 | 
						||
                    require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-agent-manager.php';
 | 
						||
                    $is_logged_in = TWP_Agent_Manager::is_agent_logged_in($user_id);
 | 
						||
                    $agent_status = TWP_Agent_Manager::get_agent_status($user_id);
 | 
						||
                    $is_available = $is_logged_in && ($agent_status && $agent_status->status === 'available');
 | 
						||
                    
 | 
						||
                    error_log("TWP Transfer: Extension {$target} to User {$user_id} - Logged in: " . ($is_logged_in ? 'yes' : 'no') . ", Status: " . ($agent_status ? $agent_status->status : 'unknown') . ", Available: " . ($is_available ? 'yes' : 'no'));
 | 
						||
                    
 | 
						||
                    // Get target user details
 | 
						||
                    $target_user = get_user_by('id', $user_id);
 | 
						||
                    $agent_phone = get_user_meta($user_id, 'twp_phone_number', true);
 | 
						||
                    
 | 
						||
                    // Create TwiML for extension transfer with timeout and voicemail
 | 
						||
                    $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
                    
 | 
						||
                    // Use TTS helper for ElevenLabs support
 | 
						||
                    require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
                    $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
                    $tts_helper->add_tts_to_twiml($twiml, 'Transferring to extension ' . $target . '. Please hold.');
 | 
						||
                    
 | 
						||
                    if ($is_available || $is_logged_in) {
 | 
						||
                        // Agent is logged in - place call in their personal queue with 2-minute timeout
 | 
						||
                        error_log("TWP Transfer: Agent {$user_id} is logged in, placing call in personal queue with timeout");
 | 
						||
                        
 | 
						||
                        // Redirect to queue wait with timeout
 | 
						||
                        $queue_wait_url = home_url('/wp-json/twilio-webhook/v1/queue-wait');
 | 
						||
                        $queue_wait_url = add_query_arg(array(
 | 
						||
                            'queue_id' => $target_queue_id,
 | 
						||
                            'call_sid' => $customer_call_sid,
 | 
						||
                            'timeout' => 120, // 2 minutes
 | 
						||
                            'timeout_action' => home_url('/wp-json/twilio-webhook/v1/extension-voicemail?user_id=' . $user_id . '&extension=' . $target)
 | 
						||
                        ), $queue_wait_url);
 | 
						||
                        
 | 
						||
                        $twiml->redirect($queue_wait_url, ['method' => 'POST']);
 | 
						||
                    } else {
 | 
						||
                        // Agent is offline or no phone configured - go straight to voicemail
 | 
						||
                        error_log("TWP Transfer: Agent {$user_id} is offline or has no phone, sending to voicemail");
 | 
						||
                        
 | 
						||
                        // Get voicemail prompt from personal queue settings
 | 
						||
                        $personal_queue = $wpdb->get_row($wpdb->prepare(
 | 
						||
                            "SELECT * FROM {$wpdb->prefix}twp_call_queues WHERE id = %d",
 | 
						||
                            $target_queue_id
 | 
						||
                        ));
 | 
						||
                        
 | 
						||
                        $voicemail_prompt = $personal_queue && $personal_queue->voicemail_prompt 
 | 
						||
                            ? $personal_queue->voicemail_prompt 
 | 
						||
                            : sprintf('%s is not available. Please leave a message after the tone.', $target_user->display_name);
 | 
						||
                        
 | 
						||
                        $tts_helper->add_tts_to_twiml($twiml, $voicemail_prompt);
 | 
						||
                        
 | 
						||
                        // Record voicemail with proper callback to save to database
 | 
						||
                        $twiml->record([
 | 
						||
                            'action' => home_url('/wp-json/twilio-webhook/v1/voicemail-callback?user_id=' . $user_id),
 | 
						||
                            'maxLength' => 120, // 2 minutes max
 | 
						||
                            'playBeep' => true,
 | 
						||
                            'transcribe' => true,
 | 
						||
                            'transcribeCallback' => home_url('/wp-json/twilio-webhook/v1/transcription?user_id=' . $user_id)
 | 
						||
                        ]);
 | 
						||
                    }
 | 
						||
                    
 | 
						||
                    // Update the customer call with proper TwiML
 | 
						||
                    $result = $twilio->update_call($customer_call_sid, array(
 | 
						||
                        'twiml' => $twiml->asXML()
 | 
						||
                    ));
 | 
						||
                    
 | 
						||
                    if ($result['success']) {
 | 
						||
                        wp_send_json_success(['message' => 'Call transferred to extension ' . $target]);
 | 
						||
                    } else {
 | 
						||
                        wp_send_json_error('Failed to transfer call: ' . $result['error']);
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    wp_send_json_error('Failed to transfer call to queue');
 | 
						||
                }
 | 
						||
                
 | 
						||
            } elseif (is_numeric($target) && strlen($target) > 4) {
 | 
						||
                // It's a queue ID
 | 
						||
                $target_queue_id = intval($target);
 | 
						||
                
 | 
						||
                // Move call to new queue
 | 
						||
                $next_position = $wpdb->get_var($wpdb->prepare(
 | 
						||
                    "SELECT COALESCE(MAX(position), 0) + 1 FROM {$wpdb->prefix}twp_queued_calls 
 | 
						||
                     WHERE queue_id = %d AND status = 'waiting'",
 | 
						||
                    $target_queue_id
 | 
						||
                ));
 | 
						||
                
 | 
						||
                $result = $wpdb->update(
 | 
						||
                    $wpdb->prefix . 'twp_queued_calls',
 | 
						||
                    array(
 | 
						||
                        'queue_id' => $target_queue_id,
 | 
						||
                        'position' => $next_position
 | 
						||
                    ),
 | 
						||
                    array('call_sid' => $call_sid),
 | 
						||
                    array('%d', '%d'),
 | 
						||
                    array('%s')
 | 
						||
                );
 | 
						||
                
 | 
						||
                if ($result !== false) {
 | 
						||
                    // Find customer call leg for transfer (important for outbound calls)
 | 
						||
                    $customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
 | 
						||
                    error_log("TWP Transfer: Using customer call leg {$customer_call_sid} for queue transfer (original: {$call_sid})");
 | 
						||
                    
 | 
						||
                    // Create TwiML to redirect call to queue
 | 
						||
                    $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
                    
 | 
						||
                    // Use TTS helper for ElevenLabs support
 | 
						||
                    require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
                    $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
                    $tts_helper->add_tts_to_twiml($twiml, 'Transferring your call. Please hold.');
 | 
						||
                    
 | 
						||
                    // Redirect to queue wait endpoint
 | 
						||
                    $queue_wait_url = home_url('/wp-json/twilio-webhook/v1/queue-wait');
 | 
						||
                    $queue_wait_url = add_query_arg(array(
 | 
						||
                        'queue_id' => $target_queue_id,
 | 
						||
                        'call_sid' => $customer_call_sid
 | 
						||
                    ), $queue_wait_url);
 | 
						||
                    
 | 
						||
                    $twiml->redirect($queue_wait_url, ['method' => 'POST']);
 | 
						||
                    
 | 
						||
                    // Update the customer call with proper TwiML
 | 
						||
                    $result = $twilio->update_call($customer_call_sid, array(
 | 
						||
                        'twiml' => $twiml->asXML()
 | 
						||
                    ));
 | 
						||
                    
 | 
						||
                    if ($result['success']) {
 | 
						||
                        wp_send_json_success(['message' => 'Call transferred to queue']);
 | 
						||
                    } else {
 | 
						||
                        wp_send_json_error('Failed to transfer call: ' . $result['error']);
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    wp_send_json_error('Failed to update queue database');
 | 
						||
                }
 | 
						||
                
 | 
						||
            } else {
 | 
						||
                // Transfer to phone number or client endpoint
 | 
						||
                
 | 
						||
                // Check if it's a client endpoint (browser phone)
 | 
						||
                if (strpos($target, 'client:') === 0) {
 | 
						||
                    // Extract agent name from client identifier
 | 
						||
                    $agent_name = substr($target, 7); // Remove 'client:' prefix
 | 
						||
                    
 | 
						||
                    // Create TwiML for client transfer
 | 
						||
                    $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
                    
 | 
						||
                    // Use TTS helper for ElevenLabs support
 | 
						||
                    require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
                    $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
                    $tts_helper->add_tts_to_twiml($twiml, 'Transferring your call to ' . $agent_name . '. Please hold.');
 | 
						||
                    
 | 
						||
                    // Use Dial with client endpoint
 | 
						||
                    $dial = $twiml->dial();
 | 
						||
                    $dial->client($agent_name);
 | 
						||
                    
 | 
						||
                    $twiml_xml = $twiml->asXML();
 | 
						||
                    
 | 
						||
                    // Find customer call leg for transfer (important for outbound calls)
 | 
						||
                    $customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
 | 
						||
                    error_log("TWP Transfer: Using customer call leg {$customer_call_sid} for client transfer (original: {$call_sid})");
 | 
						||
                    
 | 
						||
                    // Update the customer call with the transfer TwiML
 | 
						||
                    $client = $twilio->get_client();
 | 
						||
                    $call = $client->calls($customer_call_sid)->update([
 | 
						||
                        'twiml' => $twiml_xml
 | 
						||
                    ]);
 | 
						||
                    
 | 
						||
                    wp_send_json_success(['message' => 'Call transferred to agent ' . $agent_name]);
 | 
						||
                    
 | 
						||
                } elseif (preg_match('/^\+?[1-9]\d{1,14}$/', $target)) {
 | 
						||
                    // Transfer to phone number
 | 
						||
                    $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
                    
 | 
						||
                    // Use TTS helper for ElevenLabs support
 | 
						||
                    require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
                    $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
                    $tts_helper->add_tts_to_twiml($twiml, 'Transferring your call. Please hold.');
 | 
						||
                    $twiml->dial($target);
 | 
						||
                    $twiml_xml = $twiml->asXML();
 | 
						||
                    
 | 
						||
                    // Find customer call leg for transfer (important for outbound calls)
 | 
						||
                    $customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
 | 
						||
                    error_log("TWP Transfer: Using customer call leg {$customer_call_sid} for phone transfer (original: {$call_sid})");
 | 
						||
                    
 | 
						||
                    // Update the customer call with the transfer TwiML
 | 
						||
                    $client = $twilio->get_client();
 | 
						||
                    $call = $client->calls($customer_call_sid)->update([
 | 
						||
                        'twiml' => $twiml_xml
 | 
						||
                    ]);
 | 
						||
                    
 | 
						||
                    wp_send_json_success(['message' => 'Call transferred to ' . $target]);
 | 
						||
                } else {
 | 
						||
                    wp_send_json_error('Invalid transfer target format. Expected phone number or client endpoint.');
 | 
						||
                }
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (Exception $e) {
 | 
						||
            wp_send_json_error('Failed to transfer call: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for requeuing a call
 | 
						||
     */
 | 
						||
    public function ajax_requeue_call() {
 | 
						||
        // Check nonce - try frontend first, then admin
 | 
						||
        $nonce_valid = wp_verify_nonce($_POST['nonce'] ?? '', 'twp_frontend_nonce') || 
 | 
						||
                       wp_verify_nonce($_POST['nonce'] ?? '', 'twp_ajax_nonce');
 | 
						||
                       
 | 
						||
        if (!$nonce_valid) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check user permissions - require admin access or agent queue access
 | 
						||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
 | 
						||
            error_log('TWP Plugin: Permission check failed for requeue');
 | 
						||
            wp_send_json_error('Unauthorized - Admin or agent access required');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $queue_id = intval($_POST['queue_id']);
 | 
						||
        
 | 
						||
        // Validate queue exists
 | 
						||
        global $wpdb;
 | 
						||
        $queue_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        $queue = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT * FROM $queue_table WHERE id = %d",
 | 
						||
            $queue_id
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$queue) {
 | 
						||
            wp_send_json_error('Invalid queue');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $client = $twilio->get_client();
 | 
						||
            
 | 
						||
            // Find the customer call leg for requeue (important for outbound calls)
 | 
						||
            $customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
 | 
						||
            error_log("TWP Requeue: Using customer call leg {$customer_call_sid} for requeue (original: {$call_sid})");
 | 
						||
            
 | 
						||
            // Create proper TwiML using VoiceResponse
 | 
						||
            $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
            
 | 
						||
            // Use TTS helper for ElevenLabs support
 | 
						||
            require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
 | 
						||
            $tts_helper = TWP_TTS_Helper::get_instance();
 | 
						||
            $tts_helper->add_tts_to_twiml($twiml, 'Placing you back in the queue. Please hold.');
 | 
						||
            
 | 
						||
            // Redirect to queue wait endpoint with proper parameters
 | 
						||
            $queue_wait_url = home_url('/wp-json/twilio-webhook/v1/queue-wait');
 | 
						||
            $queue_wait_url = add_query_arg(array(
 | 
						||
                'queue_id' => $queue_id,
 | 
						||
                'call_sid' => $customer_call_sid
 | 
						||
            ), $queue_wait_url);
 | 
						||
            
 | 
						||
            $twiml->redirect($queue_wait_url, ['method' => 'POST']);
 | 
						||
            
 | 
						||
            // Update the customer call with the requeue TwiML
 | 
						||
            $call = $client->calls($customer_call_sid)->update([
 | 
						||
                'twiml' => $twiml->asXML()
 | 
						||
            ]);
 | 
						||
            
 | 
						||
            // Add call to our database queue tracking
 | 
						||
            $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
            
 | 
						||
            // Use enqueued_at if available, fallback to joined_at for compatibility
 | 
						||
            $insert_data = [
 | 
						||
                'queue_id' => $queue_id,
 | 
						||
                'call_sid' => $customer_call_sid, // Use customer call SID for tracking
 | 
						||
                'from_number' => $call->from,
 | 
						||
                'to_number' => $call->to ?: '',
 | 
						||
                'position' => 1, // Will be updated by queue manager
 | 
						||
                'status' => 'waiting'
 | 
						||
            ];
 | 
						||
            
 | 
						||
            // Check if enqueued_at column exists
 | 
						||
            $columns = $wpdb->get_col("DESCRIBE $calls_table");
 | 
						||
            if (in_array('enqueued_at', $columns)) {
 | 
						||
                $insert_data['enqueued_at'] = current_time('mysql');
 | 
						||
            } else {
 | 
						||
                $insert_data['joined_at'] = current_time('mysql');
 | 
						||
            }
 | 
						||
            
 | 
						||
            $wpdb->insert($calls_table, $insert_data);
 | 
						||
            
 | 
						||
            wp_send_json_success(['message' => 'Call requeued successfully']);
 | 
						||
        } catch (Exception $e) {
 | 
						||
            wp_send_json_error('Failed to requeue call: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for starting call recording
 | 
						||
     */
 | 
						||
    public function ajax_start_recording() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        if (empty($call_sid)) {
 | 
						||
            wp_send_json_error('Call SID is required for recording');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        error_log("TWP: Starting recording for call SID: $call_sid");
 | 
						||
        
 | 
						||
        // Ensure database table exists and run any migrations
 | 
						||
        TWP_Activator::ensure_tables_exist();
 | 
						||
        TWP_Activator::force_table_updates();
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $client = $twilio->get_client();
 | 
						||
            
 | 
						||
            // First, verify the call exists and is in progress
 | 
						||
            try {
 | 
						||
                $call = $client->calls($call_sid)->fetch();
 | 
						||
                error_log("TWP: Call found - Status: {$call->status}, From: {$call->from}, To: {$call->to}");
 | 
						||
                
 | 
						||
                if (!in_array($call->status, ['in-progress', 'ringing'])) {
 | 
						||
                    wp_send_json_error("Cannot record call in status: {$call->status}. Call must be in-progress.");
 | 
						||
                    return;
 | 
						||
                }
 | 
						||
            } catch (Exception $call_error) {
 | 
						||
                error_log("TWP: Error fetching call details: " . $call_error->getMessage());
 | 
						||
                wp_send_json_error("Call not found or not accessible: " . $call_error->getMessage());
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Start recording the call
 | 
						||
            $recording = $client->calls($call_sid)->recordings->create([
 | 
						||
                'recordingStatusCallback' => home_url('/wp-json/twilio-webhook/v1/recording-status'),
 | 
						||
                'recordingStatusCallbackEvent' => ['completed', 'absent'],
 | 
						||
                'recordingChannels' => 'dual'
 | 
						||
            ]);
 | 
						||
            
 | 
						||
            error_log("TWP: Recording created with SID: {$recording->sid}");
 | 
						||
            
 | 
						||
            // Store recording info in database
 | 
						||
            global $wpdb;
 | 
						||
            $recordings_table = $wpdb->prefix . 'twp_call_recordings';
 | 
						||
            
 | 
						||
            // Enhanced customer number detection using our call leg detection system
 | 
						||
            $from_number = $call->from;
 | 
						||
            $to_number = $call->to;
 | 
						||
            
 | 
						||
            error_log("TWP Recording: Initial call data - From: {$call->from}, To: {$call->to}, Direction: {$call->direction}");
 | 
						||
            
 | 
						||
            // If this is a browser phone call, use our helper to find the customer number
 | 
						||
            if (strpos($call->from, 'client:') === 0 || strpos($call->to, 'client:') === 0) {
 | 
						||
                error_log("TWP Recording: Detected browser phone call, finding customer number");
 | 
						||
                
 | 
						||
                // Find the customer call leg using our helper function
 | 
						||
                $customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
 | 
						||
                
 | 
						||
                if ($customer_call_sid && $customer_call_sid !== $call_sid) {
 | 
						||
                    // Get the customer call details
 | 
						||
                    try {
 | 
						||
                        $customer_call = $client->calls($customer_call_sid)->fetch();
 | 
						||
                        
 | 
						||
                        // Determine which field has the customer number
 | 
						||
                        $customer_number = null;
 | 
						||
                        
 | 
						||
                        // For outbound calls, customer is usually in 'to' of the customer leg
 | 
						||
                        // For inbound calls, customer is usually in 'from' of the customer leg
 | 
						||
                        if (strpos($customer_call->from, 'client:') === false && strpos($customer_call->from, '+') === 0) {
 | 
						||
                            $customer_number = $customer_call->from;
 | 
						||
                            error_log("TWP Recording: Found customer number in customer leg 'from': {$customer_number}");
 | 
						||
                        } elseif (strpos($customer_call->to, 'client:') === false && strpos($customer_call->to, '+') === 0) {
 | 
						||
                            $customer_number = $customer_call->to;
 | 
						||
                            error_log("TWP Recording: Found customer number in customer leg 'to': {$customer_number}");
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                        if ($customer_number) {
 | 
						||
                            // Store in database with customer number as 'from' for consistency
 | 
						||
                            $from_number = $customer_number;
 | 
						||
                            $to_number = $call->from; // Agent/browser client
 | 
						||
                            error_log("TWP Recording: Browser phone call - Customer: {$customer_number}, Agent: {$call->from}");
 | 
						||
                        } else {
 | 
						||
                            error_log("TWP Recording: WARNING - Customer call leg found but no customer number detected");
 | 
						||
                        }
 | 
						||
                        
 | 
						||
                    } catch (Exception $e) {
 | 
						||
                        error_log("TWP Recording: Error fetching customer call details: " . $e->getMessage());
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    error_log("TWP Recording: Could not find separate customer call leg");
 | 
						||
                    
 | 
						||
                    // Fallback: if 'to' is not a client, use it as customer number
 | 
						||
                    if (!empty($call->to) && strpos($call->to, 'client:') === false && strpos($call->to, '+') === 0) {
 | 
						||
                        $from_number = $call->to; // Customer number
 | 
						||
                        $to_number = $call->from; // Agent client
 | 
						||
                        error_log("TWP Recording: Using 'to' field as customer number: {$call->to}");
 | 
						||
                    } else {
 | 
						||
                        error_log("TWP Recording: WARNING - Could not determine customer number for browser phone call");
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            } else {
 | 
						||
                // Regular inbound call - customer is 'from', agent is 'to'
 | 
						||
                error_log("TWP Recording: Regular call - keeping original from/to values");
 | 
						||
            }
 | 
						||
            
 | 
						||
            $insert_result = $wpdb->insert($recordings_table, [
 | 
						||
                'call_sid' => $call_sid,
 | 
						||
                'recording_sid' => $recording->sid,
 | 
						||
                'from_number' => $from_number,
 | 
						||
                'to_number' => $to_number,
 | 
						||
                'agent_id' => $user_id,
 | 
						||
                'status' => 'recording',
 | 
						||
                'started_at' => current_time('mysql')
 | 
						||
            ]);
 | 
						||
            
 | 
						||
            if ($insert_result === false) {
 | 
						||
                error_log("TWP: Database insert failed: " . $wpdb->last_error);
 | 
						||
                wp_send_json_error("Failed to save recording to database: " . $wpdb->last_error);
 | 
						||
                return;
 | 
						||
            } else {
 | 
						||
                error_log("TWP: Recording saved to database - Recording SID: {$recording->sid}, Call SID: $call_sid");
 | 
						||
            }
 | 
						||
            
 | 
						||
            wp_send_json_success([
 | 
						||
                'message' => 'Recording started',
 | 
						||
                'recording_sid' => $recording->sid,
 | 
						||
                'call_sid' => $call_sid  // Include call_sid for debugging
 | 
						||
            ]);
 | 
						||
        } catch (Exception $e) {
 | 
						||
            error_log("TWP: Recording start error: " . $e->getMessage());
 | 
						||
            wp_send_json_error('Failed to start recording: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for stopping call recording
 | 
						||
     */
 | 
						||
    public function ajax_stop_recording() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $recording_sid = sanitize_text_field($_POST['recording_sid']);
 | 
						||
        
 | 
						||
        if (empty($recording_sid)) {
 | 
						||
            wp_send_json_error('Recording SID is required');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $recordings_table = $wpdb->prefix . 'twp_call_recordings';
 | 
						||
        
 | 
						||
        // Check if recording exists and is active
 | 
						||
        $recording_info = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT * FROM $recordings_table WHERE recording_sid = %s",
 | 
						||
            $recording_sid
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$recording_info) {
 | 
						||
            error_log("TWP: Recording $recording_sid not found in database, attempting Twilio-only stop");
 | 
						||
            
 | 
						||
            // Try to stop the recording in Twilio anyway (might exist there but not in DB)
 | 
						||
            try {
 | 
						||
                $twilio = new TWP_Twilio_API();
 | 
						||
                $client = $twilio->get_client();
 | 
						||
                
 | 
						||
                // We don't have the call SID, so we can't stop the recording
 | 
						||
                // Log the issue and return an appropriate error
 | 
						||
                error_log("TWP: Cannot stop recording $recording_sid - not found in database and need call SID to stop via API");
 | 
						||
                
 | 
						||
                wp_send_json_success(['message' => 'Recording stopped (was not tracked in database)']);
 | 
						||
                return;
 | 
						||
            } catch (Exception $twilio_error) {
 | 
						||
                error_log("TWP: Recording $recording_sid not found in database or Twilio: " . $twilio_error->getMessage());
 | 
						||
                wp_send_json_error('Recording not found in database or Twilio system');
 | 
						||
                return;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        if ($recording_info->status === 'completed') {
 | 
						||
            // Already stopped, just update UI
 | 
						||
            wp_send_json_success(['message' => 'Recording already stopped']);
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $client = $twilio->get_client();
 | 
						||
            
 | 
						||
            // Try to stop the recording in Twilio
 | 
						||
            // In Twilio SDK v8, you stop a recording via the call's recordings subresource
 | 
						||
            try {
 | 
						||
                // If we have multiple recordings, we need the specific recording SID
 | 
						||
                // If there's only one recording, we can use 'Twilio.CURRENT'
 | 
						||
                if ($recording_info && $recording_info->call_sid) {
 | 
						||
                    try {
 | 
						||
                        // First try with the specific recording SID
 | 
						||
                        $client->calls($recording_info->call_sid)
 | 
						||
                               ->recordings($recording_sid)
 | 
						||
                               ->update(['status' => 'stopped']);
 | 
						||
                        error_log("TWP: Successfully stopped recording $recording_sid for call {$recording_info->call_sid}");
 | 
						||
                    } catch (Exception $e) {
 | 
						||
                        // If that fails, try with Twilio.CURRENT (for single recording)
 | 
						||
                        try {
 | 
						||
                            $client->calls($recording_info->call_sid)
 | 
						||
                                   ->recordings('Twilio.CURRENT')
 | 
						||
                                   ->update(['status' => 'stopped']);
 | 
						||
                            error_log("TWP: Stopped recording using Twilio.CURRENT for call {$recording_info->call_sid}");
 | 
						||
                        } catch (Exception $e2) {
 | 
						||
                            error_log('TWP: Could not stop recording - it may already be stopped: ' . $e2->getMessage());
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    error_log('TWP: Could not find call SID for recording ' . $recording_sid);
 | 
						||
                }
 | 
						||
            } catch (Exception $twilio_error) {
 | 
						||
                // Recording might already be stopped or completed on Twilio's side
 | 
						||
                error_log('TWP: Could not stop recording in Twilio (may already be stopped): ' . $twilio_error->getMessage());
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Update database regardless
 | 
						||
            $wpdb->update(
 | 
						||
                $recordings_table,
 | 
						||
                [
 | 
						||
                    'status' => 'completed',
 | 
						||
                    'ended_at' => current_time('mysql')
 | 
						||
                ],
 | 
						||
                ['recording_sid' => $recording_sid]
 | 
						||
            );
 | 
						||
            
 | 
						||
            wp_send_json_success(['message' => 'Recording stopped']);
 | 
						||
        } catch (Exception $e) {
 | 
						||
            error_log('TWP: Error stopping recording: ' . $e->getMessage());
 | 
						||
            wp_send_json_error('Failed to stop recording: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting call recordings
 | 
						||
     */
 | 
						||
    public function ajax_get_call_recordings() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $recordings_table = $wpdb->prefix . 'twp_call_recordings';
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        // Build query based on user permissions
 | 
						||
        if (current_user_can('manage_options')) {
 | 
						||
            // Admins can see all recordings
 | 
						||
            $recordings = $wpdb->get_results("
 | 
						||
                SELECT r.*, u.display_name as agent_name
 | 
						||
                FROM $recordings_table r
 | 
						||
                LEFT JOIN {$wpdb->users} u ON r.agent_id = u.ID
 | 
						||
                ORDER BY r.started_at DESC
 | 
						||
                LIMIT 100
 | 
						||
            ");
 | 
						||
        } else {
 | 
						||
            // Regular users see only their recordings
 | 
						||
            $recordings = $wpdb->get_results($wpdb->prepare("
 | 
						||
                SELECT r.*, u.display_name as agent_name
 | 
						||
                FROM $recordings_table r
 | 
						||
                LEFT JOIN {$wpdb->users} u ON r.agent_id = u.ID
 | 
						||
                WHERE r.agent_id = %d
 | 
						||
                ORDER BY r.started_at DESC
 | 
						||
                LIMIT 50
 | 
						||
            ", $user_id));
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Format recordings for display
 | 
						||
        $formatted_recordings = [];
 | 
						||
        foreach ($recordings as $recording) {
 | 
						||
            $formatted_recordings[] = [
 | 
						||
                'id' => $recording->id,
 | 
						||
                'call_sid' => $recording->call_sid,
 | 
						||
                'recording_sid' => $recording->recording_sid,
 | 
						||
                'from_number' => $recording->from_number,
 | 
						||
                'to_number' => $recording->to_number,
 | 
						||
                'agent_name' => $recording->agent_name,
 | 
						||
                'duration' => $recording->duration,
 | 
						||
                'started_at' => $recording->started_at,
 | 
						||
                'recording_url' => $recording->recording_url,
 | 
						||
                'has_recording' => !empty($recording->recording_url)
 | 
						||
            ];
 | 
						||
        }
 | 
						||
        
 | 
						||
        wp_send_json_success($formatted_recordings);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for deleting a recording (admin only)
 | 
						||
     */
 | 
						||
    public function ajax_delete_recording() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check admin permissions
 | 
						||
        if (!current_user_can('manage_options')) {
 | 
						||
            wp_send_json_error('You do not have permission to delete recordings');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $recording_id = intval($_POST['recording_id']);
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $recordings_table = $wpdb->prefix . 'twp_call_recordings';
 | 
						||
        
 | 
						||
        // Get recording details first
 | 
						||
        $recording = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT * FROM $recordings_table WHERE id = %d",
 | 
						||
            $recording_id
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$recording) {
 | 
						||
            wp_send_json_error('Recording not found');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Delete from Twilio if we have a recording SID
 | 
						||
        if ($recording->recording_sid) {
 | 
						||
            try {
 | 
						||
                $twilio = new TWP_Twilio_API();
 | 
						||
                $client = $twilio->get_client();
 | 
						||
                
 | 
						||
                // Try to delete from Twilio
 | 
						||
                $client->recordings($recording->recording_sid)->delete();
 | 
						||
            } catch (Exception $e) {
 | 
						||
                // Log error but continue with local deletion
 | 
						||
                error_log('TWP: Failed to delete recording from Twilio: ' . $e->getMessage());
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Delete from database
 | 
						||
        $result = $wpdb->delete(
 | 
						||
            $recordings_table,
 | 
						||
            ['id' => $recording_id],
 | 
						||
            ['%d']
 | 
						||
        );
 | 
						||
        
 | 
						||
        if ($result === false) {
 | 
						||
            wp_send_json_error('Failed to delete recording from database');
 | 
						||
        } else {
 | 
						||
            wp_send_json_success(['message' => 'Recording deleted successfully']);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for getting online agents for transfer
 | 
						||
     */
 | 
						||
    public function ajax_get_online_agents() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $status_table = $wpdb->prefix . 'twp_agent_status';
 | 
						||
        
 | 
						||
        // Get all agents with their status
 | 
						||
        $agents = $wpdb->get_results($wpdb->prepare("
 | 
						||
            SELECT 
 | 
						||
                u.ID,
 | 
						||
                u.display_name,
 | 
						||
                u.user_email,
 | 
						||
                um.meta_value as phone_number,
 | 
						||
                s.status,
 | 
						||
                s.current_call_sid,
 | 
						||
                CASE 
 | 
						||
                    WHEN s.status = 'available' AND s.current_call_sid IS NULL THEN 1
 | 
						||
                    WHEN s.status = 'available' AND s.current_call_sid IS NOT NULL THEN 2
 | 
						||
                    WHEN s.status = 'busy' THEN 3
 | 
						||
                    ELSE 4
 | 
						||
                END as priority
 | 
						||
            FROM {$wpdb->users} u
 | 
						||
            LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'twp_phone_number'
 | 
						||
            LEFT JOIN $status_table s ON u.ID = s.user_id
 | 
						||
            WHERE u.ID != %d
 | 
						||
            ORDER BY priority, u.display_name
 | 
						||
        ", get_current_user_id()));
 | 
						||
        
 | 
						||
        $formatted_agents = [];
 | 
						||
        if ($agents) {
 | 
						||
            foreach ($agents as $agent) {
 | 
						||
            $transfer_method = null;
 | 
						||
            $transfer_value = null;
 | 
						||
            
 | 
						||
            // Determine transfer method
 | 
						||
            if ($agent->phone_number) {
 | 
						||
                $transfer_method = 'phone';
 | 
						||
                $transfer_value = $agent->phone_number;
 | 
						||
            } elseif ($agent->status === 'available') {
 | 
						||
                $transfer_method = 'queue';
 | 
						||
                $transfer_value = 'agent_' . $agent->ID; // User-specific queue name
 | 
						||
            }
 | 
						||
            
 | 
						||
            if ($transfer_method) {
 | 
						||
                $formatted_agents[] = [
 | 
						||
                    'id' => $agent->ID,
 | 
						||
                    'name' => $agent->display_name,
 | 
						||
                    'email' => $agent->user_email,
 | 
						||
                    'status' => $agent->status ?: 'offline',
 | 
						||
                    'is_available' => ($agent->status === 'available' && !$agent->current_call_sid),
 | 
						||
                    'has_phone' => !empty($agent->phone_number),
 | 
						||
                    'transfer_method' => $transfer_method,
 | 
						||
                    'transfer_value' => $transfer_value
 | 
						||
                ];
 | 
						||
            }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        wp_send_json_success($formatted_agents);
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for transferring call to agent queue
 | 
						||
     */
 | 
						||
    public function ajax_transfer_to_agent_queue() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $agent_id = intval($_POST['agent_id']);
 | 
						||
        $transfer_method = sanitize_text_field($_POST['transfer_method']);
 | 
						||
        $transfer_value = sanitize_text_field($_POST['transfer_value']);
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $client = $twilio->get_client();
 | 
						||
            
 | 
						||
            $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
						||
            
 | 
						||
            if ($transfer_method === 'phone') {
 | 
						||
                // Direct phone transfer
 | 
						||
                $twiml->say('Transferring your call. Please hold.');
 | 
						||
                $twiml->dial($transfer_value);
 | 
						||
            } else {
 | 
						||
                // Queue-based transfer for web phone agents
 | 
						||
                $queue_name = 'agent_' . $agent_id;
 | 
						||
                
 | 
						||
                // Create or ensure the agent-specific queue exists in Twilio
 | 
						||
                $this->ensure_agent_queue_exists($queue_name, $agent_id);
 | 
						||
                
 | 
						||
                // Notify the agent they have an incoming transfer
 | 
						||
                $this->notify_agent_of_transfer($agent_id, $call_sid);
 | 
						||
                
 | 
						||
                $twiml->say('Transferring you to an agent. Please hold.');
 | 
						||
                $enqueue = $twiml->enqueue($queue_name);
 | 
						||
                $enqueue->waitUrl(home_url('/wp-json/twilio-webhook/v1/queue-wait'));
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Update the call with the transfer TwiML
 | 
						||
            $call = $client->calls($call_sid)->update([
 | 
						||
                'twiml' => $twiml->asXML()
 | 
						||
            ]);
 | 
						||
            
 | 
						||
            wp_send_json_success(['message' => 'Call transferred successfully']);
 | 
						||
        } catch (Exception $e) {
 | 
						||
            wp_send_json_error('Failed to transfer call: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Ensure agent-specific queue exists
 | 
						||
     */
 | 
						||
    private function ensure_agent_queue_exists($queue_name, $agent_id) {
 | 
						||
        global $wpdb;
 | 
						||
        $queues_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        
 | 
						||
        // Check if queue exists
 | 
						||
        $queue = $wpdb->get_row($wpdb->prepare(
 | 
						||
            "SELECT * FROM $queues_table WHERE queue_name = %s",
 | 
						||
            $queue_name
 | 
						||
        ));
 | 
						||
        
 | 
						||
        if (!$queue) {
 | 
						||
            // Create the queue
 | 
						||
            $user = get_user_by('id', $agent_id);
 | 
						||
            $wpdb->insert($queues_table, [
 | 
						||
                'queue_name' => $queue_name,
 | 
						||
                'max_size' => 10,
 | 
						||
                'timeout_seconds' => 300,
 | 
						||
                'created_at' => current_time('mysql'),
 | 
						||
                'updated_at' => current_time('mysql')
 | 
						||
            ]);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Notify agent of incoming transfer
 | 
						||
     */
 | 
						||
    private function notify_agent_of_transfer($agent_id, $call_sid) {
 | 
						||
        // Store notification in database or send real-time notification
 | 
						||
        // This could be enhanced with WebSockets or Server-Sent Events
 | 
						||
        
 | 
						||
        // For now, just log it
 | 
						||
        error_log("TWP: Notifying agent $agent_id of incoming transfer for call $call_sid");
 | 
						||
        
 | 
						||
        // You could also update the agent's status
 | 
						||
        global $wpdb;
 | 
						||
        $status_table = $wpdb->prefix . 'twp_agent_status';
 | 
						||
        $wpdb->update(
 | 
						||
            $status_table,
 | 
						||
            ['current_call_sid' => $call_sid],
 | 
						||
            ['user_id' => $agent_id]
 | 
						||
        );
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for checking personal queue
 | 
						||
     */
 | 
						||
    public function ajax_check_personal_queue() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        $queue_name = 'agent_' . $user_id;
 | 
						||
        
 | 
						||
        global $wpdb;
 | 
						||
        $queues_table = $wpdb->prefix . 'twp_call_queues';
 | 
						||
        $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
        
 | 
						||
        // Check if there are calls in the personal queue
 | 
						||
        $waiting_call = $wpdb->get_row($wpdb->prepare("
 | 
						||
            SELECT qc.*, q.id as queue_id
 | 
						||
            FROM $calls_table qc
 | 
						||
            JOIN $queues_table q ON qc.queue_id = q.id
 | 
						||
            WHERE q.queue_name = %s 
 | 
						||
            AND qc.status = 'waiting'
 | 
						||
            ORDER BY COALESCE(qc.enqueued_at, qc.joined_at) ASC
 | 
						||
            LIMIT 1
 | 
						||
        ", $queue_name));
 | 
						||
        
 | 
						||
        if ($waiting_call) {
 | 
						||
            wp_send_json_success([
 | 
						||
                'has_waiting_call' => true,
 | 
						||
                'call_sid' => $waiting_call->call_sid,
 | 
						||
                'queue_id' => $waiting_call->queue_id,
 | 
						||
                'from_number' => $waiting_call->from_number,
 | 
						||
                'wait_time' => time() - strtotime($waiting_call->enqueued_at)
 | 
						||
            ]);
 | 
						||
        } else {
 | 
						||
            wp_send_json_success(['has_waiting_call' => false]);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * AJAX handler for accepting transfer call
 | 
						||
     */
 | 
						||
    public function ajax_accept_transfer_call() {
 | 
						||
        if (!$this->verify_ajax_nonce()) {
 | 
						||
            wp_send_json_error('Invalid nonce');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
        
 | 
						||
        $call_sid = sanitize_text_field($_POST['call_sid']);
 | 
						||
        $queue_id = intval($_POST['queue_id']);
 | 
						||
        $user_id = get_current_user_id();
 | 
						||
        
 | 
						||
        try {
 | 
						||
            $twilio = new TWP_Twilio_API();
 | 
						||
            $client = $twilio->get_client();
 | 
						||
            
 | 
						||
            // Connect the call to the browser phone
 | 
						||
            $call = $client->calls($call_sid)->update([
 | 
						||
                'url' => home_url('/wp-json/twilio-webhook/v1/browser-voice'),
 | 
						||
                'method' => 'POST'
 | 
						||
            ]);
 | 
						||
            
 | 
						||
            // Update database to mark call as connected
 | 
						||
            global $wpdb;
 | 
						||
            $calls_table = $wpdb->prefix . 'twp_queued_calls';
 | 
						||
            
 | 
						||
            $wpdb->update(
 | 
						||
                $calls_table,
 | 
						||
                [
 | 
						||
                    'status' => 'connected',
 | 
						||
                    'agent_id' => $user_id
 | 
						||
                ],
 | 
						||
                ['call_sid' => $call_sid]
 | 
						||
            );
 | 
						||
            
 | 
						||
            // Update agent status
 | 
						||
            $status_table = $wpdb->prefix . 'twp_agent_status';
 | 
						||
            $wpdb->update(
 | 
						||
                $status_table,
 | 
						||
                ['current_call_sid' => $call_sid],
 | 
						||
                ['user_id' => $user_id]
 | 
						||
            );
 | 
						||
            
 | 
						||
            wp_send_json_success(['message' => 'Transfer accepted']);
 | 
						||
        } catch (Exception $e) {
 | 
						||
            wp_send_json_error('Failed to accept transfer: ' . $e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
}
 |