From b5e091d84514ef527017ef66405aa57ab1a9e9e1 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 6 Aug 2025 16:04:03 -0700 Subject: [PATCH] twilio project revision --- CLAUDE.md | 248 +++++++++++++++++++++++++++----- admin/class-twp-admin.php | 45 ++++-- includes/class-twp-webhooks.php | 65 ++++++--- 3 files changed, 289 insertions(+), 69 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e6f7e76..6f79366 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,31 +4,191 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is a WordPress plugin for integrating Twilio functionality. The plugin is in early development stage. +This is a comprehensive WordPress plugin for Twilio voice and SMS integration, featuring: +- **Call Center Functionality**: Agent groups, call queues, call distribution +- **Business Hours Management**: Schedules for workflow routing +- **Outbound Calling**: Click-to-call with proper caller ID +- **SMS Notifications**: Agent notifications and callback management +- **Workflow Builder**: Visual call flow creation +- **Voicemail System**: Recording and transcription +- **Real-time Dashboard**: Agent queue management -## WordPress Plugin Development Structure +## Plugin Architecture -When developing this plugin, follow WordPress plugin conventions: -- Main plugin file should be in the root directory (e.g., `twilio-wp-plugin.php`) -- Use `includes/` directory for PHP class files and core functionality -- Use `admin/` directory for admin-specific functionality -- Use `public/` directory for frontend functionality -- Use `assets/` directory for CSS, JS, and image files +### Core Classes (`includes/` directory) +- **TWP_Core**: Main plugin initialization and hook registration +- **TWP_Activator**: Database table creation and plugin activation +- **TWP_Twilio_API**: Twilio REST API wrapper (custom implementation) +- **TWP_Webhooks**: Handles all Twilio webhook endpoints +- **TWP_Scheduler**: Business hours and schedule management +- **TWP_Workflow**: Call flow processing and TwiML generation +- **TWP_Call_Queue**: Queue management and position tracking +- **TWP_Agent_Groups**: Group management for call distribution +- **TWP_Agent_Manager**: Agent status and call acceptance +- **TWP_Callback_Manager**: Callback requests and processing +- **TWP_Call_Logger**: Call logging and statistics + +### Database Tables +Created by `TWP_Activator::create_tables()`: +- `twp_phone_schedules` - Business hours definitions +- `twp_call_queues` - Queue configurations +- `twp_queued_calls` - Active calls in queues +- `twp_workflows` - Call flow definitions +- `twp_call_log` - Complete call history +- `twp_sms_log` - SMS message tracking +- `twp_voicemails` - Voicemail recordings and transcriptions +- `twp_agent_groups` - Agent group definitions +- `twp_group_members` - User-to-group relationships +- `twp_agent_status` - Real-time agent availability +- `twp_callbacks` - Callback request queue + +### Admin Interface (`admin/` directory) +- **TWP_Admin**: Complete admin interface with pages for: + - Dashboard with real-time statistics + - Business Hours Schedules management + - Workflow Builder with drag-drop interface + - Call Queues configuration + - Phone Numbers management (purchase/configure) + - Voicemail inbox with playback + - Call Logs with filtering + - Agent Groups management + - Agent Queue dashboard + - **Outbound Calls interface** (click-to-call) + +### Webhook Endpoints +All registered in `TWP_Webhooks::register_endpoints()`: +- `/voice` - Main voice webhook for incoming calls +- `/sms` - SMS webhook (handles agent "1" responses) +- `/ivr-response` - IVR menu selections +- `/queue-wait` - Queue hold music and position announcements +- `/ring-group-result` - Handle no-answer scenarios and requeuing +- `/agent-connect` - Connect SMS-responding agents to calls +- `/callback-*` - Callback system webhooks +- `/outbound-agent-with-from` - Outbound calling with from number + +## Key Features Implementation + +### Agent Group System +- **Simultaneous Ring**: All group members called at once +- **Priority Ordering**: Members have priority levels +- **SMS Notifications**: Automatic alerts when no agents available +- **One-Click Accept**: Agents can text "1" to receive calls +- **Real-time Status**: Available/busy/offline tracking + +### Call Queue Management +- **Position Tracking**: Callers know their queue position +- **Timeout Handling**: Offers callback instead of hanging up +- **Auto-requeue**: Failed agent connections return to queue +- **Real-time Dashboard**: Agents see waiting calls + +### Callback System +- **Smart Callbacks**: Replace timeout hangups +- **SMS Confirmations**: Notify customers of callback requests +- **Automatic Processing**: Cron-based callback execution +- **Statistics Tracking**: Success rates and timing + +### Outbound Calling +- **From Number Selection**: Choose business line for caller ID +- **Conference Routing**: Agent-to-customer connection +- **Call Logging**: Track all outbound attempts +- **Proper Caller ID**: Target sees business number + +## WordPress Plugin Conventions + +### File Structure +``` +twilio-wp-plugin/ +├── twilio-wp-plugin.php (main plugin file) +├── includes/ (core functionality) +├── admin/ (admin interface) +├── assets/ (CSS/JS/images) +├── CLAUDE.md (this file) +``` + +### Class Naming +- Prefix: `TWP_` for all classes +- Database tables: `twp_` prefix +- Options: `twp_` prefix +- AJAX actions: `twp_` prefix + +### Database Operations +- Use WordPress `$wpdb` global for all database operations +- Always sanitize inputs with `sanitize_text_field()`, `intval()`, etc. +- Use prepared statements with `$wpdb->prepare()` +- Call `TWP_Activator::ensure_tables_exist()` before database operations + +### AJAX Handling +All AJAX endpoints registered in `TWP_Core::define_admin_hooks()`: +```php +$this->loader->add_action('wp_ajax_twp_action_name', $plugin_admin, 'ajax_action_name'); +``` + +### User Profile Integration +Agent phone numbers stored as user meta: +- Meta key: `twp_phone_number` +- Validation: `TWP_Agent_Manager::validate_phone_number()` +- Duplicate checking: `TWP_Agent_Manager::is_phone_number_duplicate()` + +## Twilio Integration + +### Current Implementation +- **Custom API Wrapper**: `TWP_Twilio_API` class using `wp_remote_post()` +- **TwiML Generation**: String-based XML construction +- **Response Handling**: Custom parsing of Twilio responses + +### Recommended Migration to Twilio PHP SDK +**URL**: https://www.twilio.com/docs/libraries/reference/twilio-php/ + +**Benefits**: +- Official SDK with better error handling +- Built-in TwiML generation classes +- Automatic retries and rate limiting +- Type safety and IDE support + +**Migration Strategy**: +1. Install via Composer: `composer require twilio/sdk` +2. Replace `TWP_Twilio_API` methods with SDK calls +3. Update TwiML generation to use SDK classes +4. Maintain existing method signatures for compatibility + +### API Response Structure +Current Twilio API responses follow this pattern: +```php +// Success response +[ + 'success' => true, + 'data' => [...] // Twilio response data +] + +// Error response +[ + 'success' => false, + 'error' => 'Error message', + 'code' => 400 +] + +// Call SID location +$call_sid = $response['data']['sid']; // NOT $response['call_sid'] +``` + +### TwiML Best Practices +Always include XML declaration: +```php +$twiml = ''; +$twiml .= ''; +$twiml .= 'Message'; +$twiml .= ''; +``` ## Development Commands -Since this is a WordPress plugin, typical commands include: - ```bash -# Install WordPress development dependencies (if using Composer) +# Install WordPress development dependencies composer install -# Install JavaScript dependencies (if using npm) +# Install JavaScript dependencies npm install -# Build assets (if using build tools) -npm run build - # Run PHP CodeSniffer for WordPress coding standards vendor/bin/phpcs @@ -37,27 +197,47 @@ vendor/bin/phpcbf # Run PHPUnit tests vendor/bin/phpunit + +# Install Twilio SDK (recommended migration) +composer require twilio/sdk ``` -## Key WordPress Plugin Conventions - -- Use WordPress hooks and filters system for extending functionality -- Follow WordPress coding standards for PHP, JavaScript, and CSS -- Prefix all functions, classes, and global variables with plugin-specific prefix to avoid conflicts -- Use WordPress's built-in functions for database operations, HTTP requests, and sanitization -- Store Twilio API credentials using WordPress options API with encryption - -## Twilio Integration Points - -When working with Twilio API: -- Store API credentials securely in WordPress options -- Use WordPress's `wp_remote_post()` and `wp_remote_get()` for API calls -- Implement proper error handling and logging using WordPress error logging -- Consider rate limiting and webhook verification for security - ## Testing Approach -- Use PHPUnit for unit testing PHP code +### Unit Testing +- Use PHPUnit for PHP code testing - Mock WordPress functions using Brain Monkey or WP_Mock -- Test Twilio API interactions using mock responses -- Use WordPress testing framework for integration tests \ No newline at end of file +- Mock Twilio API responses for reliable testing + +### Integration Testing +- Test webhook endpoints with Twilio webhook simulator +- Test complete call flows end-to-end +- Verify database operations and data integrity + +### Manual Testing +- Use Twilio Console to monitor webhook calls +- Check WordPress error logs for debugging +- Test with real phone numbers for user experience + +## Common Issues & Solutions + +### Database Issues +- **Missing Tables**: Call `TWP_Activator::ensure_tables_exist()` +- **White Admin Modals**: Check for PHP errors in workflow loading + +### Webhook Issues +- **500 Errors**: Check PHP error logs for fatal errors +- **Missing Parameters**: Parameters must be in webhook URL, not just POST data +- **TwiML Parse Errors**: Always include XML declaration + +### API Issues +- **Call SID Access**: Use `$response['data']['sid']` not `$response['call_sid']` +- **Phone Number Format**: Store with + prefix (e.g., "+1234567890") +- **From Number**: Must be a verified Twilio number + +### Agent Management +- **SMS Responses**: Agents text "1" to receive calls +- **Phone Validation**: Auto-formats US numbers to +1 format +- **Duplicate Prevention**: Checks phone numbers across all users + +This plugin provides a complete call center solution with professional-grade features suitable for business use. \ No newline at end of file diff --git a/admin/class-twp-admin.php b/admin/class-twp-admin.php index 220002e..82b90a1 100644 --- a/admin/class-twp-admin.php +++ b/admin/class-twp-admin.php @@ -1594,14 +1594,26 @@ class TWP_Admin { get_phone_numbers(); + $numbers_result = $twilio->get_phone_numbers(); - if ($numbers['success'] && is_array($numbers['numbers'])) { - foreach ($numbers['numbers'] as $number) { - echo ''; + 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 ''; + } + } else { + echo ''; } } else { - echo ''; + echo ''; + if (isset($numbers_result['error'])) { + echo ''; + } + // Debug info for troubleshooting + if (current_user_can('manage_options') && WP_DEBUG) { + echo ''; + } } ?> @@ -2944,25 +2956,30 @@ class TWP_Admin { 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, - home_url('/wp-json/twilio-webhook/v1/outbound-agent-with-from'), - array( - 'target_number' => $to_number, - 'agent_user_id' => get_current_user_id(), - 'from_number' => $from_number - ), + $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', $agent_call_result['call_sid']); + TWP_Agent_Manager::set_agent_status(get_current_user_id(), 'busy', $call_sid); // Log the outbound call TWP_Call_Logger::log_call(array( - 'call_sid' => $agent_call_result['call_sid'], + 'call_sid' => $call_sid, 'from_number' => $from_number, 'to_number' => $to_number, 'status' => 'outbound_initiated', @@ -2975,7 +2992,7 @@ class TWP_Admin { )) )); - return array('success' => true, 'call_sid' => $agent_call_result['call_sid']); + return array('success' => true, 'call_sid' => $call_sid); } return array('success' => false, 'error' => $agent_call_result['error']); diff --git a/includes/class-twp-webhooks.php b/includes/class-twp-webhooks.php index a6db9be..515ae6e 100644 --- a/includes/class-twp-webhooks.php +++ b/includes/class-twp-webhooks.php @@ -1061,28 +1061,51 @@ class TWP_Webhooks { * Handle outbound agent with from number webhook */ public function handle_outbound_agent_with_from($request) { - $params = $request->get_params(); - $target_number = isset($params['target_number']) ? $params['target_number'] : ''; - $from_number = isset($params['from_number']) ? $params['from_number'] : ''; - $agent_call_sid = isset($params['CallSid']) ? $params['CallSid'] : ''; + try { + $params = $request->get_params(); + + // Get parameters from query string or POST body + $target_number = $request->get_param('target_number') ?: ''; + $from_number = $request->get_param('from_number') ?: ''; + $agent_call_sid = $request->get_param('CallSid') ?: ''; + + // Log parameters for debugging + error_log('TWP Outbound Webhook - Target: ' . $target_number . ', From: ' . $from_number . ', CallSid: ' . $agent_call_sid); + error_log('TWP Outbound Webhook - All params: ' . print_r($params, true)); + + if ($target_number && $from_number) { + // Create TwiML to call the target number with the specified from number + $twiml = ''; + $twiml .= ''; + $twiml .= 'Connecting your outbound call...'; + $twiml .= ''; + $twiml .= '' . esc_html($target_number) . ''; + $twiml .= ''; + $twiml .= 'The number you called is not available. Please try again later.'; + $twiml .= ''; + + return $this->send_twiml_response($twiml); + } else { + // Enhanced error message with debugging info + $error_msg = 'Unable to process outbound call.'; + if (empty($target_number)) { + $error_msg .= ' Missing target number.'; + } + if (empty($from_number)) { + $error_msg .= ' Missing from number.'; + } + + error_log('TWP Outbound Error: ' . $error_msg . ' Params: ' . json_encode($params)); + + $twiml = ''; + $twiml .= '' . esc_html($error_msg) . ''; + return $this->send_twiml_response($twiml); + } - if ($target_number && $from_number) { - // Create TwiML to call the target number with the specified from number - $twiml = new \Twilio\TwiML\VoiceResponse(); - $twiml->say('Connecting your outbound call...', ['voice' => 'alice']); - - $dial = $twiml->dial([ - 'callerId' => $from_number, // Use the specified from number as caller ID - 'timeout' => 30 - ]); - $dial->number($target_number); - - // If no answer, leave a message - $twiml->say('The number you called is not available. Please try again later.', ['voice' => 'alice']); - - return $this->send_twiml_response($twiml->asXML()); - } else { - $twiml = 'Unable to process outbound call.'; + } catch (Exception $e) { + error_log('TWP Outbound Webhook Exception: ' . $e->getMessage()); + $twiml = ''; + $twiml .= 'Technical error occurred. Please try again.'; return $this->send_twiml_response($twiml); } }