version = TWP_VERSION; $this->plugin_name = 'twilio-wp-plugin'; $this->load_dependencies(); $this->set_locale(); $this->ensure_user_roles(); $this->define_admin_hooks(); $this->define_public_hooks(); $this->define_api_hooks(); } /** * Load required dependencies */ private function load_dependencies() { // Loader class require_once TWP_PLUGIN_DIR . 'includes/class-twp-loader.php'; // API classes require_once TWP_PLUGIN_DIR . 'includes/class-twp-twilio-api.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-elevenlabs-api.php'; // Feature classes require_once TWP_PLUGIN_DIR . 'includes/class-twp-scheduler.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-call-queue.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-workflow.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-webhooks.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-call-logger.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-agent-groups.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-agent-manager.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-callback-manager.php'; // Admin classes require_once TWP_PLUGIN_DIR . 'admin/class-twp-admin.php'; $this->loader = new TWP_Loader(); } /** * Define locale for internationalization */ private function set_locale() { add_action('plugins_loaded', function() { load_plugin_textdomain( 'twilio-wp-plugin', false, dirname(TWP_PLUGIN_BASENAME) . '/languages/' ); }); } /** * Ensure custom user roles exist */ private function ensure_user_roles() { $role = get_role('phone_agent'); if (!$role) { // Create the Phone Agent role with limited capabilities add_role('phone_agent', 'Phone Agent', array( // Basic WordPress capabilities 'read' => true, // Profile management 'edit_profile' => true, // Phone agent specific capabilities 'twp_access_voicemails' => true, 'twp_access_call_log' => true, 'twp_access_agent_queue' => true, 'twp_access_outbound_calls' => true, 'twp_access_sms_inbox' => true, 'twp_access_browser_phone' => true, 'twp_access_phone_numbers' => true, )); } else { // Update existing role with any missing capabilities $capabilities = array( 'twp_access_voicemails', 'twp_access_call_log', 'twp_access_agent_queue', 'twp_access_outbound_calls', 'twp_access_sms_inbox', 'twp_access_browser_phone', 'twp_access_phone_numbers' ); foreach ($capabilities as $cap) { if (!$role->has_cap($cap)) { $role->add_cap($cap); } } } } /** * Register admin hooks */ private function define_admin_hooks() { $plugin_admin = new TWP_Admin($this->get_plugin_name(), $this->get_version()); $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles'); $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts'); $this->loader->add_action('admin_menu', $plugin_admin, 'add_plugin_admin_menu'); $this->loader->add_action('admin_init', $plugin_admin, 'register_settings'); $this->loader->add_action('admin_notices', $plugin_admin, 'show_admin_notices'); // AJAX handlers $this->loader->add_action('wp_ajax_twp_save_schedule', $plugin_admin, 'ajax_save_schedule'); $this->loader->add_action('wp_ajax_twp_delete_schedule', $plugin_admin, 'ajax_delete_schedule'); $this->loader->add_action('wp_ajax_twp_get_schedules', $plugin_admin, 'ajax_get_schedules'); $this->loader->add_action('wp_ajax_twp_get_schedule', $plugin_admin, 'ajax_get_schedule'); $this->loader->add_action('wp_ajax_twp_save_workflow', $plugin_admin, 'ajax_save_workflow'); $this->loader->add_action('wp_ajax_twp_update_workflow', $plugin_admin, 'ajax_save_workflow'); $this->loader->add_action('wp_ajax_twp_get_workflow', $plugin_admin, 'ajax_get_workflow'); $this->loader->add_action('wp_ajax_twp_delete_workflow', $plugin_admin, 'ajax_delete_workflow'); // Phone number management AJAX $this->loader->add_action('wp_ajax_twp_get_phone_numbers', $plugin_admin, 'ajax_get_phone_numbers'); $this->loader->add_action('wp_ajax_twp_search_available_numbers', $plugin_admin, 'ajax_search_available_numbers'); $this->loader->add_action('wp_ajax_twp_purchase_number', $plugin_admin, 'ajax_purchase_number'); $this->loader->add_action('wp_ajax_twp_configure_number', $plugin_admin, 'ajax_configure_number'); $this->loader->add_action('wp_ajax_twp_release_number', $plugin_admin, 'ajax_release_number'); // Queue management AJAX $this->loader->add_action('wp_ajax_twp_get_queue', $plugin_admin, 'ajax_get_queue'); $this->loader->add_action('wp_ajax_twp_save_queue', $plugin_admin, 'ajax_save_queue'); $this->loader->add_action('wp_ajax_twp_get_queue_details', $plugin_admin, 'ajax_get_queue_details'); $this->loader->add_action('wp_ajax_twp_get_all_queues', $plugin_admin, 'ajax_get_all_queues'); $this->loader->add_action('wp_ajax_twp_delete_queue', $plugin_admin, 'ajax_delete_queue'); $this->loader->add_action('wp_ajax_twp_get_dashboard_stats', $plugin_admin, 'ajax_get_dashboard_stats'); // Eleven Labs AJAX $this->loader->add_action('wp_ajax_twp_get_elevenlabs_voices', $plugin_admin, 'ajax_get_elevenlabs_voices'); $this->loader->add_action('wp_ajax_twp_get_elevenlabs_models', $plugin_admin, 'ajax_get_elevenlabs_models'); $this->loader->add_action('wp_ajax_twp_preview_voice', $plugin_admin, 'ajax_preview_voice'); // Voicemail management AJAX $this->loader->add_action('wp_ajax_twp_get_voicemail', $plugin_admin, 'ajax_get_voicemail'); $this->loader->add_action('wp_ajax_twp_delete_voicemail', $plugin_admin, 'ajax_delete_voicemail'); $this->loader->add_action('wp_ajax_twp_transcribe_voicemail', $plugin_admin, 'ajax_transcribe_voicemail'); $this->loader->add_action('wp_ajax_twp_get_voicemail_audio', $plugin_admin, 'ajax_get_voicemail_audio'); // Agent group management AJAX $this->loader->add_action('wp_ajax_twp_get_all_groups', $plugin_admin, 'ajax_get_all_groups'); $this->loader->add_action('wp_ajax_twp_get_group', $plugin_admin, 'ajax_get_group'); $this->loader->add_action('wp_ajax_twp_save_group', $plugin_admin, 'ajax_save_group'); $this->loader->add_action('wp_ajax_twp_delete_group', $plugin_admin, 'ajax_delete_group'); $this->loader->add_action('wp_ajax_twp_get_group_members', $plugin_admin, 'ajax_get_group_members'); $this->loader->add_action('wp_ajax_twp_add_group_member', $plugin_admin, 'ajax_add_group_member'); $this->loader->add_action('wp_ajax_twp_remove_group_member', $plugin_admin, 'ajax_remove_group_member'); // Agent queue management AJAX $this->loader->add_action('wp_ajax_twp_accept_call', $plugin_admin, 'ajax_accept_call'); $this->loader->add_action('wp_ajax_twp_accept_next_queue_call', $plugin_admin, 'ajax_accept_next_queue_call'); $this->loader->add_action('wp_ajax_twp_get_waiting_calls', $plugin_admin, 'ajax_get_waiting_calls'); $this->loader->add_action('wp_ajax_twp_set_agent_status', $plugin_admin, 'ajax_set_agent_status'); $this->loader->add_action('wp_ajax_twp_get_call_details', $plugin_admin, 'ajax_get_call_details'); // Callback and outbound call AJAX $this->loader->add_action('wp_ajax_twp_request_callback', $plugin_admin, 'ajax_request_callback'); $this->loader->add_action('wp_ajax_twp_initiate_outbound_call', $plugin_admin, 'ajax_initiate_outbound_call'); $this->loader->add_action('wp_ajax_twp_initiate_outbound_call_with_from', $plugin_admin, 'ajax_initiate_outbound_call_with_from'); $this->loader->add_action('wp_ajax_twp_get_callbacks', $plugin_admin, 'ajax_get_callbacks'); // Phone number maintenance $this->loader->add_action('wp_ajax_twp_update_phone_status_callbacks', $plugin_admin, 'ajax_update_phone_status_callbacks'); $this->loader->add_action('wp_ajax_twp_toggle_number_status_callback', $plugin_admin, 'ajax_toggle_number_status_callback'); // Browser phone $this->loader->add_action('wp_ajax_twp_generate_capability_token', $plugin_admin, 'ajax_generate_capability_token'); $this->loader->add_action('wp_ajax_twp_save_call_mode', $plugin_admin, 'ajax_save_call_mode'); $this->loader->add_action('wp_ajax_twp_auto_configure_twiml_app', $plugin_admin, 'ajax_auto_configure_twiml_app'); $this->loader->add_action('wp_ajax_twp_configure_phone_numbers_only', $plugin_admin, 'ajax_configure_phone_numbers_only'); // SMS management $this->loader->add_action('wp_ajax_twp_delete_sms', $plugin_admin, 'ajax_delete_sms'); $this->loader->add_action('wp_ajax_twp_delete_conversation', $plugin_admin, 'ajax_delete_conversation'); $this->loader->add_action('wp_ajax_twp_get_conversation', $plugin_admin, 'ajax_get_conversation'); $this->loader->add_action('wp_ajax_twp_send_sms_reply', $plugin_admin, 'ajax_send_sms_reply'); } /** * Register public hooks */ private function define_public_hooks() { // Webhook endpoints $webhooks = new TWP_Webhooks(); $this->loader->add_action('init', $webhooks, 'register_endpoints'); // Initialize Agent Manager TWP_Agent_Manager::init(); // Scheduled events $scheduler = new TWP_Scheduler(); $this->loader->add_action('twp_check_schedules', $scheduler, 'check_active_schedules'); $queue = new TWP_Call_Queue(); $this->loader->add_action('twp_process_queue', $queue, 'process_waiting_calls'); // Callback processing $this->loader->add_action('twp_process_callbacks', 'TWP_Callback_Manager', 'process_callbacks'); // Call queue cleanup $this->loader->add_action('twp_cleanup_old_calls', $this, 'cleanup_old_calls'); // Schedule cron events if (!wp_next_scheduled('twp_check_schedules')) { wp_schedule_event(time(), 'twp_every_minute', 'twp_check_schedules'); } if (!wp_next_scheduled('twp_process_queue')) { wp_schedule_event(time(), 'twp_every_30_seconds', 'twp_process_queue'); } if (!wp_next_scheduled('twp_process_callbacks')) { wp_schedule_event(time(), 'twp_every_minute', 'twp_process_callbacks'); } if (!wp_next_scheduled('twp_cleanup_old_calls')) { wp_schedule_event(time(), 'hourly', 'twp_cleanup_old_calls'); } } /** * Register API hooks */ private function define_api_hooks() { // REST API endpoints add_action('rest_api_init', function() { register_rest_route('twilio-wp/v1', '/schedules', array( 'methods' => 'GET', 'callback' => array('TWP_Scheduler', 'get_schedules'), 'permission_callback' => function() { return current_user_can('manage_options'); } )); register_rest_route('twilio-wp/v1', '/workflows', array( 'methods' => 'GET', 'callback' => array('TWP_Workflow', 'get_workflows'), 'permission_callback' => function() { return current_user_can('manage_options'); } )); register_rest_route('twilio-wp/v1', '/queue-status', array( 'methods' => 'GET', 'callback' => array('TWP_Call_Queue', 'get_queue_status'), 'permission_callback' => function() { return current_user_can('manage_options'); } )); }); } /** * Run the loader */ public function run() { // Initialize webhooks $webhooks = new TWP_Webhooks(); $webhooks->register_endpoints(); // Add custom cron schedules add_filter('cron_schedules', function($schedules) { $schedules['twp_every_minute'] = array( 'interval' => 60, 'display' => __('Every Minute', 'twilio-wp-plugin') ); $schedules['twp_every_30_seconds'] = array( 'interval' => 30, 'display' => __('Every 30 Seconds', 'twilio-wp-plugin') ); return $schedules; }); $this->loader->run(); } /** * Get plugin name */ public function get_plugin_name() { return $this->plugin_name; } /** * Get version */ public function get_version() { return $this->version; } /** * Cleanup old stuck calls */ public function cleanup_old_calls() { global $wpdb; $calls_table = $wpdb->prefix . 'twp_queued_calls'; // Clean up calls that have been in 'answered' status for more than 2 hours // These are likely stuck due to missed webhooks or other issues $updated = $wpdb->query( "UPDATE $calls_table SET status = 'completed', ended_at = NOW() WHERE status = 'answered' AND joined_at < DATE_SUB(NOW(), INTERVAL 2 HOUR)" ); if ($updated > 0) { error_log("TWP Cleanup: Updated {$updated} stuck calls from 'answered' to 'completed' status"); } // Backup check for waiting calls (status callbacks should handle most cases) // Only check older calls that might have missed status callbacks $waiting_calls = $wpdb->get_results( "SELECT call_sid FROM $calls_table WHERE status = 'waiting' AND joined_at < DATE_SUB(NOW(), INTERVAL 30 MINUTE) AND joined_at > DATE_SUB(NOW(), INTERVAL 6 HOUR) LIMIT 5" ); if (!empty($waiting_calls)) { $twilio = new TWP_Twilio_API(); $cleaned_count = 0; foreach ($waiting_calls as $call) { try { // Check call status with Twilio $call_info = $twilio->get_call_info($call->call_sid); if ($call_info && isset($call_info['status'])) { // If call is completed, busy, failed, or canceled, remove from queue if (in_array($call_info['status'], ['completed', 'busy', 'failed', 'canceled'])) { $wpdb->update( $calls_table, array( 'status' => 'hangup', 'ended_at' => current_time('mysql') ), array('call_sid' => $call->call_sid), array('%s', '%s'), array('%s') ); $cleaned_count++; error_log("TWP Cleanup: Detected ended call {$call->call_sid} with status {$call_info['status']}"); } } } catch (Exception $e) { error_log("TWP Cleanup: Error checking call {$call->call_sid}: " . $e->getMessage()); } } if ($cleaned_count > 0) { error_log("TWP Cleanup: Cleaned up {$cleaned_count} ended calls from queue"); } } // Clean up very old waiting calls (older than 24 hours) - these are likely orphaned $updated_waiting = $wpdb->query( "UPDATE $calls_table SET status = 'timeout', ended_at = NOW() WHERE status = 'waiting' AND joined_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)" ); if ($updated_waiting > 0) { error_log("TWP Cleanup: Updated {$updated_waiting} old waiting calls to 'timeout' status"); } } }