Fix hold/resume functionality and customer number detection
CRITICAL FIXES: - Fixed hold/resume functions to use proper Hold Queue system instead of empty TwiML - Enhanced customer number detection for voicemail and call recording interfaces - Resolved customer disconnections during hold/resume operations on outbound calls Hold/Resume System Improvements: - Updated ajax_toggle_hold() to use TWP_User_Queue_Manager for proper queue management - Fixed resume function to redirect calls back to appropriate queues (personal/shared) - Added comprehensive logging for hold queue operations and call leg detection - Enhanced hold experience with proper TTS messages and queue tracking Customer Number Detection Enhancements: - Enhanced handle_voicemail_callback() with fallback logic for missing From parameters - Added browser phone detection to identify client: calls and find real customer numbers - Enhanced call recording to use find_customer_call_leg() for proper customer identification - Fixed admin interfaces to show actual phone numbers instead of "client:agentname" Technical Improvements: - Integrated Hold Queue system with existing call leg detection infrastructure - Added proper TwiML generation for hold/resume operations - Enhanced error handling and logging for debugging complex call topologies - Maintains database consistency with queue position tracking 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
671
CLAUDE.md
671
CLAUDE.md
@@ -1,19 +1,20 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md - Twilio WordPress Plugin Documentation
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
This file provides comprehensive guidance to Claude Code (claude.ai/code) when working with the Twilio WordPress Plugin codebase.
|
||||||
|
|
||||||
## 🚨 CRITICAL: Testing & Deployment Environment
|
## 🚨 CRITICAL: Testing & Deployment Environment
|
||||||
|
|
||||||
**THIS PLUGIN RUNS ON A REMOTE SERVER IN A DOCKER CONTAINER - NOT LOCALLY**
|
**THIS PLUGIN RUNS ON A REMOTE SERVER IN A DOCKER CONTAINER - NOT LOCALLY**
|
||||||
- **Production Server Path**: `/home/shadowdao/public_html/wp-content/plugins/twilio-wp-plugin/`
|
- **Production Server Path**: `/home/shadowdao/public_html/wp-content/plugins/twilio-wp-plugin/`
|
||||||
- **Website URL**: `https://phone.cloud-hosting.io/`
|
- **Production URL**: `https://phone.cloud-hosting.io/`
|
||||||
- **Development Path**: `/home/jknapp/code/twilio-wp-plugin/`
|
- **Development Path**: `/home/jknapp/code/twilio-wp-plugin/`
|
||||||
- **Deployment Method**: Files synced via rsync from development to Docker container
|
- **Deployment Method**: Files synced via rsync from development to Docker container
|
||||||
|
|
||||||
**IMPORTANT**:
|
**IMPORTANT NOTES**:
|
||||||
- NEVER assume local testing - all tests must work on remote server
|
- NEVER assume local testing - all tests must work on remote server
|
||||||
- Direct PHP tests work (`php test-twilio-direct.php send`)
|
- Direct PHP tests work (`php test-twilio-direct.php send`)
|
||||||
- WordPress admin context has issues that need investigation
|
- WordPress admin context has issues that need investigation
|
||||||
|
- The plugin requires Twilio PHP SDK v8.7.0 to function
|
||||||
|
|
||||||
## 📞 Standardized Phone Number Variable Names
|
## 📞 Standardized Phone Number Variable Names
|
||||||
|
|
||||||
@@ -38,164 +39,318 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
- **Test Agent Number**: `+19095737372`
|
- **Test Agent Number**: `+19095737372`
|
||||||
- **Fake Test Number**: `+19512345678` (DO NOT SEND SMS TO THIS)
|
- **Fake Test Number**: `+19512345678` (DO NOT SEND SMS TO THIS)
|
||||||
|
|
||||||
### Webhook URLs:
|
### Legacy Webhook URLs:
|
||||||
- **SMS**: `https://www.streamers.channel/wp-json/twilio-webhook/v1/sms`
|
- **SMS**: `https://www.streamers.channel/wp-json/twilio-webhook/v1/sms`
|
||||||
- **Voice**: `https://www.streamers.channel/wp-json/twilio-webhook/v1/voice`
|
- **Voice**: `https://www.streamers.channel/wp-json/twilio-webhook/v1/voice`
|
||||||
|
|
||||||
## Project Overview
|
## 🏗️ Plugin Architecture Overview
|
||||||
|
|
||||||
This is a comprehensive WordPress plugin for Twilio voice and SMS integration, featuring:
|
### Core Plugin Structure
|
||||||
- **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
|
|
||||||
|
|
||||||
## Plugin Architecture
|
|
||||||
|
|
||||||
### Core Classes (`includes/` directory)
|
|
||||||
- **TWP_Core**: Main plugin initialization and hook registration
|
|
||||||
- **TWP_Activator**: Database table creation and plugin activation
|
|
||||||
- **TWP_Twilio_API**: Official Twilio PHP SDK wrapper (requires SDK v8.7.0)
|
|
||||||
- **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/
|
||||||
├── twilio-wp-plugin.php (main plugin file)
|
├── twilio-wp-plugin.php # Main plugin file (entry point)
|
||||||
├── includes/ (core functionality)
|
├── includes/ # Core functionality classes
|
||||||
├── admin/ (admin interface)
|
│ ├── class-twp-core.php # Main plugin initialization
|
||||||
├── assets/ (CSS/JS/images)
|
│ ├── class-twp-activator.php # Database setup
|
||||||
├── CLAUDE.md (this file)
|
│ ├── class-twp-twilio-api.php # Twilio SDK wrapper
|
||||||
|
│ ├── class-twp-webhooks.php # REST API endpoints
|
||||||
|
│ ├── class-twp-tts-helper.php # TTS with ElevenLabs/Twilio
|
||||||
|
│ └── ... # Other core classes
|
||||||
|
├── admin/ # Admin interface
|
||||||
|
│ └── class-twp-admin.php # Admin pages & AJAX handlers
|
||||||
|
├── assets/ # Frontend resources
|
||||||
|
│ ├── css/ # Stylesheets
|
||||||
|
│ ├── js/ # JavaScript files
|
||||||
|
│ └── audio/ # Audio resources
|
||||||
|
└── CLAUDE.md # This documentation file
|
||||||
```
|
```
|
||||||
|
|
||||||
### Class Naming
|
## 📦 Core Classes Documentation
|
||||||
- Prefix: `TWP_` for all classes
|
|
||||||
- Database tables: `twp_` prefix
|
|
||||||
- Options: `twp_` prefix
|
|
||||||
- AJAX actions: `twp_` prefix
|
|
||||||
|
|
||||||
### Database Operations
|
### Main Classes (`includes/` directory)
|
||||||
- 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
|
#### TWP_Core
|
||||||
All AJAX endpoints registered in `TWP_Core::define_admin_hooks()`:
|
- **Purpose**: Main plugin initialization and hook registration
|
||||||
```php
|
- **Key Methods**:
|
||||||
$this->loader->add_action('wp_ajax_twp_action_name', $plugin_admin, 'ajax_action_name');
|
- `define_admin_hooks()`: Registers all admin AJAX actions
|
||||||
```
|
- `define_public_hooks()`: Registers frontend hooks
|
||||||
|
- `define_webhook_hooks()`: Registers REST API endpoints
|
||||||
|
|
||||||
### User Profile Integration
|
#### TWP_Activator
|
||||||
Agent phone numbers stored as user meta:
|
- **Purpose**: Database table creation and plugin activation
|
||||||
- Meta key: `twp_phone_number`
|
- **Tables Created**: 15 database tables (see Database Schema section)
|
||||||
- Validation: `TWP_Agent_Manager::validate_phone_number()`
|
- **Key Methods**:
|
||||||
- Duplicate checking: `TWP_Agent_Manager::is_phone_number_duplicate()`
|
- `ensure_tables_exist()`: Checks and creates missing tables
|
||||||
|
- `migrate_tables()`: Handles schema migrations
|
||||||
|
- `add_missing_columns()`: Updates table structures
|
||||||
|
|
||||||
## Twilio Integration
|
#### TWP_Twilio_API
|
||||||
|
- **Purpose**: Wrapper for Twilio PHP SDK v8.7.0
|
||||||
|
- **Important**: Uses direct instantiation (`new TWP_Twilio_API()`)
|
||||||
|
- **Key Methods**:
|
||||||
|
- `make_call()`: Initiates outbound calls
|
||||||
|
- `send_sms()`: Sends SMS messages
|
||||||
|
- `update_call()`: Updates call state (hold/resume)
|
||||||
|
- `create_queue_twiml()`: Generates queue TwiML
|
||||||
|
|
||||||
### Current Implementation (SDK-Only)
|
#### TWP_Webhooks
|
||||||
- **Official Twilio SDK**: Uses `twilio/sdk` v8.7.0 for all operations
|
- **Purpose**: Handles all Twilio webhook endpoints
|
||||||
- **TwiML Generation**: Uses `\Twilio\TwiML\VoiceResponse` classes
|
- **Namespace**: `twilio-webhook/v1`
|
||||||
- **Response Handling**: Native Twilio SDK response objects
|
- **Total Endpoints**: 26 REST API routes
|
||||||
- **Error Handling**: Proper `\Twilio\Exceptions\TwilioException` handling
|
|
||||||
|
|
||||||
### Installation Requirements
|
#### TWP_TTS_Helper (NEW)
|
||||||
**IMPORTANT**: The Twilio PHP SDK v8.7.0 is **REQUIRED** for this plugin to function.
|
- **Purpose**: Text-to-Speech with ElevenLabs integration and caching
|
||||||
|
- **Features**:
|
||||||
|
- Automatic ElevenLabs detection
|
||||||
|
- 30-day cache for generated audio
|
||||||
|
- Fallback to Twilio voice
|
||||||
|
- **Key Methods**:
|
||||||
|
- `add_tts_to_twiml()`: Adds TTS to TwiML response
|
||||||
|
- `generate_tts_audio()`: Pre-generates cached audio
|
||||||
|
|
||||||
**Installation Methods**:
|
#### TWP_ElevenLabs_API
|
||||||
1. **Script Installation** (Recommended):
|
- **Purpose**: Integration with ElevenLabs TTS service
|
||||||
```bash
|
- **Configuration**: Uses `twp_elevenlabs_*` options
|
||||||
chmod +x install-twilio-sdk.sh
|
- **Features**: Voice selection, model configuration, audio generation
|
||||||
./install-twilio-sdk.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Composer Installation**:
|
#### TWP_Workflow
|
||||||
```bash
|
- **Purpose**: Call flow processing and TwiML generation
|
||||||
composer install
|
- **Features**: IVR menus, queue routing, schedule checking
|
||||||
```
|
|
||||||
|
|
||||||
**PHP Requirements**: PHP 8.0+ required for SDK compatibility
|
#### TWP_Call_Queue
|
||||||
|
- **Purpose**: Queue management and position tracking
|
||||||
|
- **Features**: User-specific queues, hold queues, priority handling
|
||||||
|
|
||||||
|
#### TWP_Agent_Manager
|
||||||
|
- **Purpose**: Agent status and call acceptance
|
||||||
|
- **Features**: Phone number validation, availability tracking
|
||||||
|
|
||||||
|
#### TWP_Callback_Manager
|
||||||
|
- **Purpose**: Callback request handling
|
||||||
|
- **Features**: SMS confirmations, automatic processing
|
||||||
|
|
||||||
|
#### TWP_Call_Logger
|
||||||
|
- **Purpose**: Call logging and statistics
|
||||||
|
- **Features**: Detailed call records, duration tracking
|
||||||
|
|
||||||
|
#### TWP_Shortcodes
|
||||||
|
- **Purpose**: WordPress shortcodes for frontend features
|
||||||
|
- **Shortcode**: `[twp_browser_phone]` - Browser phone interface
|
||||||
|
|
||||||
|
## 💾 Database Schema
|
||||||
|
|
||||||
|
### Complete Table List (15 tables)
|
||||||
|
1. `twp_phone_schedules` - Business hours definitions
|
||||||
|
2. `twp_call_queues` - Queue configurations (includes user-specific)
|
||||||
|
3. `twp_queued_calls` - Active calls in queues
|
||||||
|
4. `twp_workflows` - Call flow definitions
|
||||||
|
5. `twp_workflow_phones` - Phone-to-workflow mappings
|
||||||
|
6. `twp_call_log` - Complete call history
|
||||||
|
7. `twp_sms_log` - SMS message tracking
|
||||||
|
8. `twp_voicemails` - Voicemail recordings and transcriptions
|
||||||
|
9. `twp_agent_groups` - Agent group definitions
|
||||||
|
10. `twp_group_members` - User-to-group relationships
|
||||||
|
11. `twp_agent_status` - Real-time agent availability
|
||||||
|
12. `twp_callbacks` - Callback request queue
|
||||||
|
13. `twp_call_recordings` - Call recording metadata
|
||||||
|
14. `twp_user_extensions` - User extension numbers
|
||||||
|
15. `twp_queue_assignments` - User queue assignments
|
||||||
|
|
||||||
|
### Key Table Structures
|
||||||
|
|
||||||
|
#### twp_call_queues (Enhanced)
|
||||||
|
- Supports user-specific queues (`user_id` field)
|
||||||
|
- Queue types: `general`, `personal`, `hold`
|
||||||
|
- Extension support for direct dialing
|
||||||
|
- TTS message configuration
|
||||||
|
|
||||||
|
#### twp_queued_calls
|
||||||
|
- Uses `enqueued_at` timestamp (migrated from `joined_at`)
|
||||||
|
- No `customer_number` field (uses `from_number`/`to_number`)
|
||||||
|
- Tracks agent assignment and status
|
||||||
|
|
||||||
|
## 🔌 REST API Endpoints (Webhooks)
|
||||||
|
|
||||||
|
### Voice Endpoints
|
||||||
|
- `/voice` - Main incoming call handler
|
||||||
|
- `/browser-voice` - Browser phone calls
|
||||||
|
- `/smart-routing` - Intelligent call routing
|
||||||
|
- `/agent-screen` - Agent screening before connection
|
||||||
|
- `/agent-confirm` - Agent confirmation handler
|
||||||
|
- `/ring-group-result` - Handle ring group outcomes
|
||||||
|
- `/agent-connect` - Connect accepted agents
|
||||||
|
|
||||||
|
### SMS Endpoints
|
||||||
|
- `/sms` - Main SMS handler (includes "1" responses)
|
||||||
|
- `/status` - Call/SMS status updates
|
||||||
|
|
||||||
|
### Queue Management
|
||||||
|
- `/queue-wait` - Queue hold music and announcements
|
||||||
|
- `/queue-action` - Queue-specific actions
|
||||||
|
- `/ivr-response` - IVR menu selections
|
||||||
|
|
||||||
|
### Voicemail
|
||||||
|
- `/voicemail-callback` - Voicemail recording handler
|
||||||
|
- `/voicemail-complete` - Post-recording processing
|
||||||
|
- `/voicemail-audio/{id}` - Audio playback proxy
|
||||||
|
|
||||||
|
### Recording
|
||||||
|
- `/recording-status` - Recording status callbacks
|
||||||
|
- `/recording-audio/{id}` - Recording playback proxy
|
||||||
|
- `/transcription` - Transcription webhooks
|
||||||
|
|
||||||
|
### Callback System
|
||||||
|
- `/callback-choice` - Customer callback selection
|
||||||
|
- `/request-callback` - Callback request handler
|
||||||
|
- `/callback-agent` - Agent-side callback
|
||||||
|
- `/callback-customer` - Customer-side callback
|
||||||
|
|
||||||
|
### Outbound Calling
|
||||||
|
- `/outbound-agent` - Basic outbound calls
|
||||||
|
- `/outbound-agent-with-from` - Outbound with caller ID selection
|
||||||
|
|
||||||
|
### Utility
|
||||||
|
- `/resume-call` - Resume held calls
|
||||||
|
- `/smart-routing-fallback` - Routing error handler
|
||||||
|
- `/browser-fallback` - Browser phone fallback
|
||||||
|
|
||||||
|
## 🎛️ AJAX Endpoints (Admin & Frontend)
|
||||||
|
|
||||||
|
### Total: 68 AJAX Actions
|
||||||
|
|
||||||
|
### Categories:
|
||||||
|
1. **Schedule Management** (4 actions)
|
||||||
|
2. **Workflow Management** (5 actions)
|
||||||
|
3. **Phone Number Management** (5 actions)
|
||||||
|
4. **Queue Management** (6 actions)
|
||||||
|
5. **Agent Management** (11 actions)
|
||||||
|
6. **Call Control** (15 actions)
|
||||||
|
7. **Voicemail** (5 actions)
|
||||||
|
8. **Recording** (4 actions)
|
||||||
|
9. **SMS Management** (4 actions)
|
||||||
|
10. **ElevenLabs Integration** (3 actions)
|
||||||
|
11. **Browser Phone** (4 actions)
|
||||||
|
12. **Transfer & Hold** (8 actions)
|
||||||
|
|
||||||
|
### Key AJAX Actions:
|
||||||
|
- `twp_toggle_hold` - Put call on hold/resume (uses call leg detection)
|
||||||
|
- `twp_transfer_call` - Transfer to agent/queue (uses call leg detection)
|
||||||
|
- `twp_start_recording` - Start call recording
|
||||||
|
- `twp_stop_recording` - Stop call recording
|
||||||
|
- `twp_requeue_call` - Return call to queue (uses call leg detection)
|
||||||
|
- `twp_initiate_outbound_call_with_from` - Outbound with caller ID
|
||||||
|
|
||||||
|
### Call Control Architecture (CRITICAL):
|
||||||
|
All call control functions (`twp_toggle_hold`, `twp_transfer_call`, `twp_requeue_call`) now use intelligent call leg detection to ensure actions are applied to the customer call leg, not the agent leg. This prevents customer disconnections in complex call topologies.
|
||||||
|
|
||||||
|
## 🎨 Frontend Components
|
||||||
|
|
||||||
|
### JavaScript Files
|
||||||
|
1. **admin.js** (116KB) - Admin interface functionality
|
||||||
|
2. **browser-phone-frontend.js** (85KB) - Browser phone implementation
|
||||||
|
3. **twp-service-worker.js** (2.5KB) - Push notifications
|
||||||
|
|
||||||
|
### Browser Phone Features
|
||||||
|
- Twilio Device SDK integration
|
||||||
|
- Real-time call controls (hold, transfer, record)
|
||||||
|
- Queue monitoring dashboard
|
||||||
|
- Agent status management
|
||||||
|
- Call history display
|
||||||
|
- Visual call state indicators
|
||||||
|
|
||||||
|
### CSS Files
|
||||||
|
- **admin.css** - Admin interface styling
|
||||||
|
- **browser-phone-frontend.css** - Browser phone UI
|
||||||
|
|
||||||
|
## 🔧 Recent Fixes & Improvements
|
||||||
|
|
||||||
|
### CRITICAL: Outbound Call Issues RESOLVED (September 2025)
|
||||||
|
- **Issue**: Hold, transfer, and requeue functions were applying actions to wrong call leg (agent instead of customer), causing customer disconnections
|
||||||
|
- **Root Cause**: Complex call topologies in outbound calls create agent and customer call legs with different SIDs
|
||||||
|
- **Solution**: New intelligent call leg detection system
|
||||||
|
- **Functions Fixed**: `ajax_toggle_hold()`, `ajax_transfer_call()`, `ajax_requeue_call()`
|
||||||
|
- **Result**: All functions now work correctly for both inbound and outbound calls without disconnecting customers
|
||||||
|
|
||||||
|
### Customer Number Detection Issues RESOLVED (September 2025)
|
||||||
|
- **Issue**: Customer numbers showing as "client:agentname" instead of actual phone numbers in voicemail and call recording admin interfaces
|
||||||
|
- **Root Cause**: Browser phone calls create complex call topologies where customer information is stored in different call legs
|
||||||
|
- **Solution**: Enhanced customer number detection with fallback mechanisms
|
||||||
|
- **Areas Fixed**: Voicemail callback handling and call recording customer identification
|
||||||
|
- **Result**: Both inbound and outbound calls now properly identify real customer phone numbers
|
||||||
|
|
||||||
|
### Call Leg Detection System (NEW)
|
||||||
|
- **Function**: `find_customer_call_leg($call_sid, $api)` in `TWP_Admin` class
|
||||||
|
- **Purpose**: Identifies customer vs agent call legs in complex call topologies
|
||||||
|
- **Detection Logic**:
|
||||||
|
- Detects browser phone calls by checking for `client:` prefixes
|
||||||
|
- Uses parent call relationships to find customer leg
|
||||||
|
- Searches active calls for related customer connections
|
||||||
|
- Comprehensive fallback mechanisms
|
||||||
|
- **Logging**: Extensive debugging output for call relationship tracking
|
||||||
|
|
||||||
|
### Enhanced Customer Number Detection (NEW)
|
||||||
|
- **Voicemail Callback Enhancement** (`TWP_Webhooks::handle_voicemail_callback()`):
|
||||||
|
- Fallback logic retrieves customer numbers from call log when From parameter missing
|
||||||
|
- Browser phone detection identifies `client:` calls and finds real customer numbers
|
||||||
|
- Parent call analysis and related call search functionality
|
||||||
|
- Comprehensive logging for customer number detection process
|
||||||
|
- **Call Recording Enhancement** (`TWP_Admin::ajax_start_recording()`):
|
||||||
|
- Browser phone detection using `client:` prefix identification
|
||||||
|
- Integration with `find_customer_call_leg()` helper for proper customer identification
|
||||||
|
- Smart number extraction from appropriate call legs (from/to field analysis)
|
||||||
|
- Enhanced logging for recording customer number detection
|
||||||
|
|
||||||
|
### Browser Phone Call Support Enhanced
|
||||||
|
- **Client Transfer Support**: Fixed "Invalid phone number format" errors for `client:` transfers
|
||||||
|
- **Detection**: Automatically identifies `client:agentname` format calls
|
||||||
|
- **Transfer Methods**:
|
||||||
|
- Client transfers: `$dial->client($agent_name)`
|
||||||
|
- Phone transfers: `$twiml->dial($target)`
|
||||||
|
- Queue transfers: Uses customer leg for proper queue placement
|
||||||
|
- **Customer Number Resolution**: Properly identifies real phone numbers in complex call topologies
|
||||||
|
- **Admin Interface Fixes**: Voicemail and recording interfaces now show actual customer numbers instead of client identifiers
|
||||||
|
|
||||||
|
### Hold Functionality (Fixed)
|
||||||
|
- **Issue**: `TWP_Twilio_API::get_instance()` error
|
||||||
|
- **Fix**: Changed to direct instantiation + call leg detection
|
||||||
|
- **Enhancement**: Added ElevenLabs TTS with caching
|
||||||
|
- **Customer Impact**: Hold now affects customer (not agent) with proper music/messages
|
||||||
|
|
||||||
|
### Transfer System (Fixed)
|
||||||
|
- **Issue**: Transfers were disconnecting customers in outbound calls
|
||||||
|
- **Fix**: All transfer types now use correct customer call leg
|
||||||
|
- **Types Supported**: Queue, client (browser phone), phone number transfers
|
||||||
|
- **Enhancement**: Proper TwiML generation for each transfer type
|
||||||
|
|
||||||
|
### Requeue Functionality (Fixed)
|
||||||
|
- **Issue**: Requeue was disconnecting customers instead of placing them back in queue
|
||||||
|
- **Fix**: Uses customer call leg for queue placement
|
||||||
|
- **Enhancement**: Maintains proper call tracking with customer SID
|
||||||
|
- **Database**: Uses `enqueued_at` column when available, falls back to `joined_at`
|
||||||
|
|
||||||
|
### Recording Management (Fixed)
|
||||||
|
- **Issue**: "Unknown subresource update" error
|
||||||
|
- **Fix**: Proper SDK v8 syntax using call recordings subresource
|
||||||
|
- **Fallback**: Tries `Twilio.CURRENT` if specific SID fails
|
||||||
|
|
||||||
|
### Queue Management (Fixed)
|
||||||
|
- **Issue**: `Enqueue::waitUrl()` undefined method
|
||||||
|
- **Fix**: Pass `waitUrl` as option: `$response->enqueue($queue_name, ['waitUrl' => $url])`
|
||||||
|
|
||||||
|
### TTS Integration (New)
|
||||||
|
- **Feature**: ElevenLabs integration with intelligent caching
|
||||||
|
- **Cache Duration**: 30 days for identical text
|
||||||
|
- **Fallback**: Automatic fallback to Twilio voice
|
||||||
|
- **Performance**: Cached audio loads instantly
|
||||||
|
|
||||||
|
## 🚀 Twilio Integration Details
|
||||||
|
|
||||||
|
### SDK Version
|
||||||
|
- **Required**: Twilio PHP SDK v8.7.0
|
||||||
|
- **Installation**: Via Composer or install script
|
||||||
|
- **PHP Requirement**: PHP 8.0+
|
||||||
|
|
||||||
### API Response Structure
|
### API Response Structure
|
||||||
Current Twilio API responses follow this pattern:
|
|
||||||
```php
|
```php
|
||||||
// Success response
|
// Success response
|
||||||
[
|
[
|
||||||
@@ -209,79 +364,199 @@ Current Twilio API responses follow this pattern:
|
|||||||
'error' => 'Error message',
|
'error' => 'Error message',
|
||||||
'code' => 400
|
'code' => 400
|
||||||
]
|
]
|
||||||
|
|
||||||
// Call SID location
|
|
||||||
$call_sid = $response['data']['sid']; // NOT $response['call_sid']
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### TwiML Best Practices
|
### TwiML Generation Best Practices
|
||||||
Always include XML declaration:
|
- Always use SDK classes for TwiML generation
|
||||||
|
- Include proper XML headers when needed
|
||||||
|
- Use TTS helper for voice synthesis
|
||||||
|
- Implement proper error handling
|
||||||
|
|
||||||
|
### Recording Stop Methods (SDK v8)
|
||||||
```php
|
```php
|
||||||
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
|
// Method 1: Specific recording
|
||||||
$twiml .= '<Response>';
|
$client->calls($call_sid)
|
||||||
$twiml .= '<Say voice="alice">Message</Say>';
|
->recordings($recording_sid)
|
||||||
$twiml .= '</Response>';
|
->update(['status' => 'stopped']);
|
||||||
|
|
||||||
|
// Method 2: Single active recording
|
||||||
|
$client->calls($call_sid)
|
||||||
|
->recordings('Twilio.CURRENT')
|
||||||
|
->update(['status' => 'stopped']);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development Commands
|
## 🛠️ Development Guidelines
|
||||||
|
|
||||||
```bash
|
### Call Control Functions (CRITICAL)
|
||||||
# Install WordPress development dependencies
|
**ALWAYS use call leg detection for hold, transfer, and requeue operations:**
|
||||||
composer install
|
```php
|
||||||
|
// Correct pattern for call control functions
|
||||||
|
private function find_customer_call_leg($call_sid, $api) {
|
||||||
|
// Detects browser phone vs regular calls
|
||||||
|
// Uses parent call relationships
|
||||||
|
// Searches active calls for customer leg
|
||||||
|
// Returns correct SID for operations
|
||||||
|
}
|
||||||
|
|
||||||
# Install JavaScript dependencies
|
// Usage in AJAX handlers
|
||||||
npm install
|
$customer_call_sid = $this->find_customer_call_leg($call_sid, $twilio);
|
||||||
|
$result = $api->update_call($customer_call_sid, ['twiml' => $twiml_xml]);
|
||||||
# Run PHP CodeSniffer for WordPress coding standards
|
|
||||||
vendor/bin/phpcs
|
|
||||||
|
|
||||||
# Fix PHP coding standards automatically
|
|
||||||
vendor/bin/phpcbf
|
|
||||||
|
|
||||||
# Run PHPUnit tests
|
|
||||||
vendor/bin/phpunit
|
|
||||||
|
|
||||||
# Install Twilio SDK (recommended migration)
|
|
||||||
composer require twilio/sdk
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing Approach
|
**Why This Matters:**
|
||||||
|
- Outbound calls create separate agent and customer call legs
|
||||||
|
- Applying actions to agent leg disconnects customer
|
||||||
|
- Browser phone calls use `client:` identifiers
|
||||||
|
- Customer leg must be identified for proper call control
|
||||||
|
|
||||||
### Unit Testing
|
### Database Operations
|
||||||
- Use PHPUnit for PHP code testing
|
- Always use `$wpdb` global
|
||||||
- Mock WordPress functions using Brain Monkey or WP_Mock
|
- Sanitize with `sanitize_text_field()`, `intval()`
|
||||||
- Mock Twilio API responses for reliable testing
|
- Use prepared statements: `$wpdb->prepare()`
|
||||||
|
- Call `TWP_Activator::ensure_tables_exist()` before operations
|
||||||
|
|
||||||
### Integration Testing
|
### AJAX Handler Pattern
|
||||||
- Test webhook endpoints with Twilio webhook simulator
|
```php
|
||||||
- Test complete call flows end-to-end
|
public function ajax_handler_name() {
|
||||||
- Verify database operations and data integrity
|
if (!$this->verify_ajax_nonce()) {
|
||||||
|
wp_send_json_error('Invalid nonce');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
### Manual Testing
|
// Handler logic
|
||||||
- Use Twilio Console to monitor webhook calls
|
|
||||||
- Check WordPress error logs for debugging
|
|
||||||
- Test with real phone numbers for user experience
|
|
||||||
|
|
||||||
## Common Issues & Solutions
|
wp_send_json_success($data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Log errors with `error_log()`
|
||||||
|
- Return structured error responses
|
||||||
|
- Implement fallback mechanisms
|
||||||
|
- Handle Twilio exceptions properly
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- Classes: `TWP_Class_Name`
|
||||||
|
- Tables: `twp_table_name`
|
||||||
|
- Options: `twp_option_name`
|
||||||
|
- AJAX actions: `twp_action_name`
|
||||||
|
- Nonces: `twp_ajax_nonce` or `twp_frontend_nonce`
|
||||||
|
|
||||||
|
## 📋 Common Issues & Solutions
|
||||||
|
|
||||||
### Database Issues
|
### Database Issues
|
||||||
- **Missing Tables**: Call `TWP_Activator::ensure_tables_exist()`
|
- **Missing Tables**: Run `TWP_Activator::ensure_tables_exist()`
|
||||||
- **White Admin Modals**: Check for PHP errors in workflow loading
|
- **Schema Changes**: Check `add_missing_columns()` method
|
||||||
|
- **Migration Issues**: Review `migrate_tables()` implementation
|
||||||
|
|
||||||
### Webhook Issues
|
### Webhook Issues
|
||||||
- **500 Errors**: Check PHP error logs for fatal errors
|
- **500 Errors**: Check PHP error logs
|
||||||
- **Missing Parameters**: Parameters must be in webhook URL, not just POST data
|
- **TwiML Errors**: Verify XML structure
|
||||||
- **TwiML Parse Errors**: Always include XML declaration
|
- **Authentication**: Webhooks use `__return_true` permission
|
||||||
|
|
||||||
### API Issues
|
### API Issues
|
||||||
- **Call SID Access**: Use `$response['data']['sid']` not `$response['call_sid']`
|
- **Instantiation**: Use `new TWP_Twilio_API()` not singleton
|
||||||
- **Phone Number Format**: Store with + prefix (e.g., "+1234567890")
|
- **Phone Format**: Always use E.164 format (+1XXXXXXXXXX)
|
||||||
- **From Number**: Must be a verified Twilio number
|
- **Call SID**: Access via `$response['data']['sid']`
|
||||||
|
|
||||||
### Agent Management
|
### Call Control Issues (Outbound Calls)
|
||||||
- **SMS Responses**: Agents text "1" to receive calls
|
- **Customer Disconnections**: Use `find_customer_call_leg()` before hold/transfer/requeue
|
||||||
- **Phone Validation**: Auto-formats US numbers to +1 format
|
- **Wrong Call Leg**: Check error logs for "Call Leg Detection" messages
|
||||||
- **Duplicate Prevention**: Checks phone numbers across all users
|
- **Browser Phone Issues**: Look for `client:` prefix detection in logs
|
||||||
|
- **Transfer Failures**: Verify customer call leg is being used for TwiML updates
|
||||||
|
- **Customer Number Display**: Check for "TWP Voicemail Callback" or "TWP Recording" log entries for customer number detection
|
||||||
|
- **Client Identifier Issues**: Search logs for "client:" identifier handling and real customer number detection
|
||||||
|
|
||||||
This plugin provides a complete call center solution with professional-grade features suitable for business use.
|
### Hold/Transfer Issues
|
||||||
- our production url is https://phone.cloud-hosting.io
|
- **Hold Music**: Default URL provided, customizable via settings
|
||||||
|
- **TTS Caching**: 30-day cache, auto-cleanup available
|
||||||
|
- **Transfer**: Supports agent queues and phone numbers
|
||||||
|
- **Call Topology**: Complex outbound calls require call leg detection
|
||||||
|
|
||||||
|
## 🧪 Testing Approach
|
||||||
|
|
||||||
|
### Unit Testing
|
||||||
|
- PHPUnit for PHP code
|
||||||
|
- Mock WordPress functions
|
||||||
|
- Mock Twilio API responses
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
- Test webhook endpoints
|
||||||
|
- Verify database operations
|
||||||
|
- Test complete call flows
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
- Monitor Twilio Console
|
||||||
|
- Check WordPress debug logs
|
||||||
|
- Test with real phone numbers
|
||||||
|
|
||||||
|
## 📚 Key Features Implementation
|
||||||
|
|
||||||
|
### Agent System
|
||||||
|
- **SMS Accept**: Agents text "1" to accept calls
|
||||||
|
- **Real-time Status**: Available/busy/offline states
|
||||||
|
- **Group Management**: Priority-based call distribution
|
||||||
|
- **Personal Queues**: Agent-specific call queues
|
||||||
|
|
||||||
|
### Call Queue System
|
||||||
|
- **Position Tracking**: Real-time queue position
|
||||||
|
- **Timeout Handling**: Automatic callback offers
|
||||||
|
- **Hold Queues**: Temporary call parking
|
||||||
|
- **User Queues**: Personal agent queues
|
||||||
|
|
||||||
|
### Browser Phone
|
||||||
|
- **Twilio Device**: Full SDK integration
|
||||||
|
- **Call Controls**: Hold, transfer, record, mute
|
||||||
|
- **Visual Interface**: Real-time status updates
|
||||||
|
- **Queue Dashboard**: Monitor waiting calls
|
||||||
|
|
||||||
|
### Recording System
|
||||||
|
- **Start/Stop**: Dynamic recording control
|
||||||
|
- **Storage**: Database tracking with Twilio URLs
|
||||||
|
- **Playback**: Authenticated proxy endpoints
|
||||||
|
- **Transcription**: Automatic with callbacks
|
||||||
|
|
||||||
|
### ElevenLabs TTS
|
||||||
|
- **Auto-detection**: Uses ElevenLabs when configured
|
||||||
|
- **Caching**: 30-day cache for repeated phrases
|
||||||
|
- **Fallback**: Seamless Twilio voice fallback
|
||||||
|
- **Performance**: Instant cached audio delivery
|
||||||
|
|
||||||
|
## 📝 Configuration Requirements
|
||||||
|
|
||||||
|
### WordPress Settings
|
||||||
|
- **Twilio Credentials**: Account SID, Auth Token
|
||||||
|
- **TwiML App**: For browser phone functionality
|
||||||
|
- **Phone Numbers**: At least one Twilio number
|
||||||
|
- **Webhook URLs**: Configure in Twilio Console
|
||||||
|
|
||||||
|
### Optional Settings
|
||||||
|
- **ElevenLabs**: API key and voice selection
|
||||||
|
- **Hold Music**: Custom URL support
|
||||||
|
- **SMS Notifications**: Agent alert numbers
|
||||||
|
- **Business Hours**: Schedule configurations
|
||||||
|
|
||||||
|
## 🔍 Debugging Tips
|
||||||
|
|
||||||
|
1. **Enable WordPress Debug**: `WP_DEBUG = true`
|
||||||
|
2. **Check Error Logs**: `/wp-content/debug.log`
|
||||||
|
3. **Monitor Twilio Console**: Real-time webhook debugging
|
||||||
|
4. **Database Queries**: Use `$wpdb->last_error`
|
||||||
|
5. **Browser Console**: Check JavaScript errors
|
||||||
|
6. **Network Tab**: Monitor AJAX requests
|
||||||
|
7. **Call Leg Detection**: Look for "TWP Call Leg Detection" log entries
|
||||||
|
8. **Outbound Call Issues**: Check for agent vs customer call SID usage
|
||||||
|
9. **Browser Phone Debugging**: Search logs for "client:" identifier handling
|
||||||
|
|
||||||
|
## 📖 External Resources
|
||||||
|
|
||||||
|
- **Twilio PHP SDK**: https://www.twilio.com/docs/libraries/reference/twilio-php/
|
||||||
|
- **WordPress REST API**: https://developer.wordpress.org/rest-api/
|
||||||
|
- **ElevenLabs API**: https://api.elevenlabs.io/docs
|
||||||
|
- **Twilio TwiML**: https://www.twilio.com/docs/voice/twiml
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: September 2025*
|
||||||
|
*Plugin Version: Production Ready*
|
||||||
|
*Maintained for: phone.cloud-hosting.io*
|
40
README.md
40
README.md
@@ -109,6 +109,20 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will
|
|||||||
|
|
||||||
## Recent Updates
|
## Recent Updates
|
||||||
|
|
||||||
|
### CRITICAL: Outbound Call Issues Fixed (September 2025)
|
||||||
|
- **Customer Disconnection Issue Resolved**: Fixed major problem where hold, transfer, and requeue functions were disconnecting customers in outbound calls
|
||||||
|
- **Call Leg Detection System**: New intelligent system identifies customer vs agent call legs in complex call topologies
|
||||||
|
- **Browser Phone Transfers**: Fixed "Invalid phone number format" errors when transferring to browser phone agents
|
||||||
|
- **Outbound Call Stability**: All call control functions now work correctly for both inbound and outbound scenarios
|
||||||
|
- **Enhanced Logging**: Comprehensive debugging for call relationship tracking
|
||||||
|
|
||||||
|
### Customer Number Detection Improvements (September 2025)
|
||||||
|
- **Voicemail Interface Fix**: Customer numbers now display correctly instead of showing "client:agentname" for browser phone calls
|
||||||
|
- **Call Recording Interface Fix**: Recording admin interface now shows actual customer phone numbers for all call types
|
||||||
|
- **Smart Number Detection**: Enhanced fallback logic retrieves customer numbers from call logs and parent call analysis
|
||||||
|
- **Browser Phone Support**: Proper handling of complex call topologies created by browser phone calls
|
||||||
|
- **User Experience**: Admin interfaces now consistently display meaningful customer information
|
||||||
|
|
||||||
### Browser Phone Upgrade (v2.0)
|
### Browser Phone Upgrade (v2.0)
|
||||||
- **Migrated to Twilio Voice SDK v2**: Replaced deprecated Client SDK v1.14
|
- **Migrated to Twilio Voice SDK v2**: Replaced deprecated Client SDK v1.14
|
||||||
- **Improved Stability**: Better error handling and automatic recovery
|
- **Improved Stability**: Better error handling and automatic recovery
|
||||||
@@ -331,6 +345,19 @@ php test-sdk.php
|
|||||||
- Review workflow step configuration
|
- Review workflow step configuration
|
||||||
- Check notification_number field (not phone_number)
|
- Check notification_number field (not phone_number)
|
||||||
|
|
||||||
|
#### Outbound Call Control Issues
|
||||||
|
- **Customer Disconnections**: Fixed in latest version with call leg detection
|
||||||
|
- **Hold Not Working**: Ensure you have the latest version with `find_customer_call_leg()` function
|
||||||
|
- **Transfer Failures**: Check error logs for "Call Leg Detection" messages
|
||||||
|
- **Browser Phone Transfers**: Use latest version that supports `client:` identifier transfers
|
||||||
|
- **Requeue Problems**: Verify customer call leg is being used, not agent leg
|
||||||
|
|
||||||
|
#### Customer Number Display Issues
|
||||||
|
- **"client:agentname" in Voicemails**: Fixed in latest version with enhanced customer number detection
|
||||||
|
- **Wrong Numbers in Recording Interface**: Fixed with browser phone detection and call leg analysis
|
||||||
|
- **Missing Customer Info**: Check error logs for "TWP Voicemail Callback" or "TWP Recording" customer detection messages
|
||||||
|
- **Browser Phone Call Issues**: Enhanced detection now properly identifies real customer numbers in complex call topologies
|
||||||
|
|
||||||
#### SMS Not Sending from Admin
|
#### SMS Not Sending from Admin
|
||||||
- Test with direct PHP script: `php test-twilio-direct.php send`
|
- Test with direct PHP script: `php test-twilio-direct.php send`
|
||||||
- Verify Twilio credentials in settings
|
- Verify Twilio credentials in settings
|
||||||
@@ -413,7 +440,18 @@ All webhooks are REST API endpoints under `/wp-json/twilio-webhook/v1/`:
|
|||||||
|
|
||||||
## Version History
|
## Version History
|
||||||
|
|
||||||
### v2.1.0 (Current)
|
### v2.2.0 (Current - September 2025)
|
||||||
|
- **CRITICAL FIXES**: Resolved major outbound call issues
|
||||||
|
- **Call Leg Detection**: New intelligent system for complex call topologies
|
||||||
|
- **Customer Disconnection Fix**: Hold, transfer, and requeue now work correctly
|
||||||
|
- **Browser Phone Transfer Support**: Fixed `client:` identifier handling
|
||||||
|
- **Enhanced Debugging**: Comprehensive call relationship tracking
|
||||||
|
- **Outbound Call Stability**: All functions work for both inbound and outbound calls
|
||||||
|
- **Customer Number Detection**: Enhanced voicemail and recording interfaces to show real customer numbers
|
||||||
|
- **Browser Phone Interface Fixes**: Admin interfaces now properly identify customers in complex call scenarios
|
||||||
|
- **Fallback Logic**: Comprehensive customer number detection with call log and parent call analysis
|
||||||
|
|
||||||
|
### v2.1.0
|
||||||
- **Multiple Phone Numbers**: Workflows can now handle multiple phone numbers
|
- **Multiple Phone Numbers**: Workflows can now handle multiple phone numbers
|
||||||
- **Discord & Slack Integration**: Real-time notifications for call events
|
- **Discord & Slack Integration**: Real-time notifications for call events
|
||||||
- **Enhanced Monitoring**: Automated queue timeout tracking and alerts
|
- **Enhanced Monitoring**: Automated queue timeout tracking and alerts
|
||||||
|
File diff suppressed because it is too large
Load Diff
220
includes/class-twp-tts-helper.php
Normal file
220
includes/class-twp-tts-helper.php
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* TTS Helper class to handle Text-to-Speech with ElevenLabs or Twilio
|
||||||
|
*/
|
||||||
|
class TWP_TTS_Helper {
|
||||||
|
|
||||||
|
private static $instance = null;
|
||||||
|
private $elevenlabs_api = null;
|
||||||
|
private $use_elevenlabs = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance
|
||||||
|
*/
|
||||||
|
public static function get_instance() {
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
// Check if ElevenLabs is configured
|
||||||
|
$api_key = get_option('twp_elevenlabs_api_key');
|
||||||
|
$voice_id = get_option('twp_elevenlabs_voice_id');
|
||||||
|
|
||||||
|
if (!empty($api_key) && !empty($voice_id)) {
|
||||||
|
$this->use_elevenlabs = true;
|
||||||
|
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-elevenlabs-api.php';
|
||||||
|
$this->elevenlabs_api = new TWP_ElevenLabs_API();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for text
|
||||||
|
*/
|
||||||
|
private function get_cache_key($text) {
|
||||||
|
$voice_id = get_option('twp_elevenlabs_voice_id');
|
||||||
|
$model_id = get_option('twp_elevenlabs_model_id', 'eleven_multilingual_v2');
|
||||||
|
return 'twp_tts_' . md5($text . $voice_id . $model_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached audio URL if exists
|
||||||
|
*/
|
||||||
|
private function get_cached_audio($text) {
|
||||||
|
$cache_key = $this->get_cache_key($text);
|
||||||
|
$cached_data = get_transient($cache_key);
|
||||||
|
|
||||||
|
if ($cached_data !== false) {
|
||||||
|
// Verify the file still exists
|
||||||
|
if (file_exists($cached_data['file_path'])) {
|
||||||
|
error_log("TWP TTS: Using cached audio for text: " . substr($text, 0, 50) . "...");
|
||||||
|
return $cached_data;
|
||||||
|
} else {
|
||||||
|
// File was deleted, remove from cache
|
||||||
|
delete_transient($cache_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save audio to cache
|
||||||
|
*/
|
||||||
|
private function cache_audio($text, $audio_data) {
|
||||||
|
$cache_key = $this->get_cache_key($text);
|
||||||
|
// Cache for 30 days
|
||||||
|
set_transient($cache_key, $audio_data, 30 * DAY_IN_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add TTS to TwiML Response
|
||||||
|
*
|
||||||
|
* @param \Twilio\TwiML\VoiceResponse $twiml The TwiML response object
|
||||||
|
* @param string $text The text to speak
|
||||||
|
* @param array $options Options for voice settings (used for Twilio fallback)
|
||||||
|
* @return bool Success status
|
||||||
|
*/
|
||||||
|
public function add_tts_to_twiml($twiml, $text, $options = []) {
|
||||||
|
// Default Twilio voice options
|
||||||
|
$default_options = [
|
||||||
|
'voice' => 'alice',
|
||||||
|
'language' => 'en-US'
|
||||||
|
];
|
||||||
|
$options = array_merge($default_options, $options);
|
||||||
|
|
||||||
|
if ($this->use_elevenlabs) {
|
||||||
|
// First check cache
|
||||||
|
$cached_audio = $this->get_cached_audio($text);
|
||||||
|
|
||||||
|
if ($cached_audio !== false) {
|
||||||
|
$twiml->play($cached_audio['file_url']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in cache, generate new audio
|
||||||
|
$audio_result = $this->elevenlabs_api->text_to_speech($text);
|
||||||
|
|
||||||
|
if ($audio_result && isset($audio_result['success']) && $audio_result['success']) {
|
||||||
|
// Cache the result
|
||||||
|
$this->cache_audio($text, [
|
||||||
|
'file_url' => $audio_result['file_url'],
|
||||||
|
'file_path' => $audio_result['file_path']
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Use the generated audio file
|
||||||
|
$twiml->play($audio_result['file_url']);
|
||||||
|
error_log("TWP TTS: Generated new ElevenLabs audio for text: " . substr($text, 0, 50) . "...");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Log the failure and fall back to Twilio
|
||||||
|
error_log("TWP TTS: ElevenLabs failed, falling back to Twilio. Error: " .
|
||||||
|
(isset($audio_result['error']) ? $audio_result['error'] : 'Unknown error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to Twilio's built-in TTS
|
||||||
|
$twiml->say($text, $options);
|
||||||
|
error_log("TWP TTS: Using Twilio voice for text: " . substr($text, 0, 50) . "...");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate TTS audio file (for pre-generation)
|
||||||
|
*
|
||||||
|
* @param string $text The text to convert
|
||||||
|
* @return array|false Array with file_url on success, false on failure
|
||||||
|
*/
|
||||||
|
public function generate_tts_audio($text) {
|
||||||
|
if (!$this->use_elevenlabs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check cache
|
||||||
|
$cached_audio = $this->get_cached_audio($text);
|
||||||
|
if ($cached_audio !== false) {
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'file_url' => $cached_audio['file_url'],
|
||||||
|
'file_path' => $cached_audio['file_path'],
|
||||||
|
'cached' => true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in cache, generate new audio
|
||||||
|
$result = $this->elevenlabs_api->text_to_speech($text);
|
||||||
|
|
||||||
|
if ($result && isset($result['success']) && $result['success']) {
|
||||||
|
// Cache the result
|
||||||
|
$this->cache_audio($text, [
|
||||||
|
'file_url' => $result['file_url'],
|
||||||
|
'file_path' => $result['file_path']
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'file_url' => $result['file_url'],
|
||||||
|
'file_path' => $result['file_path'],
|
||||||
|
'cached' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if ElevenLabs is configured and available
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_elevenlabs_available() {
|
||||||
|
return $this->use_elevenlabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configured voice info
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_voice_info() {
|
||||||
|
if ($this->use_elevenlabs) {
|
||||||
|
return [
|
||||||
|
'provider' => 'elevenlabs',
|
||||||
|
'voice_id' => get_option('twp_elevenlabs_voice_id'),
|
||||||
|
'model_id' => get_option('twp_elevenlabs_model_id', 'eleven_multilingual_v2')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'provider' => 'twilio',
|
||||||
|
'voice' => 'alice',
|
||||||
|
'language' => 'en-US'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up old TTS files (maintenance)
|
||||||
|
*/
|
||||||
|
public function cleanup_old_files($hours = 24) {
|
||||||
|
$upload_dir = wp_upload_dir();
|
||||||
|
$path = $upload_dir['path'];
|
||||||
|
|
||||||
|
// Only clean up TTS files older than specified hours
|
||||||
|
$expire_time = time() - ($hours * 3600);
|
||||||
|
|
||||||
|
$files = glob($path . '/tts_*.mp3');
|
||||||
|
if ($files) {
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (filemtime($file) < $expire_time) {
|
||||||
|
unlink($file);
|
||||||
|
error_log("TWP TTS: Cleaned up old file: " . basename($file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1297,8 +1297,11 @@ class TWP_Webhooks {
|
|||||||
$from = isset($params['From']) ? $params['From'] : '';
|
$from = isset($params['From']) ? $params['From'] : '';
|
||||||
$workflow_id = isset($params['workflow_id']) ? intval($params['workflow_id']) : 0;
|
$workflow_id = isset($params['workflow_id']) ? intval($params['workflow_id']) : 0;
|
||||||
|
|
||||||
|
// Enhanced customer number detection for voicemails
|
||||||
|
$customer_number = $from;
|
||||||
|
|
||||||
// If From is not provided in the callback, try to get it from the call log
|
// If From is not provided in the callback, try to get it from the call log
|
||||||
if (empty($from) && !empty($call_sid)) {
|
if (empty($customer_number) && !empty($call_sid)) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$call_log_table = $wpdb->prefix . 'twp_call_log';
|
$call_log_table = $wpdb->prefix . 'twp_call_log';
|
||||||
$call_record = $wpdb->get_row($wpdb->prepare(
|
$call_record = $wpdb->get_row($wpdb->prepare(
|
||||||
@@ -1306,11 +1309,81 @@ class TWP_Webhooks {
|
|||||||
$call_sid
|
$call_sid
|
||||||
));
|
));
|
||||||
if ($call_record && $call_record->from_number) {
|
if ($call_record && $call_record->from_number) {
|
||||||
$from = $call_record->from_number;
|
$customer_number = $call_record->from_number;
|
||||||
error_log('TWP Voicemail Callback: Retrieved from_number from call log: ' . $from);
|
error_log('TWP Voicemail Callback: Retrieved from_number from call log: ' . $customer_number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we got a client identifier (browser phone), try to find the real customer number
|
||||||
|
if (!empty($customer_number) && strpos($customer_number, 'client:') === 0) {
|
||||||
|
error_log('TWP Voicemail Callback: Detected client identifier, looking for customer number');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize Twilio API to find the customer call leg
|
||||||
|
$twilio_api = new TWP_Twilio_API();
|
||||||
|
$client = $twilio_api->get_client();
|
||||||
|
$call = $client->calls($call_sid)->fetch();
|
||||||
|
|
||||||
|
// Use similar logic to find_customer_call_leg but adapted for voicemail
|
||||||
|
$real_customer_number = null;
|
||||||
|
|
||||||
|
// Check if this call has a parent that contains a real phone number
|
||||||
|
if ($call->parentCallSid) {
|
||||||
|
try {
|
||||||
|
$parent_call = $client->calls($call->parentCallSid)->fetch();
|
||||||
|
if (strpos($parent_call->from, 'client:') === false && strpos($parent_call->from, '+') === 0) {
|
||||||
|
$real_customer_number = $parent_call->from;
|
||||||
|
} elseif (strpos($parent_call->to, 'client:') === false && strpos($parent_call->to, '+') === 0) {
|
||||||
|
$real_customer_number = $parent_call->to;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("TWP Voicemail Callback: Could not fetch parent call: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no parent success, search related calls
|
||||||
|
if (!$real_customer_number) {
|
||||||
|
$related_calls = $client->calls->read(['status' => 'completed'], 20);
|
||||||
|
foreach ($related_calls as $related_call) {
|
||||||
|
if ($related_call->sid === $call_sid) continue;
|
||||||
|
|
||||||
|
// Check if calls are related
|
||||||
|
$is_related = false;
|
||||||
|
if ($call->parentCallSid && $related_call->parentCallSid === $call->parentCallSid) {
|
||||||
|
$is_related = true;
|
||||||
|
} elseif ($related_call->parentCallSid === $call_sid) {
|
||||||
|
$is_related = true;
|
||||||
|
} elseif ($related_call->sid === $call->parentCallSid) {
|
||||||
|
$is_related = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_related) {
|
||||||
|
if (strpos($related_call->from, 'client:') === false && strpos($related_call->from, '+') === 0) {
|
||||||
|
$real_customer_number = $related_call->from;
|
||||||
|
break;
|
||||||
|
} elseif (strpos($related_call->to, 'client:') === false && strpos($related_call->to, '+') === 0) {
|
||||||
|
$real_customer_number = $related_call->to;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($real_customer_number) {
|
||||||
|
$customer_number = $real_customer_number;
|
||||||
|
error_log("TWP Voicemail Callback: Found real customer number: {$customer_number}");
|
||||||
|
} else {
|
||||||
|
error_log("TWP Voicemail Callback: WARNING - Could not find real customer number, keeping client identifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("TWP Voicemail Callback: Error finding customer number: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update $from to use the detected customer number
|
||||||
|
$from = $customer_number;
|
||||||
|
|
||||||
// Debug what we extracted
|
// Debug what we extracted
|
||||||
error_log('TWP Voicemail Callback: recording_url=' . $recording_url . ', from=' . $from . ', workflow_id=' . $workflow_id . ', call_sid=' . $call_sid);
|
error_log('TWP Voicemail Callback: recording_url=' . $recording_url . ', from=' . $from . ', workflow_id=' . $workflow_id . ', call_sid=' . $call_sid);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user