twilio project revision
This commit is contained in:
248
CLAUDE.md
248
CLAUDE.md
@@ -4,31 +4,191 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## 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:
|
### Core Classes (`includes/` directory)
|
||||||
- Main plugin file should be in the root directory (e.g., `twilio-wp-plugin.php`)
|
- **TWP_Core**: Main plugin initialization and hook registration
|
||||||
- Use `includes/` directory for PHP class files and core functionality
|
- **TWP_Activator**: Database table creation and plugin activation
|
||||||
- Use `admin/` directory for admin-specific functionality
|
- **TWP_Twilio_API**: Twilio REST API wrapper (custom implementation)
|
||||||
- Use `public/` directory for frontend functionality
|
- **TWP_Webhooks**: Handles all Twilio webhook endpoints
|
||||||
- Use `assets/` directory for CSS, JS, and image files
|
- **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 = '<?xml version="1.0" encoding="UTF-8"?>';
|
||||||
|
$twiml .= '<Response>';
|
||||||
|
$twiml .= '<Say voice="alice">Message</Say>';
|
||||||
|
$twiml .= '</Response>';
|
||||||
|
```
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
Since this is a WordPress plugin, typical commands include:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install WordPress development dependencies (if using Composer)
|
# Install WordPress development dependencies
|
||||||
composer install
|
composer install
|
||||||
|
|
||||||
# Install JavaScript dependencies (if using npm)
|
# Install JavaScript dependencies
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Build assets (if using build tools)
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Run PHP CodeSniffer for WordPress coding standards
|
# Run PHP CodeSniffer for WordPress coding standards
|
||||||
vendor/bin/phpcs
|
vendor/bin/phpcs
|
||||||
|
|
||||||
@@ -37,27 +197,47 @@ vendor/bin/phpcbf
|
|||||||
|
|
||||||
# Run PHPUnit tests
|
# Run PHPUnit tests
|
||||||
vendor/bin/phpunit
|
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
|
## 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
|
- Mock WordPress functions using Brain Monkey or WP_Mock
|
||||||
- Test Twilio API interactions using mock responses
|
- Mock Twilio API responses for reliable testing
|
||||||
- Use WordPress testing framework for integration tests
|
|
||||||
|
### 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.
|
@@ -1594,14 +1594,26 @@ class TWP_Admin {
|
|||||||
<?php
|
<?php
|
||||||
// Get Twilio phone numbers
|
// Get Twilio phone numbers
|
||||||
$twilio = new TWP_Twilio_API();
|
$twilio = new TWP_Twilio_API();
|
||||||
$numbers = $twilio->get_phone_numbers();
|
$numbers_result = $twilio->get_phone_numbers();
|
||||||
|
|
||||||
if ($numbers['success'] && is_array($numbers['numbers'])) {
|
if ($numbers_result['success'] && isset($numbers_result['data']['incoming_phone_numbers'])) {
|
||||||
foreach ($numbers['numbers'] as $number) {
|
$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>';
|
echo '<option value="' . esc_attr($number['phone_number']) . '">' . esc_html($number['phone_number']) . '</option>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo '<option value="" disabled>No phone numbers available - purchase a number first</option>';
|
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>
|
</select>
|
||||||
@@ -2944,25 +2956,30 @@ class TWP_Admin {
|
|||||||
private function initiate_outbound_call_with_from($from_number, $to_number, $agent_phone) {
|
private function initiate_outbound_call_with_from($from_number, $to_number, $agent_phone) {
|
||||||
$twilio = new TWP_Twilio_API();
|
$twilio = new TWP_Twilio_API();
|
||||||
|
|
||||||
// First call the agent
|
// Build webhook URL with parameters
|
||||||
$agent_call_result = $twilio->make_call(
|
$webhook_url = home_url('/wp-json/twilio-webhook/v1/outbound-agent-with-from') . '?' . http_build_query(array(
|
||||||
$agent_phone,
|
|
||||||
home_url('/wp-json/twilio-webhook/v1/outbound-agent-with-from'),
|
|
||||||
array(
|
|
||||||
'target_number' => $to_number,
|
'target_number' => $to_number,
|
||||||
'agent_user_id' => get_current_user_id(),
|
'agent_user_id' => get_current_user_id(),
|
||||||
'from_number' => $from_number
|
'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
|
$from_number // Use specified from number
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($agent_call_result['success']) {
|
if ($agent_call_result['success']) {
|
||||||
|
$call_sid = isset($agent_call_result['data']['sid']) ? $agent_call_result['data']['sid'] : null;
|
||||||
|
|
||||||
// Set agent to busy
|
// 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
|
// Log the outbound call
|
||||||
TWP_Call_Logger::log_call(array(
|
TWP_Call_Logger::log_call(array(
|
||||||
'call_sid' => $agent_call_result['call_sid'],
|
'call_sid' => $call_sid,
|
||||||
'from_number' => $from_number,
|
'from_number' => $from_number,
|
||||||
'to_number' => $to_number,
|
'to_number' => $to_number,
|
||||||
'status' => 'outbound_initiated',
|
'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']);
|
return array('success' => false, 'error' => $agent_call_result['error']);
|
||||||
|
@@ -1061,28 +1061,51 @@ class TWP_Webhooks {
|
|||||||
* Handle outbound agent with from number webhook
|
* Handle outbound agent with from number webhook
|
||||||
*/
|
*/
|
||||||
public function handle_outbound_agent_with_from($request) {
|
public function handle_outbound_agent_with_from($request) {
|
||||||
|
try {
|
||||||
$params = $request->get_params();
|
$params = $request->get_params();
|
||||||
$target_number = isset($params['target_number']) ? $params['target_number'] : '';
|
|
||||||
$from_number = isset($params['from_number']) ? $params['from_number'] : '';
|
// Get parameters from query string or POST body
|
||||||
$agent_call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
$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) {
|
if ($target_number && $from_number) {
|
||||||
// Create TwiML to call the target number with the specified from number
|
// Create TwiML to call the target number with the specified from number
|
||||||
$twiml = new \Twilio\TwiML\VoiceResponse();
|
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
|
||||||
$twiml->say('Connecting your outbound call...', ['voice' => 'alice']);
|
$twiml .= '<Response>';
|
||||||
|
$twiml .= '<Say voice="alice">Connecting your outbound call...</Say>';
|
||||||
|
$twiml .= '<Dial callerId="' . esc_attr($from_number) . '" timeout="30">';
|
||||||
|
$twiml .= '<Number>' . esc_html($target_number) . '</Number>';
|
||||||
|
$twiml .= '</Dial>';
|
||||||
|
$twiml .= '<Say voice="alice">The number you called is not available. Please try again later.</Say>';
|
||||||
|
$twiml .= '</Response>';
|
||||||
|
|
||||||
$dial = $twiml->dial([
|
return $this->send_twiml_response($twiml);
|
||||||
'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 {
|
} else {
|
||||||
$twiml = '<Response><Say voice="alice">Unable to process outbound call.</Say><Hangup/></Response>';
|
// 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 = '<?xml version="1.0" encoding="UTF-8"?>';
|
||||||
|
$twiml .= '<Response><Say voice="alice">' . esc_html($error_msg) . '</Say><Hangup/></Response>';
|
||||||
|
return $this->send_twiml_response($twiml);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('TWP Outbound Webhook Exception: ' . $e->getMessage());
|
||||||
|
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
|
||||||
|
$twiml .= '<Response><Say voice="alice">Technical error occurred. Please try again.</Say><Hangup/></Response>';
|
||||||
return $this->send_twiml_response($twiml);
|
return $this->send_twiml_response($twiml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user