prefix . $table; $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)); if (!$table_exists) { $missing_tables[] = $table; } } if (!empty($missing_tables)) { error_log('TWP Plugin: Missing database tables: ' . implode(', ', $missing_tables) . '. Creating them now.'); self::create_tables(); return false; // Tables were missing } // Check for and perform any needed migrations self::migrate_tables(); // Always run column updates to catch any schema changes self::add_missing_columns(); return true; // All tables exist } /** * Create plugin database tables */ private static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // Phone schedules table $table_schedules = $wpdb->prefix . 'twp_phone_schedules'; $sql_schedules = "CREATE TABLE $table_schedules ( id int(11) NOT NULL AUTO_INCREMENT, phone_number varchar(20), schedule_name varchar(100) NOT NULL, days_of_week varchar(100) NOT NULL, start_time time NOT NULL, end_time time NOT NULL, workflow_id varchar(100), forward_number varchar(20), after_hours_action varchar(20) DEFAULT 'workflow', after_hours_workflow_id varchar(100), after_hours_forward_number varchar(20), holiday_dates text, is_active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY phone_number (phone_number) ) $charset_collate;"; // Call queues table (enhanced for user-specific queues) $table_queues = $wpdb->prefix . 'twp_call_queues'; $sql_queues = "CREATE TABLE $table_queues ( id int(11) NOT NULL AUTO_INCREMENT, queue_name varchar(100) NOT NULL, queue_type varchar(20) DEFAULT 'general', user_id bigint(20), extension varchar(10), notification_number varchar(20), agent_group_id int(11), max_size int(11) DEFAULT 10, wait_music_url varchar(255), tts_message text, timeout_seconds int(11) DEFAULT 300, voicemail_prompt text, is_hold_queue tinyint(1) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY agent_group_id (agent_group_id), KEY notification_number (notification_number), KEY user_id (user_id), KEY extension (extension), KEY queue_type (queue_type), UNIQUE KEY user_queue (user_id, queue_type) ) $charset_collate;"; // Queued calls table $table_queued_calls = $wpdb->prefix . 'twp_queued_calls'; $sql_queued_calls = "CREATE TABLE $table_queued_calls ( id int(11) NOT NULL AUTO_INCREMENT, queue_id int(11) NOT NULL, call_sid varchar(100) NOT NULL, from_number varchar(20) NOT NULL, to_number varchar(20) NOT NULL, position int(11) NOT NULL, status varchar(20) DEFAULT 'waiting', agent_phone varchar(20), agent_call_sid varchar(100), joined_at datetime DEFAULT CURRENT_TIMESTAMP, answered_at datetime, ended_at datetime, PRIMARY KEY (id), KEY queue_id (queue_id), KEY call_sid (call_sid), KEY status (status) ) $charset_collate;"; // Workflows table (keeping phone_number for backward compatibility, will be deprecated) $table_workflows = $wpdb->prefix . 'twp_workflows'; $sql_workflows = "CREATE TABLE $table_workflows ( id int(11) NOT NULL AUTO_INCREMENT, workflow_name varchar(100) NOT NULL, phone_number varchar(20) NOT NULL, workflow_data longtext, is_active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY phone_number (phone_number) ) $charset_collate;"; // Workflow phone numbers junction table $table_workflow_phones = $wpdb->prefix . 'twp_workflow_phones'; $sql_workflow_phones = "CREATE TABLE $table_workflow_phones ( id int(11) NOT NULL AUTO_INCREMENT, workflow_id int(11) NOT NULL, phone_number varchar(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY workflow_phone (workflow_id, phone_number), KEY workflow_id (workflow_id), KEY phone_number (phone_number) ) $charset_collate;"; // Call log table $table_call_log = $wpdb->prefix . 'twp_call_log'; $sql_call_log = "CREATE TABLE $table_call_log ( id int(11) NOT NULL AUTO_INCREMENT, call_sid varchar(100) NOT NULL, from_number varchar(20), to_number varchar(20), status varchar(20) NOT NULL, duration int(11) DEFAULT 0, workflow_id int(11), workflow_name varchar(100), queue_time int(11) DEFAULT 0, actions_taken text, call_data longtext, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY call_sid (call_sid), KEY from_number (from_number), KEY status (status) ) $charset_collate;"; // SMS log table $table_sms_log = $wpdb->prefix . 'twp_sms_log'; $sql_sms_log = "CREATE TABLE $table_sms_log ( id int(11) NOT NULL AUTO_INCREMENT, message_sid varchar(100) NOT NULL, from_number varchar(20) NOT NULL, to_number varchar(20) NOT NULL, body text, received_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY message_sid (message_sid) ) $charset_collate;"; // Voicemails table $table_voicemails = $wpdb->prefix . 'twp_voicemails'; $sql_voicemails = "CREATE TABLE $table_voicemails ( id int(11) NOT NULL AUTO_INCREMENT, workflow_id int(11), from_number varchar(20) NOT NULL, recording_url varchar(255), duration int(11) DEFAULT 0, transcription text, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY workflow_id (workflow_id) ) $charset_collate;"; // Agent groups table $table_agent_groups = $wpdb->prefix . 'twp_agent_groups'; $sql_agent_groups = "CREATE TABLE $table_agent_groups ( id int(11) NOT NULL AUTO_INCREMENT, group_name varchar(100) NOT NULL, description text, ring_strategy varchar(20) DEFAULT 'simultaneous', timeout_seconds int(11) DEFAULT 30, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY group_name (group_name) ) $charset_collate;"; // Group members table $table_group_members = $wpdb->prefix . 'twp_group_members'; $sql_group_members = "CREATE TABLE $table_group_members ( id int(11) NOT NULL AUTO_INCREMENT, group_id int(11) NOT NULL, user_id bigint(20) NOT NULL, priority int(11) DEFAULT 0, is_active tinyint(1) DEFAULT 1, added_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY group_id (group_id), KEY user_id (user_id), UNIQUE KEY group_user (group_id, user_id) ) $charset_collate;"; // Agent status table (enhanced with login tracking) $table_agent_status = $wpdb->prefix . 'twp_agent_status'; $sql_agent_status = "CREATE TABLE $table_agent_status ( id int(11) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, status varchar(20) DEFAULT 'offline', is_logged_in tinyint(1) DEFAULT 0, logged_in_at datetime, current_call_sid varchar(100), last_activity datetime DEFAULT CURRENT_TIMESTAMP, available_for_queues tinyint(1) DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY user_id (user_id) ) $charset_collate;"; // User extensions table $table_user_extensions = $wpdb->prefix . 'twp_user_extensions'; $sql_user_extensions = "CREATE TABLE $table_user_extensions ( id int(11) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, extension varchar(10) NOT NULL, direct_dial_number varchar(20), personal_queue_id int(11), hold_queue_id int(11), created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY user_id (user_id), UNIQUE KEY extension (extension), KEY personal_queue_id (personal_queue_id), KEY hold_queue_id (hold_queue_id) ) $charset_collate;"; // Queue assignments table (many-to-many relationship) $table_queue_assignments = $wpdb->prefix . 'twp_queue_assignments'; $sql_queue_assignments = "CREATE TABLE $table_queue_assignments ( id int(11) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, queue_id int(11) NOT NULL, is_primary tinyint(1) DEFAULT 0, assigned_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY user_queue (user_id, queue_id), KEY user_id (user_id), KEY queue_id (queue_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_schedules); dbDelta($sql_queues); dbDelta($sql_queued_calls); dbDelta($sql_workflows); dbDelta($sql_call_log); dbDelta($sql_sms_log); dbDelta($sql_voicemails); // Callbacks table $table_callbacks = $wpdb->prefix . 'twp_callbacks'; $sql_callbacks = "CREATE TABLE $table_callbacks ( id int(11) NOT NULL AUTO_INCREMENT, phone_number varchar(20) NOT NULL, requested_at datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'pending', attempts int(11) DEFAULT 0, last_attempt datetime, completed_at datetime, queue_id int(11), original_call_sid varchar(100), callback_call_sid varchar(100), notes text, PRIMARY KEY (id), KEY phone_number (phone_number), KEY status (status), KEY queue_id (queue_id) ) $charset_collate;"; // Call recordings table $table_recordings = $wpdb->prefix . 'twp_call_recordings'; $sql_recordings = "CREATE TABLE $table_recordings ( id int(11) NOT NULL AUTO_INCREMENT, call_sid varchar(100) NOT NULL, recording_sid varchar(100), recording_url varchar(500), duration int(11) DEFAULT 0, from_number varchar(50), to_number varchar(50), agent_id bigint(20), status varchar(20) DEFAULT 'recording', started_at datetime DEFAULT CURRENT_TIMESTAMP, ended_at datetime, file_size int(11), transcription text, notes text, PRIMARY KEY (id), KEY call_sid (call_sid), KEY recording_sid (recording_sid), KEY agent_id (agent_id), KEY started_at (started_at) ) $charset_collate;"; dbDelta($sql_schedules); dbDelta($sql_queues); dbDelta($sql_queued_calls); dbDelta($sql_workflows); dbDelta($sql_workflow_phones); dbDelta($sql_call_log); dbDelta($sql_sms_log); dbDelta($sql_voicemails); dbDelta($sql_agent_groups); dbDelta($sql_group_members); dbDelta($sql_agent_status); dbDelta($sql_callbacks); dbDelta($sql_recordings); dbDelta($sql_user_extensions); dbDelta($sql_queue_assignments); // Add missing columns for existing installations self::add_missing_columns(); } /** * Add missing columns for existing installations */ private static function add_missing_columns() { global $wpdb; // Add new queue columns for user-specific queues $table_queues = $wpdb->prefix . 'twp_call_queues'; // Check and add queue_type column $queue_type_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'queue_type'"); if (empty($queue_type_exists)) { $wpdb->query("ALTER TABLE $table_queues ADD COLUMN queue_type varchar(20) DEFAULT 'general' AFTER queue_name"); $wpdb->query("ALTER TABLE $table_queues ADD INDEX queue_type (queue_type)"); } // Check and add user_id column $user_id_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'user_id'"); if (empty($user_id_exists)) { $wpdb->query("ALTER TABLE $table_queues ADD COLUMN user_id bigint(20) AFTER queue_type"); $wpdb->query("ALTER TABLE $table_queues ADD INDEX user_id (user_id)"); } // Check and add extension column $extension_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'extension'"); if (empty($extension_exists)) { $wpdb->query("ALTER TABLE $table_queues ADD COLUMN extension varchar(10) AFTER user_id"); $wpdb->query("ALTER TABLE $table_queues ADD INDEX extension (extension)"); } // Check and add voicemail_prompt column $voicemail_prompt_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'voicemail_prompt'"); if (empty($voicemail_prompt_exists)) { $wpdb->query("ALTER TABLE $table_queues ADD COLUMN voicemail_prompt text AFTER timeout_seconds"); } // Check and add is_hold_queue column $is_hold_queue_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'is_hold_queue'"); if (empty($is_hold_queue_exists)) { $wpdb->query("ALTER TABLE $table_queues ADD COLUMN is_hold_queue tinyint(1) DEFAULT 0 AFTER voicemail_prompt"); } // Add unique constraint for user queues if it doesn't exist $unique_constraint_exists = $wpdb->get_results("SHOW INDEX FROM $table_queues WHERE Key_name = 'user_queue'"); if (empty($unique_constraint_exists)) { // Only add if both columns exist if (!empty($user_id_exists) || $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'user_id'")) { $wpdb->query("ALTER TABLE $table_queues ADD UNIQUE KEY user_queue (user_id, queue_type)"); } } // Add user_id column to voicemails table for extension voicemails $table_voicemails = $wpdb->prefix . 'twp_voicemails'; $voicemail_user_id_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_voicemails LIKE 'user_id'"); if (empty($voicemail_user_id_exists)) { $wpdb->query("ALTER TABLE $table_voicemails ADD COLUMN user_id int(11) DEFAULT NULL AFTER workflow_id"); $wpdb->query("ALTER TABLE $table_voicemails ADD INDEX user_id (user_id)"); } // Add login tracking columns to agent_status table $table_agent_status = $wpdb->prefix . 'twp_agent_status'; $is_logged_in_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_agent_status LIKE 'is_logged_in'"); if (empty($is_logged_in_exists)) { $wpdb->query("ALTER TABLE $table_agent_status ADD COLUMN is_logged_in tinyint(1) DEFAULT 0 AFTER status"); } $logged_in_at_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_agent_status LIKE 'logged_in_at'"); if (empty($logged_in_at_exists)) { $wpdb->query("ALTER TABLE $table_agent_status ADD COLUMN logged_in_at datetime AFTER is_logged_in"); } // Add auto_busy_at column to track when agent was automatically set to busy $auto_busy_at_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_agent_status LIKE 'auto_busy_at'"); if (empty($auto_busy_at_exists)) { $wpdb->query("ALTER TABLE $table_agent_status ADD COLUMN auto_busy_at datetime DEFAULT NULL AFTER logged_in_at"); } $table_schedules = $wpdb->prefix . 'twp_phone_schedules'; // Check if holiday_dates column exists $column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_schedules LIKE 'holiday_dates'"); if (empty($column_exists)) { $wpdb->query("ALTER TABLE $table_schedules ADD COLUMN holiday_dates text AFTER after_hours_forward_number"); } // Check if days_of_week column needs to be expanded $column_info = $wpdb->get_results("SHOW COLUMNS FROM $table_schedules LIKE 'days_of_week'"); if (!empty($column_info) && $column_info[0]->Type === 'varchar(20)') { $wpdb->query("ALTER TABLE $table_schedules MODIFY COLUMN days_of_week varchar(100) NOT NULL"); } // Add new columns to call queues table and migrate phone_number to notification_number $table_queues = $wpdb->prefix . 'twp_call_queues'; // Check if phone_number column exists and notification_number doesn't - need migration $phone_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'phone_number'"); $notification_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'notification_number'"); if (!empty($phone_column_exists) && empty($notification_column_exists)) { // Migrate phone_number to notification_number $wpdb->query("ALTER TABLE $table_queues CHANGE phone_number notification_number varchar(20)"); // Update the index name $wpdb->query("ALTER TABLE $table_queues DROP INDEX phone_number"); $wpdb->query("ALTER TABLE $table_queues ADD INDEX notification_number (notification_number)"); } elseif (empty($phone_column_exists) && empty($notification_column_exists)) { // Fresh installation - add notification_number column $wpdb->query("ALTER TABLE $table_queues ADD COLUMN notification_number varchar(20) AFTER queue_name"); $wpdb->query("ALTER TABLE $table_queues ADD INDEX notification_number (notification_number)"); } // Check if agent_group_id column exists in queues table $group_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'agent_group_id'"); if (empty($group_column_exists)) { $wpdb->query("ALTER TABLE $table_queues ADD COLUMN agent_group_id int(11) AFTER notification_number"); $wpdb->query("ALTER TABLE $table_queues ADD INDEX agent_group_id (agent_group_id)"); } // Add agent columns to queued_calls table if they don't exist $table_queued_calls = $wpdb->prefix . 'twp_queued_calls'; $agent_phone_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queued_calls LIKE 'agent_phone'"); if (empty($agent_phone_exists)) { $wpdb->query("ALTER TABLE $table_queued_calls ADD COLUMN agent_phone varchar(20) AFTER status"); } $agent_call_sid_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queued_calls LIKE 'agent_call_sid'"); if (empty($agent_call_sid_exists)) { $wpdb->query("ALTER TABLE $table_queued_calls ADD COLUMN agent_call_sid varchar(100) AFTER agent_phone"); } // Add status index if it doesn't exist $status_index_exists = $wpdb->get_results("SHOW INDEX FROM $table_queued_calls WHERE Key_name = 'status'"); if (empty($status_index_exists)) { $wpdb->query("ALTER TABLE $table_queued_calls ADD INDEX status (status)"); } // Add notified_timeout column for Discord/Slack notifications $notified_timeout_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queued_calls LIKE 'notified_timeout'"); if (empty($notified_timeout_exists)) { $wpdb->query("ALTER TABLE $table_queued_calls ADD COLUMN notified_timeout datetime AFTER agent_call_sid"); } // Add enqueued_at column (some code uses this instead of joined_at) $enqueued_at_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queued_calls LIKE 'enqueued_at'"); if (empty($enqueued_at_exists)) { $wpdb->query("ALTER TABLE $table_queued_calls ADD COLUMN enqueued_at datetime DEFAULT CURRENT_TIMESTAMP AFTER notified_timeout"); error_log('TWP: Added enqueued_at column to twp_queued_calls table'); } // Fix phone number field lengths in call recordings table for outbound calls $table_recordings = $wpdb->prefix . 'twp_call_recordings'; // Check if table exists first $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_recordings)); if ($table_exists) { // Check current column lengths $from_column = $wpdb->get_results("SHOW COLUMNS FROM $table_recordings LIKE 'from_number'"); if (!empty($from_column) && strpos($from_column[0]->Type, 'varchar(20)') !== false) { $wpdb->query("ALTER TABLE $table_recordings MODIFY COLUMN from_number varchar(50)"); error_log('TWP: Updated from_number column to varchar(50)'); } $to_column = $wpdb->get_results("SHOW COLUMNS FROM $table_recordings LIKE 'to_number'"); if (!empty($to_column) && strpos($to_column[0]->Type, 'varchar(20)') !== false) { $wpdb->query("ALTER TABLE $table_recordings MODIFY COLUMN to_number varchar(50)"); error_log('TWP: Updated to_number column to varchar(50)'); } } } /** * Perform table migrations for existing installations */ private static function migrate_tables() { // Call the existing add_missing_columns function which now includes queue columns self::add_missing_columns(); } /** * Force database table updates - can be called manually if needed */ public static function force_table_updates() { self::add_missing_columns(); error_log('TWP: Forced database table updates completed'); } /** * Set default plugin options */ private static function set_default_options() { add_option('twp_twilio_account_sid', ''); add_option('twp_twilio_auth_token', ''); add_option('twp_elevenlabs_api_key', ''); add_option('twp_elevenlabs_voice_id', ''); add_option('twp_elevenlabs_model_id', 'eleven_multilingual_v2'); add_option('twp_default_queue_timeout', 300); add_option('twp_default_queue_size', 10); add_option('twp_urgent_keywords', 'urgent,emergency,important,asap,help'); add_option('twp_sms_notification_number', ''); add_option('twp_default_sms_number', ''); } /** * Create custom user roles for the plugin */ private static function create_user_roles() { // Remove role first if it exists to ensure clean setup remove_role('phone_agent'); // Add 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, )); } }