code push
This commit is contained in:
		
							
								
								
									
										37
									
								
								CLAUDE.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								CLAUDE.md
									
									
									
									
									
								
							@@ -18,7 +18,7 @@ This is a comprehensive WordPress plugin for Twilio voice and SMS integration, f
 | 
			
		||||
### 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_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
 | 
			
		||||
@@ -131,25 +131,28 @@ Agent phone numbers stored as user meta:
 | 
			
		||||
 | 
			
		||||
## 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
 | 
			
		||||
### Current Implementation (SDK-Only)
 | 
			
		||||
- **Official Twilio SDK**: Uses `twilio/sdk` v8.7.0 for all operations
 | 
			
		||||
- **TwiML Generation**: Uses `\Twilio\TwiML\VoiceResponse` classes
 | 
			
		||||
- **Response Handling**: Native Twilio SDK response objects
 | 
			
		||||
- **Error Handling**: Proper `\Twilio\Exceptions\TwilioException` handling
 | 
			
		||||
 | 
			
		||||
### Recommended Migration to Twilio PHP SDK
 | 
			
		||||
**URL**: https://www.twilio.com/docs/libraries/reference/twilio-php/
 | 
			
		||||
### Installation Requirements
 | 
			
		||||
**IMPORTANT**: The Twilio PHP SDK v8.7.0 is **REQUIRED** for this plugin to function.
 | 
			
		||||
 | 
			
		||||
**Benefits**:
 | 
			
		||||
- Official SDK with better error handling
 | 
			
		||||
- Built-in TwiML generation classes
 | 
			
		||||
- Automatic retries and rate limiting
 | 
			
		||||
- Type safety and IDE support
 | 
			
		||||
**Installation Methods**:
 | 
			
		||||
1. **Script Installation** (Recommended):
 | 
			
		||||
   ```bash
 | 
			
		||||
   chmod +x install-twilio-sdk.sh
 | 
			
		||||
   ./install-twilio-sdk.sh
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
**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
 | 
			
		||||
2. **Composer Installation**:
 | 
			
		||||
   ```bash
 | 
			
		||||
   composer install
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
**PHP Requirements**: PHP 8.0+ required for SDK compatibility
 | 
			
		||||
 | 
			
		||||
### API Response Structure
 | 
			
		||||
Current Twilio API responses follow this pattern:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										134
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								README.md
									
									
									
									
									
								
							@@ -1,2 +1,134 @@
 | 
			
		||||
# twilio-wp-plugin
 | 
			
		||||
# Twilio WordPress Plugin
 | 
			
		||||
 | 
			
		||||
A comprehensive WordPress plugin for Twilio voice and SMS integration with advanced call center functionality.
 | 
			
		||||
 | 
			
		||||
## ⚠️ IMPORTANT: SDK Required
 | 
			
		||||
 | 
			
		||||
This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will not work without it.
 | 
			
		||||
 | 
			
		||||
## Quick Installation
 | 
			
		||||
 | 
			
		||||
1. **Install the Twilio SDK** (Required):
 | 
			
		||||
   ```bash
 | 
			
		||||
   chmod +x install-twilio-sdk.sh
 | 
			
		||||
   ./install-twilio-sdk.sh
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
2. **Test the SDK installation**:
 | 
			
		||||
   ```bash
 | 
			
		||||
   php test-sdk.php
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
3. **Configure Twilio Credentials** in WordPress admin:
 | 
			
		||||
   - Account SID
 | 
			
		||||
   - Auth Token  
 | 
			
		||||
   - Phone Number
 | 
			
		||||
 | 
			
		||||
4. **Test the installation** with a sample call.
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
- **PHP 8.0+** (required for Twilio SDK v8.7.0)
 | 
			
		||||
- **WordPress 5.0+**
 | 
			
		||||
- **Twilio Account** with active phone number
 | 
			
		||||
- **curl** and **tar** (for SDK installation)
 | 
			
		||||
 | 
			
		||||
## Key Features
 | 
			
		||||
 | 
			
		||||
- 📞 **Call Center Operations**: Agent groups, queues, call distribution
 | 
			
		||||
- 🕒 **Business Hours Management**: Automated routing based on schedules  
 | 
			
		||||
- 📱 **Outbound Calling**: Click-to-call with proper caller ID
 | 
			
		||||
- 💬 **SMS Integration**: Agent notifications and command system
 | 
			
		||||
- 🎛️ **Workflow Builder**: Visual call flow creation
 | 
			
		||||
- 🎤 **Voicemail System**: Recording, transcription, and notifications
 | 
			
		||||
- 📊 **Real-time Dashboard**: Queue management and statistics
 | 
			
		||||
 | 
			
		||||
## Installation Methods
 | 
			
		||||
 | 
			
		||||
### Option 1: Installation Script (Recommended)
 | 
			
		||||
```bash
 | 
			
		||||
# Run in plugin directory
 | 
			
		||||
./install-twilio-sdk.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Option 2: Composer (For Development)
 | 
			
		||||
```bash
 | 
			
		||||
composer install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Option 3: Manual Installation
 | 
			
		||||
1. Download Twilio SDK v8.7.0 from GitHub
 | 
			
		||||
2. Extract to `vendor/twilio/sdk/`  
 | 
			
		||||
3. Create autoloader (see install script for reference)
 | 
			
		||||
 | 
			
		||||
## Architecture
 | 
			
		||||
 | 
			
		||||
The plugin uses:
 | 
			
		||||
- **Official Twilio PHP SDK v8.7.0** for all API operations
 | 
			
		||||
- **Native TwiML classes** for response generation
 | 
			
		||||
- **WordPress hooks and filters** for integration
 | 
			
		||||
- **Custom database tables** for call management
 | 
			
		||||
- **REST API endpoints** for webhooks
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
 | 
			
		||||
1. Install the SDK using the provided script
 | 
			
		||||
2. Configure Twilio credentials in WordPress admin
 | 
			
		||||
3. Set up phone numbers and webhook URLs
 | 
			
		||||
4. Create agent groups and workflows
 | 
			
		||||
5. Test with sample calls
 | 
			
		||||
 | 
			
		||||
## Troubleshooting
 | 
			
		||||
 | 
			
		||||
### "Twilio SDK classes not available" Error
 | 
			
		||||
 | 
			
		||||
1. **Run the installation script**:
 | 
			
		||||
   ```bash
 | 
			
		||||
   chmod +x install-twilio-sdk.sh
 | 
			
		||||
   ./install-twilio-sdk.sh
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
2. **Test SDK installation**:
 | 
			
		||||
   ```bash
 | 
			
		||||
   php test-sdk.php
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
3. **Check file permissions**:
 | 
			
		||||
   ```bash
 | 
			
		||||
   ls -la vendor/
 | 
			
		||||
   ls -la vendor/twilio/sdk/
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
4. **Verify directory structure**:
 | 
			
		||||
   ```
 | 
			
		||||
   vendor/
 | 
			
		||||
   ├── autoload.php
 | 
			
		||||
   └── twilio/
 | 
			
		||||
       └── sdk/
 | 
			
		||||
           ├── Rest/Client.php
 | 
			
		||||
           ├── TwiML/VoiceResponse.php
 | 
			
		||||
           └── ... (other SDK files)
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
### Plugin Shows 500 Error
 | 
			
		||||
 | 
			
		||||
- Check WordPress error logs
 | 
			
		||||
- Enable WP_DEBUG in wp-config.php
 | 
			
		||||
- Look for TWP Plugin error messages in logs
 | 
			
		||||
 | 
			
		||||
### SDK Installation Fails
 | 
			
		||||
 | 
			
		||||
- Ensure `curl` and `tar` are installed
 | 
			
		||||
- Check internet connection
 | 
			
		||||
- Try manual installation (see Installation Methods)
 | 
			
		||||
 | 
			
		||||
## Support
 | 
			
		||||
 | 
			
		||||
- Check `CLAUDE.md` for detailed technical documentation
 | 
			
		||||
- Review `TWILIO_SDK_MIGRATION.md` for migration details  
 | 
			
		||||
- Enable WordPress debug logging for troubleshooting
 | 
			
		||||
- Use Twilio Console debugger for webhook testing
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
This plugin integrates with Twilio services and requires a Twilio account.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										240
									
								
								TWILIO_SDK_MIGRATION.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								TWILIO_SDK_MIGRATION.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
# Twilio PHP SDK Migration Guide
 | 
			
		||||
 | 
			
		||||
This document outlines the migration of the Twilio WordPress Plugin from a custom API wrapper to the official Twilio PHP SDK v8.7.0.
 | 
			
		||||
 | 
			
		||||
## Overview
 | 
			
		||||
 | 
			
		||||
The plugin now supports both the official Twilio PHP SDK and falls back to a custom implementation when the SDK is not available. This provides better error handling, type safety, and access to the latest Twilio features while maintaining backward compatibility.
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
### Option 1: Using the Installation Script (Recommended)
 | 
			
		||||
 | 
			
		||||
1. Navigate to your plugin directory
 | 
			
		||||
2. Run the installation script:
 | 
			
		||||
   ```bash
 | 
			
		||||
   chmod +x install-twilio-sdk.sh
 | 
			
		||||
   ./install-twilio-sdk.sh
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
### Option 2: Manual Installation with Composer
 | 
			
		||||
 | 
			
		||||
1. Ensure Composer is installed on your system
 | 
			
		||||
2. Run in the plugin directory:
 | 
			
		||||
   ```bash
 | 
			
		||||
   composer install
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
### Option 3: Manual Download
 | 
			
		||||
 | 
			
		||||
1. Download Twilio SDK v8.7.0 from: https://github.com/twilio/twilio-php/releases
 | 
			
		||||
2. Extract to `vendor/twilio/sdk/`
 | 
			
		||||
3. Create autoloader file (see installation script for reference)
 | 
			
		||||
 | 
			
		||||
## How It Works
 | 
			
		||||
 | 
			
		||||
### Auto-Detection
 | 
			
		||||
 | 
			
		||||
The plugin automatically detects if the Twilio SDK is available by:
 | 
			
		||||
1. Checking for `vendor/autoload.php`
 | 
			
		||||
2. Verifying the `Twilio\Rest\Client` class exists
 | 
			
		||||
3. Attempting to initialize the client
 | 
			
		||||
 | 
			
		||||
### Dual-Mode Operation
 | 
			
		||||
 | 
			
		||||
**SDK Mode (When Available):**
 | 
			
		||||
- Uses official Twilio PHP SDK classes
 | 
			
		||||
- Better error handling with specific Twilio exceptions
 | 
			
		||||
- Type safety and IDE support
 | 
			
		||||
- Automatic retries and rate limiting
 | 
			
		||||
- Official TwiML generation classes
 | 
			
		||||
 | 
			
		||||
**Fallback Mode (Default):**
 | 
			
		||||
- Uses custom WordPress HTTP API calls
 | 
			
		||||
- String-based TwiML generation
 | 
			
		||||
- Maintains all existing functionality
 | 
			
		||||
- No external dependencies required
 | 
			
		||||
 | 
			
		||||
## Code Changes
 | 
			
		||||
 | 
			
		||||
### API Class Updates
 | 
			
		||||
 | 
			
		||||
The `TWP_Twilio_API` class now:
 | 
			
		||||
- Detects SDK availability in the constructor
 | 
			
		||||
- Routes all methods through SDK when available
 | 
			
		||||
- Falls back to custom implementation seamlessly
 | 
			
		||||
- Maintains identical method signatures for compatibility
 | 
			
		||||
 | 
			
		||||
### TwiML Generation
 | 
			
		||||
 | 
			
		||||
**New Unified Approach:**
 | 
			
		||||
```php
 | 
			
		||||
// Works with both SDK and fallback
 | 
			
		||||
$api = new TWP_Twilio_API();
 | 
			
		||||
$response = $api->create_twiml();
 | 
			
		||||
$response->say('Hello world', ['voice' => 'alice']);
 | 
			
		||||
$response->dial('+1234567890', ['timeout' => 30]);
 | 
			
		||||
$twiml = $response->asXML();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Old Approach (Now Deprecated):**
 | 
			
		||||
```php
 | 
			
		||||
// String concatenation - now handled automatically
 | 
			
		||||
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
 | 
			
		||||
$twiml .= '<Response>';
 | 
			
		||||
$twiml .= '<Say voice="alice">Hello world</Say>';
 | 
			
		||||
$twiml .= '</Response>';
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Updated Classes
 | 
			
		||||
 | 
			
		||||
1. **TWP_Twilio_API**: Core API wrapper with SDK integration
 | 
			
		||||
2. **TWP_Webhooks**: All webhook handlers updated to use new TwiML builder
 | 
			
		||||
3. **TWP_Callback_Manager**: Outbound calling and callback management
 | 
			
		||||
4. **TWP_Workflow**: Call flow TwiML generation
 | 
			
		||||
5. **TWP_Agent_Manager**: Agent call routing
 | 
			
		||||
 | 
			
		||||
## Benefits
 | 
			
		||||
 | 
			
		||||
### With SDK Available
 | 
			
		||||
- **Better Error Handling**: Specific exception types for different error scenarios
 | 
			
		||||
- **Type Safety**: IDE support and parameter validation
 | 
			
		||||
- **Official Support**: Direct access to latest Twilio features
 | 
			
		||||
- **Reliability**: Built-in retry logic and rate limiting
 | 
			
		||||
- **Performance**: Optimized for high-volume operations
 | 
			
		||||
 | 
			
		||||
### Without SDK (Fallback)
 | 
			
		||||
- **No Dependencies**: Works on any PHP installation
 | 
			
		||||
- **Lightweight**: Minimal memory footprint
 | 
			
		||||
- **Compatibility**: Works with older PHP versions
 | 
			
		||||
- **Reliability**: Battle-tested custom implementation
 | 
			
		||||
 | 
			
		||||
## Supported PHP Versions
 | 
			
		||||
 | 
			
		||||
- **With SDK**: PHP 8.0+ (SDK v8.7.0 supports PHP 8.4)
 | 
			
		||||
- **Without SDK**: PHP 7.2+ (fallback implementation)
 | 
			
		||||
 | 
			
		||||
## Feature Support
 | 
			
		||||
 | 
			
		||||
### API Operations
 | 
			
		||||
- ✅ Make calls
 | 
			
		||||
- ✅ Send SMS
 | 
			
		||||
- ✅ Get call details
 | 
			
		||||
- ✅ Update active calls
 | 
			
		||||
- ✅ Manage phone numbers
 | 
			
		||||
- ✅ Search available numbers
 | 
			
		||||
- ✅ Purchase/release numbers
 | 
			
		||||
- ✅ Configure webhooks
 | 
			
		||||
 | 
			
		||||
### TwiML Generation
 | 
			
		||||
- ✅ Say (text-to-speech)
 | 
			
		||||
- ✅ Dial (with caller ID, timeout)
 | 
			
		||||
- ✅ Gather (digit collection with prompts)
 | 
			
		||||
- ✅ Enqueue (queue management)
 | 
			
		||||
- ✅ Record (voicemail recording)
 | 
			
		||||
- ✅ Redirect (call flow control)
 | 
			
		||||
- ✅ Hangup
 | 
			
		||||
 | 
			
		||||
### Advanced Features
 | 
			
		||||
- ✅ Webhook signature validation
 | 
			
		||||
- ✅ Conference calling
 | 
			
		||||
- ✅ Queue management
 | 
			
		||||
- ✅ Agent group routing
 | 
			
		||||
- ✅ Callback requests
 | 
			
		||||
- ✅ Voicemail transcription
 | 
			
		||||
 | 
			
		||||
## Testing
 | 
			
		||||
 | 
			
		||||
### Verifying SDK Installation
 | 
			
		||||
 | 
			
		||||
Check the admin dashboard or logs for:
 | 
			
		||||
```
 | 
			
		||||
TWP Plugin: Twilio SDK v8.7.0 loaded successfully
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### API Testing
 | 
			
		||||
 | 
			
		||||
Use the plugin's test call feature to verify:
 | 
			
		||||
1. Outbound calling works
 | 
			
		||||
2. TwiML generation is valid
 | 
			
		||||
3. Webhooks receive proper responses
 | 
			
		||||
4. Error handling functions correctly
 | 
			
		||||
 | 
			
		||||
### Manual Testing
 | 
			
		||||
 | 
			
		||||
1. **Test Call Flow**: Make a test call to verify TwiML generation
 | 
			
		||||
2. **Webhook Testing**: Use Twilio's webhook debugger
 | 
			
		||||
3. **Error Scenarios**: Test invalid numbers, network failures
 | 
			
		||||
4. **Queue Operations**: Test agent groups and callbacks
 | 
			
		||||
 | 
			
		||||
## Troubleshooting
 | 
			
		||||
 | 
			
		||||
### SDK Not Loading
 | 
			
		||||
 | 
			
		||||
**Check autoloader exists:**
 | 
			
		||||
```bash
 | 
			
		||||
ls -la vendor/autoload.php
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Check SDK files:**
 | 
			
		||||
```bash
 | 
			
		||||
ls -la vendor/twilio/sdk/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**PHP Version:**
 | 
			
		||||
```bash
 | 
			
		||||
php -v  # Should be 8.0+ for SDK support
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Common Issues
 | 
			
		||||
 | 
			
		||||
1. **"Class 'Twilio\Rest\Client' not found"**
 | 
			
		||||
   - SDK not properly installed
 | 
			
		||||
   - Run installation script again
 | 
			
		||||
 | 
			
		||||
2. **"Call to undefined method"**
 | 
			
		||||
   - Fallback mode active
 | 
			
		||||
   - Check SDK installation
 | 
			
		||||
   - Verify PHP version compatibility
 | 
			
		||||
 | 
			
		||||
3. **"Permission denied" on installation script**
 | 
			
		||||
   - Run: `chmod +x install-twilio-sdk.sh`
 | 
			
		||||
 | 
			
		||||
### Fallback Mode Indicators
 | 
			
		||||
 | 
			
		||||
The plugin uses fallback mode when:
 | 
			
		||||
- No `vendor/autoload.php` file
 | 
			
		||||
- Twilio classes not available
 | 
			
		||||
- SDK initialization fails
 | 
			
		||||
- PHP version incompatibility
 | 
			
		||||
 | 
			
		||||
## Migration Timeline
 | 
			
		||||
 | 
			
		||||
- ✅ **Phase 1**: Core API wrapper updated
 | 
			
		||||
- ✅ **Phase 2**: Webhook classes migrated
 | 
			
		||||
- ✅ **Phase 3**: TwiML generation unified
 | 
			
		||||
- ✅ **Phase 4**: Error handling improved
 | 
			
		||||
- ⏳ **Phase 5**: Testing and validation
 | 
			
		||||
- 📋 **Phase 6**: Documentation complete
 | 
			
		||||
 | 
			
		||||
## Rollback Plan
 | 
			
		||||
 | 
			
		||||
If issues arise, the plugin automatically falls back to the custom implementation. No manual intervention required.
 | 
			
		||||
 | 
			
		||||
To completely disable SDK usage:
 | 
			
		||||
1. Remove or rename `vendor/` directory
 | 
			
		||||
2. Plugin will automatically use fallback mode
 | 
			
		||||
 | 
			
		||||
## Support
 | 
			
		||||
 | 
			
		||||
For issues related to:
 | 
			
		||||
- **Plugin functionality**: Check WordPress error logs
 | 
			
		||||
- **Twilio API errors**: Check Twilio Console debugger
 | 
			
		||||
- **SDK-specific issues**: Refer to [Twilio PHP SDK documentation](https://www.twilio.com/docs/libraries/reference/twilio-php/)
 | 
			
		||||
 | 
			
		||||
## Resources
 | 
			
		||||
 | 
			
		||||
- [Twilio PHP SDK GitHub](https://github.com/twilio/twilio-php)
 | 
			
		||||
- [Twilio API Documentation](https://www.twilio.com/docs/usage/api)
 | 
			
		||||
- [TwiML Reference](https://www.twilio.com/docs/voice/twiml)
 | 
			
		||||
- [WordPress HTTP API](https://developer.wordpress.org/reference/classes/wp_http/)
 | 
			
		||||
							
								
								
									
										29
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "twilio-wp/twilio-wp-plugin",
 | 
			
		||||
    "description": "WordPress plugin for Twilio voice and SMS integration - REQUIRES Twilio SDK",
 | 
			
		||||
    "type": "wordpress-plugin",
 | 
			
		||||
    "require": {
 | 
			
		||||
        "php": ">=8.0",
 | 
			
		||||
        "twilio/sdk": "^8.7"
 | 
			
		||||
    },
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "classmap": [
 | 
			
		||||
            "includes/",
 | 
			
		||||
            "admin/"
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "optimize-autoloader": true,
 | 
			
		||||
        "platform": {
 | 
			
		||||
            "php": "8.0"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "post-install-cmd": [
 | 
			
		||||
            "echo 'Twilio SDK installed successfully!'"
 | 
			
		||||
        ],
 | 
			
		||||
        "install-sdk": [
 | 
			
		||||
            "./install-twilio-sdk.sh"
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								debug-phone-numbers.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								debug-phone-numbers.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Debug script to see what Twilio SDK actually returns
 | 
			
		||||
 * Run this to debug phone number issues
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Load WordPress (adjust path as needed)
 | 
			
		||||
$wp_load_path = dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-load.php';
 | 
			
		||||
if (!file_exists($wp_load_path)) {
 | 
			
		||||
    echo "WordPress not found. Please adjust the path in this script.\n";
 | 
			
		||||
    echo "Looking for: $wp_load_path\n";
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
require_once $wp_load_path;
 | 
			
		||||
 | 
			
		||||
echo "Debug: Twilio Phone Numbers\n";
 | 
			
		||||
echo "===========================\n\n";
 | 
			
		||||
 | 
			
		||||
// Load Twilio SDK
 | 
			
		||||
$autoloader = __DIR__ . '/vendor/autoload.php';
 | 
			
		||||
if (!file_exists($autoloader)) {
 | 
			
		||||
    echo "ERROR: SDK not found. Run ./install-twilio-sdk.sh first.\n";
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
require_once $autoloader;
 | 
			
		||||
 | 
			
		||||
// Get Twilio credentials from WordPress
 | 
			
		||||
$account_sid = get_option('twp_twilio_account_sid');
 | 
			
		||||
$auth_token = get_option('twp_twilio_auth_token');
 | 
			
		||||
 | 
			
		||||
if (empty($account_sid) || empty($auth_token)) {
 | 
			
		||||
    echo "ERROR: Twilio credentials not configured in WordPress.\n";
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo "Account SID: " . substr($account_sid, 0, 10) . "...\n";
 | 
			
		||||
echo "Auth Token: " . substr($auth_token, 0, 10) . "...\n\n";
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    // Create Twilio client
 | 
			
		||||
    $client = new \Twilio\Rest\Client($account_sid, $auth_token);
 | 
			
		||||
    echo "✅ Twilio client created successfully\n\n";
 | 
			
		||||
    
 | 
			
		||||
    // Get phone numbers
 | 
			
		||||
    echo "Fetching phone numbers...\n";
 | 
			
		||||
    $numbers = $client->incomingPhoneNumbers->read([], 10);
 | 
			
		||||
    
 | 
			
		||||
    if (empty($numbers)) {
 | 
			
		||||
        echo "No phone numbers found in your Twilio account.\n";
 | 
			
		||||
        exit(0);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    echo "Found " . count($numbers) . " phone number(s):\n\n";
 | 
			
		||||
    
 | 
			
		||||
    foreach ($numbers as $i => $number) {
 | 
			
		||||
        echo "=== Phone Number " . ($i + 1) . " ===\n";
 | 
			
		||||
        echo "SID: " . $number->sid . "\n";
 | 
			
		||||
        echo "Phone Number: " . $number->phoneNumber . "\n";
 | 
			
		||||
        echo "Friendly Name: " . ($number->friendlyName ?: '[Not set]') . "\n";
 | 
			
		||||
        echo "Voice URL: " . ($number->voiceUrl ?: '[Not set]') . "\n";
 | 
			
		||||
        echo "SMS URL: " . ($number->smsUrl ?: '[Not set]') . "\n";
 | 
			
		||||
        echo "Account SID: " . $number->accountSid . "\n";
 | 
			
		||||
        
 | 
			
		||||
        // Debug capabilities object
 | 
			
		||||
        echo "\nCapabilities (raw object):\n";
 | 
			
		||||
        var_dump($number->capabilities);
 | 
			
		||||
        
 | 
			
		||||
        echo "\nCapabilities (properties):\n";
 | 
			
		||||
        echo "- Voice: " . ($number->capabilities->voice ? 'YES' : 'NO') . "\n";
 | 
			
		||||
        echo "- SMS: " . ($number->capabilities->sms ? 'YES' : 'NO') . "\n";  
 | 
			
		||||
        echo "- MMS: " . ($number->capabilities->mms ? 'YES' : 'NO') . "\n";
 | 
			
		||||
        echo "\n" . str_repeat('-', 40) . "\n\n";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
    echo "ERROR: " . $e->getMessage() . "\n";
 | 
			
		||||
    echo "Class: " . get_class($e) . "\n";
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo "Debug complete!\n";
 | 
			
		||||
@@ -239,8 +239,7 @@ class TWP_Agent_Manager {
 | 
			
		||||
        
 | 
			
		||||
        // Create TwiML to redirect the call
 | 
			
		||||
        $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
        $dial = $twiml->dial();
 | 
			
		||||
        $dial->number($phone_number, [
 | 
			
		||||
        $twiml->dial($phone_number, [
 | 
			
		||||
            'statusCallback' => home_url('/wp-json/twilio-webhook/v1/call-status'),
 | 
			
		||||
            'statusCallbackEvent' => array('completed')
 | 
			
		||||
        ]);
 | 
			
		||||
@@ -307,20 +306,17 @@ class TWP_Agent_Manager {
 | 
			
		||||
        // Play a message while dialing
 | 
			
		||||
        $twiml->say('Please wait while we connect your call...', ['voice' => 'alice']);
 | 
			
		||||
        
 | 
			
		||||
        // Create a dial with simultaneous ring
 | 
			
		||||
        // Create a dial with simultaneous ring to all group members
 | 
			
		||||
        $dial = $twiml->dial([
 | 
			
		||||
            'timeout' => 30,
 | 
			
		||||
            'action' => home_url('/wp-json/twilio-webhook/v1/dial-status'),
 | 
			
		||||
            'method' => 'POST'
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        // Add each member's number to the dial
 | 
			
		||||
        // Add each member's number to the dial for simultaneous ring
 | 
			
		||||
        foreach ($members as $member) {
 | 
			
		||||
            if ($member['phone_number']) {
 | 
			
		||||
                $dial->number($member['phone_number'], [
 | 
			
		||||
                    'statusCallback' => home_url('/wp-json/twilio-webhook/v1/member-status'),
 | 
			
		||||
                    'statusCallbackEvent' => array('answered', 'completed')
 | 
			
		||||
                ]);
 | 
			
		||||
                $dial->number($member['phone_number']);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
@@ -232,17 +232,13 @@ class TWP_Callback_Manager {
 | 
			
		||||
     * Handle outbound agent answered
 | 
			
		||||
     */
 | 
			
		||||
    public static function handle_outbound_agent_answered($target_number, $agent_call_sid) {
 | 
			
		||||
        $twilio = new TWP_Twilio_API();
 | 
			
		||||
        
 | 
			
		||||
        // Create TwiML to call the target number
 | 
			
		||||
        $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
        $twiml->say('Connecting your call...', ['voice' => 'alice']);
 | 
			
		||||
        
 | 
			
		||||
        $dial = $twiml->dial([
 | 
			
		||||
            'callerId' => get_option('twp_caller_id_number', ''), // Use configured caller ID
 | 
			
		||||
        $twiml->dial($target_number, [
 | 
			
		||||
            'callerId' => get_option('twp_caller_id_number', ''),
 | 
			
		||||
            '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']);
 | 
			
		||||
@@ -259,7 +255,10 @@ class TWP_Callback_Manager {
 | 
			
		||||
        $gather = $twiml->gather([
 | 
			
		||||
            'numDigits' => 1,
 | 
			
		||||
            'timeout' => 10,
 | 
			
		||||
            'action' => home_url('/wp-json/twilio-webhook/v1/callback-choice'),
 | 
			
		||||
            'action' => home_url('/wp-json/twilio-webhook/v1/callback-choice?' . http_build_query([
 | 
			
		||||
                'queue_id' => $queue_id,
 | 
			
		||||
                'phone_number' => $caller_number
 | 
			
		||||
            ])),
 | 
			
		||||
            'method' => 'POST'
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
@@ -1,276 +1,474 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Twilio API integration class
 | 
			
		||||
 * Twilio API integration class using official Twilio PHP SDK
 | 
			
		||||
 */
 | 
			
		||||
class TWP_Twilio_API {
 | 
			
		||||
    
 | 
			
		||||
    private $account_sid;
 | 
			
		||||
    private $auth_token;
 | 
			
		||||
    private $client;
 | 
			
		||||
    private $phone_number;
 | 
			
		||||
    private $api_base = 'https://api.twilio.com/2010-04-01';
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct() {
 | 
			
		||||
        $this->account_sid = get_option('twp_twilio_account_sid');
 | 
			
		||||
        $this->auth_token = get_option('twp_twilio_auth_token');
 | 
			
		||||
        $this->init_sdk_client();
 | 
			
		||||
        $this->phone_number = get_option('twp_twilio_phone_number');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize Twilio SDK client
 | 
			
		||||
     */
 | 
			
		||||
    private function init_sdk_client() {
 | 
			
		||||
        // Check if autoloader exists
 | 
			
		||||
        $autoloader_path = TWP_PLUGIN_DIR . 'vendor/autoload.php';
 | 
			
		||||
        if (!file_exists($autoloader_path)) {
 | 
			
		||||
            error_log('TWP Plugin: Autoloader not found at: ' . $autoloader_path);
 | 
			
		||||
            throw new Exception('Twilio SDK not found. Please run: ./install-twilio-sdk.sh');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Load the autoloader
 | 
			
		||||
        require_once $autoloader_path;
 | 
			
		||||
        
 | 
			
		||||
        // Give more detailed error information
 | 
			
		||||
        if (!class_exists('Twilio\Rest\Client')) {
 | 
			
		||||
            $sdk_path = TWP_PLUGIN_DIR . 'vendor/twilio/sdk';
 | 
			
		||||
            $client_file = $sdk_path . '/Rest/Client.php';
 | 
			
		||||
            
 | 
			
		||||
            error_log('TWP Plugin: Twilio SDK classes not found.');
 | 
			
		||||
            error_log('TWP Plugin: Looking for SDK at: ' . $sdk_path);
 | 
			
		||||
            error_log('TWP Plugin: Client.php exists: ' . (file_exists($client_file) ? 'YES' : 'NO'));
 | 
			
		||||
            error_log('TWP Plugin: SDK directory contents: ' . print_r(scandir($sdk_path), true));
 | 
			
		||||
            
 | 
			
		||||
            throw new Exception('Twilio SDK classes not available. Please reinstall with: ./install-twilio-sdk.sh');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $account_sid = get_option('twp_twilio_account_sid');
 | 
			
		||||
        $auth_token = get_option('twp_twilio_auth_token');
 | 
			
		||||
        
 | 
			
		||||
        if (empty($account_sid) || empty($auth_token)) {
 | 
			
		||||
            throw new Exception('Twilio credentials not configured. Please check your WordPress admin settings.');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            $this->client = new \Twilio\Rest\Client($account_sid, $auth_token);
 | 
			
		||||
            error_log('TWP Plugin: Twilio SDK initialized successfully');
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            error_log('TWP Plugin: Failed to initialize Twilio client: ' . $e->getMessage());
 | 
			
		||||
            throw new Exception('Failed to initialize Twilio SDK: ' . $e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Make a phone call
 | 
			
		||||
     */
 | 
			
		||||
    public function make_call($to_number, $twiml_url, $status_callback = null, $from_number = null) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/Calls.json';
 | 
			
		||||
        
 | 
			
		||||
        $data = array(
 | 
			
		||||
            'To' => $to_number,
 | 
			
		||||
            'From' => $from_number ?: $this->phone_number,
 | 
			
		||||
            'Url' => $twiml_url
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($status_callback) {
 | 
			
		||||
            $data['StatusCallback'] = $status_callback;
 | 
			
		||||
            $data['StatusCallbackEvent'] = array('initiated', 'ringing', 'answered', 'completed');
 | 
			
		||||
        try {
 | 
			
		||||
            $params = [
 | 
			
		||||
                'url' => $twiml_url,
 | 
			
		||||
                'from' => $from_number ?: $this->phone_number,
 | 
			
		||||
                'to' => $to_number
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            if ($status_callback) {
 | 
			
		||||
                $params['statusCallback'] = $status_callback;
 | 
			
		||||
                $params['statusCallbackEvent'] = ['initiated', 'ringing', 'answered', 'completed'];
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $call = $this->client->calls->create(
 | 
			
		||||
                $to_number,
 | 
			
		||||
                $from_number ?: $this->phone_number,
 | 
			
		||||
                $params
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $call->sid,
 | 
			
		||||
                    'status' => $call->status,
 | 
			
		||||
                    'from' => $call->from,
 | 
			
		||||
                    'to' => $call->to,
 | 
			
		||||
                    'direction' => $call->direction,
 | 
			
		||||
                    'price' => $call->price,
 | 
			
		||||
                    'priceUnit' => $call->priceUnit
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->make_request('POST', $url, $data);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Forward a call
 | 
			
		||||
     */
 | 
			
		||||
    public function forward_call($call_sid, $to_number) {
 | 
			
		||||
        $twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
 | 
			
		||||
        $dial = $twiml->addChild('Dial');
 | 
			
		||||
        $dial->addChild('Number', $to_number);
 | 
			
		||||
        
 | 
			
		||||
        return $this->update_call($call_sid, array(
 | 
			
		||||
            'Twiml' => $twiml->asXML()
 | 
			
		||||
        ));
 | 
			
		||||
        try {
 | 
			
		||||
            $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
            $twiml->dial($to_number);
 | 
			
		||||
            
 | 
			
		||||
            $call = $this->client->calls($call_sid)->update([
 | 
			
		||||
                'twiml' => $twiml->asXML()
 | 
			
		||||
            ]);
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $call->sid,
 | 
			
		||||
                    'status' => $call->status
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Update an active call
 | 
			
		||||
     */
 | 
			
		||||
    public function update_call($call_sid, $params) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/Calls/' . $call_sid . '.json';
 | 
			
		||||
        return $this->make_request('POST', $url, $params);
 | 
			
		||||
        try {
 | 
			
		||||
            $call = $this->client->calls($call_sid)->update($params);
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $call->sid,
 | 
			
		||||
                    'status' => $call->status,
 | 
			
		||||
                    'from' => $call->from,
 | 
			
		||||
                    'to' => $call->to
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get call details
 | 
			
		||||
     */
 | 
			
		||||
    public function get_call($call_sid) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/Calls/' . $call_sid . '.json';
 | 
			
		||||
        return $this->make_request('GET', $url);
 | 
			
		||||
        try {
 | 
			
		||||
            $call = $this->client->calls($call_sid)->fetch();
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $call->sid,
 | 
			
		||||
                    'status' => $call->status,
 | 
			
		||||
                    'from' => $call->from,
 | 
			
		||||
                    'to' => $call->to,
 | 
			
		||||
                    'direction' => $call->direction,
 | 
			
		||||
                    'duration' => $call->duration,
 | 
			
		||||
                    'price' => $call->price,
 | 
			
		||||
                    'priceUnit' => $call->priceUnit
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Create TwiML for queue
 | 
			
		||||
     */
 | 
			
		||||
    public function create_queue_twiml($queue_name, $wait_url = null, $wait_message = null) {
 | 
			
		||||
        $twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
 | 
			
		||||
        
 | 
			
		||||
        if ($wait_message) {
 | 
			
		||||
            $say = $twiml->addChild('Say', $wait_message);
 | 
			
		||||
            $say->addAttribute('voice', 'alice');
 | 
			
		||||
        try {
 | 
			
		||||
            $response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
            
 | 
			
		||||
            if ($wait_message) {
 | 
			
		||||
                $response->say($wait_message, ['voice' => 'alice']);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $enqueue = $response->enqueue($queue_name);
 | 
			
		||||
            
 | 
			
		||||
            if ($wait_url) {
 | 
			
		||||
                $enqueue->waitUrl($wait_url);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return $response->asXML();
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            error_log('TWP Plugin: Failed to create queue TwiML: ' . $e->getMessage());
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $enqueue = $twiml->addChild('Enqueue', $queue_name);
 | 
			
		||||
        
 | 
			
		||||
        if ($wait_url) {
 | 
			
		||||
            $enqueue->addAttribute('waitUrl', $wait_url);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $twiml->asXML();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Create TwiML for IVR menu
 | 
			
		||||
     */
 | 
			
		||||
    public function create_ivr_twiml($message, $options = array()) {
 | 
			
		||||
        $twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
 | 
			
		||||
        
 | 
			
		||||
        $gather = $twiml->addChild('Gather');
 | 
			
		||||
        $gather->addAttribute('numDigits', '1');
 | 
			
		||||
        $gather->addAttribute('timeout', '10');
 | 
			
		||||
        
 | 
			
		||||
        if (!empty($options['action_url'])) {
 | 
			
		||||
            $gather->addAttribute('action', $options['action_url']);
 | 
			
		||||
        try {
 | 
			
		||||
            $response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
            
 | 
			
		||||
            $gather = $response->gather([
 | 
			
		||||
                'numDigits' => 1,
 | 
			
		||||
                'timeout' => 10,
 | 
			
		||||
                'action' => isset($options['action_url']) ? $options['action_url'] : null
 | 
			
		||||
            ]);
 | 
			
		||||
            
 | 
			
		||||
            $gather->say($message, ['voice' => 'alice']);
 | 
			
		||||
            
 | 
			
		||||
            if (!empty($options['no_input_message'])) {
 | 
			
		||||
                $response->say($options['no_input_message'], ['voice' => 'alice']);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return $response->asXML();
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            error_log('TWP Plugin: Failed to create IVR TwiML: ' . $e->getMessage());
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $say = $gather->addChild('Say', $message);
 | 
			
		||||
        $say->addAttribute('voice', 'alice');
 | 
			
		||||
        
 | 
			
		||||
        // Fallback if no input
 | 
			
		||||
        if (!empty($options['no_input_message'])) {
 | 
			
		||||
            $say_fallback = $twiml->addChild('Say', $options['no_input_message']);
 | 
			
		||||
            $say_fallback->addAttribute('voice', 'alice');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $twiml->asXML();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Send SMS
 | 
			
		||||
     */
 | 
			
		||||
    public function send_sms($to_number, $message) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/Messages.json';
 | 
			
		||||
        
 | 
			
		||||
        $data = array(
 | 
			
		||||
            'To' => $to_number,
 | 
			
		||||
            'From' => $this->phone_number,
 | 
			
		||||
            'Body' => $message
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $this->make_request('POST', $url, $data);
 | 
			
		||||
    public function send_sms($to_number, $message, $from_number = null) {
 | 
			
		||||
        try {
 | 
			
		||||
            $sms = $this->client->messages->create(
 | 
			
		||||
                $to_number,
 | 
			
		||||
                [
 | 
			
		||||
                    'from' => $from_number ?: $this->phone_number,
 | 
			
		||||
                    'body' => $message
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $sms->sid,
 | 
			
		||||
                    'status' => $sms->status,
 | 
			
		||||
                    'from' => $sms->from,
 | 
			
		||||
                    'to' => $sms->to,
 | 
			
		||||
                    'body' => $sms->body,
 | 
			
		||||
                    'price' => $sms->price,
 | 
			
		||||
                    'priceUnit' => $sms->priceUnit
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get available phone numbers
 | 
			
		||||
     */
 | 
			
		||||
    public function get_phone_numbers() {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers.json';
 | 
			
		||||
        return $this->make_request('GET', $url);
 | 
			
		||||
        try {
 | 
			
		||||
            $numbers = $this->client->incomingPhoneNumbers->read([], 50);
 | 
			
		||||
            
 | 
			
		||||
            $numbers_data = [];
 | 
			
		||||
            foreach ($numbers as $number) {
 | 
			
		||||
                $numbers_data[] = [
 | 
			
		||||
                    'sid' => $number->sid ?: '',
 | 
			
		||||
                    'phoneNumber' => $number->phoneNumber ?: '',
 | 
			
		||||
                    'friendlyName' => $number->friendlyName ?: $number->phoneNumber ?: 'Unknown',
 | 
			
		||||
                    'voiceUrl' => $number->voiceUrl ?: '',
 | 
			
		||||
                    'smsUrl' => $number->smsUrl ?: '',
 | 
			
		||||
                    'capabilities' => [
 | 
			
		||||
                        'voice' => $number->capabilities ? (bool)$number->capabilities->getVoice() : false,
 | 
			
		||||
                        'sms' => $number->capabilities ? (bool)$number->capabilities->getSms() : false,
 | 
			
		||||
                        'mms' => $number->capabilities ? (bool)$number->capabilities->getMms() : false
 | 
			
		||||
                    ]
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'incoming_phone_numbers' => $numbers_data
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Search for available phone numbers
 | 
			
		||||
     */
 | 
			
		||||
    public function search_available_numbers($country_code = 'US', $area_code = null, $contains = null, $limit = 20) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/AvailablePhoneNumbers/' . $country_code . '/Local.json';
 | 
			
		||||
        
 | 
			
		||||
        $params = array('Limit' => $limit);
 | 
			
		||||
        
 | 
			
		||||
        if ($area_code) {
 | 
			
		||||
            $params['AreaCode'] = $area_code;
 | 
			
		||||
        try {
 | 
			
		||||
            $params = ['limit' => $limit];
 | 
			
		||||
            
 | 
			
		||||
            if ($area_code) {
 | 
			
		||||
                $params['areaCode'] = $area_code;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if ($contains) {
 | 
			
		||||
                $params['contains'] = $contains;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $numbers = $this->client->availablePhoneNumbers($country_code)
 | 
			
		||||
                ->local
 | 
			
		||||
                ->read($params, $limit);
 | 
			
		||||
            
 | 
			
		||||
            $numbers_data = [];
 | 
			
		||||
            foreach ($numbers as $number) {
 | 
			
		||||
                $numbers_data[] = [
 | 
			
		||||
                    'phoneNumber' => $number->phoneNumber ?: '',
 | 
			
		||||
                    'friendlyName' => $number->friendlyName ?: $number->phoneNumber ?: 'Available Number',
 | 
			
		||||
                    'locality' => $number->locality ?: '',
 | 
			
		||||
                    'region' => $number->region ?: '',
 | 
			
		||||
                    'postalCode' => $number->postalCode ?: '',
 | 
			
		||||
                    'capabilities' => [
 | 
			
		||||
                        'voice' => $number->capabilities ? (bool)$number->capabilities->getVoice() : false,
 | 
			
		||||
                        'sms' => $number->capabilities ? (bool)$number->capabilities->getSms() : false,
 | 
			
		||||
                        'mms' => $number->capabilities ? (bool)$number->capabilities->getMms() : false
 | 
			
		||||
                    ]
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'available_phone_numbers' => $numbers_data
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($contains) {
 | 
			
		||||
            $params['Contains'] = $contains;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $url .= '?' . http_build_query($params);
 | 
			
		||||
        
 | 
			
		||||
        return $this->make_request('GET', $url);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Purchase a phone number
 | 
			
		||||
     */
 | 
			
		||||
    public function purchase_phone_number($phone_number, $voice_url = null, $sms_url = null) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers.json';
 | 
			
		||||
        
 | 
			
		||||
        $data = array(
 | 
			
		||||
            'PhoneNumber' => $phone_number
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($voice_url) {
 | 
			
		||||
            $data['VoiceUrl'] = $voice_url;
 | 
			
		||||
            $data['VoiceMethod'] = 'POST';
 | 
			
		||||
        try {
 | 
			
		||||
            $params = ['phoneNumber' => $phone_number];
 | 
			
		||||
            
 | 
			
		||||
            if ($voice_url) {
 | 
			
		||||
                $params['voiceUrl'] = $voice_url;
 | 
			
		||||
                $params['voiceMethod'] = 'POST';
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if ($sms_url) {
 | 
			
		||||
                $params['smsUrl'] = $sms_url;
 | 
			
		||||
                $params['smsMethod'] = 'POST';
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $number = $this->client->incomingPhoneNumbers->create($params);
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $number->sid,
 | 
			
		||||
                    'phoneNumber' => $number->phoneNumber,
 | 
			
		||||
                    'friendlyName' => $number->friendlyName,
 | 
			
		||||
                    'voiceUrl' => $number->voiceUrl,
 | 
			
		||||
                    'smsUrl' => $number->smsUrl
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($sms_url) {
 | 
			
		||||
            $data['SmsUrl'] = $sms_url;
 | 
			
		||||
            $data['SmsMethod'] = 'POST';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->make_request('POST', $url, $data);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Release a phone number
 | 
			
		||||
     */
 | 
			
		||||
    public function release_phone_number($phone_number_sid) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers/' . $phone_number_sid . '.json';
 | 
			
		||||
        return $this->make_request('DELETE', $url);
 | 
			
		||||
        try {
 | 
			
		||||
            $this->client->incomingPhoneNumbers($phone_number_sid)->delete();
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'message' => 'Phone number released successfully'
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure phone number webhook
 | 
			
		||||
     */
 | 
			
		||||
    public function configure_phone_number($phone_sid, $voice_url, $sms_url = null) {
 | 
			
		||||
        $url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers/' . $phone_sid . '.json';
 | 
			
		||||
        
 | 
			
		||||
        $data = array(
 | 
			
		||||
            'VoiceUrl' => $voice_url,
 | 
			
		||||
            'VoiceMethod' => 'POST'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($sms_url) {
 | 
			
		||||
            $data['SmsUrl'] = $sms_url;
 | 
			
		||||
            $data['SmsMethod'] = 'POST';
 | 
			
		||||
        try {
 | 
			
		||||
            $params = [
 | 
			
		||||
                'voiceUrl' => $voice_url,
 | 
			
		||||
                'voiceMethod' => 'POST'
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            if ($sms_url) {
 | 
			
		||||
                $params['smsUrl'] = $sms_url;
 | 
			
		||||
                $params['smsMethod'] = 'POST';
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $number = $this->client->incomingPhoneNumbers($phone_sid)->update($params);
 | 
			
		||||
            
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => [
 | 
			
		||||
                    'sid' => $number->sid,
 | 
			
		||||
                    'phoneNumber' => $number->phoneNumber,
 | 
			
		||||
                    'voiceUrl' => $number->voiceUrl,
 | 
			
		||||
                    'smsUrl' => $number->smsUrl
 | 
			
		||||
                ]
 | 
			
		||||
            ];
 | 
			
		||||
        } catch (\Twilio\Exceptions\TwilioException $e) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $e->getMessage(),
 | 
			
		||||
                'code' => $e->getCode()
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->make_request('POST', $url, $data);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Make API request
 | 
			
		||||
     * Create TwiML helper - returns SDK VoiceResponse
 | 
			
		||||
     */
 | 
			
		||||
    private function make_request($method, $url, $data = array()) {
 | 
			
		||||
        $args = array(
 | 
			
		||||
            'method' => $method,
 | 
			
		||||
            'headers' => array(
 | 
			
		||||
                'Authorization' => 'Basic ' . base64_encode($this->account_sid . ':' . $this->auth_token),
 | 
			
		||||
                'Content-Type' => 'application/x-www-form-urlencoded'
 | 
			
		||||
            ),
 | 
			
		||||
            'timeout' => 30
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($method === 'POST' && !empty($data)) {
 | 
			
		||||
            $args['body'] = $data;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($method === 'GET') {
 | 
			
		||||
            $response = wp_remote_get($url, $args);
 | 
			
		||||
        } else {
 | 
			
		||||
            $response = wp_remote_post($url, $args);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (is_wp_error($response)) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $response->get_error_message()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $body = wp_remote_retrieve_body($response);
 | 
			
		||||
        $decoded = json_decode($body, true);
 | 
			
		||||
        
 | 
			
		||||
        $status_code = wp_remote_retrieve_response_code($response);
 | 
			
		||||
        
 | 
			
		||||
        if ($status_code >= 200 && $status_code < 300) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'data' => $decoded
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => isset($decoded['message']) ? $decoded['message'] : 'API request failed',
 | 
			
		||||
                'code' => $status_code
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    public function create_twiml() {
 | 
			
		||||
        return new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the Twilio client instance
 | 
			
		||||
     */
 | 
			
		||||
    public function get_client() {
 | 
			
		||||
        return $this->client;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate webhook signature
 | 
			
		||||
     */
 | 
			
		||||
    public function validate_webhook_signature($url, $params, $signature) {
 | 
			
		||||
        $data = $url;
 | 
			
		||||
        
 | 
			
		||||
        if (is_array($params) && !empty($params)) {
 | 
			
		||||
            ksort($params);
 | 
			
		||||
            foreach ($params as $key => $value) {
 | 
			
		||||
                $data .= $key . $value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $computed_signature = base64_encode(hash_hmac('sha1', $data, $this->auth_token, true));
 | 
			
		||||
        
 | 
			
		||||
        return hash_equals($signature, $computed_signature);
 | 
			
		||||
        $validator = new \Twilio\Security\RequestValidator(get_option('twp_twilio_auth_token'));
 | 
			
		||||
        return $validator->validate($signature, $url, $params);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -658,11 +658,10 @@ class TWP_Webhooks {
 | 
			
		||||
     * Send default response
 | 
			
		||||
     */
 | 
			
		||||
    private function send_default_response() {
 | 
			
		||||
        $twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
 | 
			
		||||
        $say = $twiml->addChild('Say', 'Thank you for calling. Goodbye.');
 | 
			
		||||
        $say->addAttribute('voice', 'alice');
 | 
			
		||||
        $twiml->addChild('Hangup');
 | 
			
		||||
        echo $twiml->asXML();
 | 
			
		||||
        $response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
        $response->say('Thank you for calling. Goodbye.', ['voice' => 'alice']);
 | 
			
		||||
        $response->hangup();
 | 
			
		||||
        echo $response->asXML();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1074,17 +1073,13 @@ class TWP_Webhooks {
 | 
			
		||||
            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 = '<?xml version="1.0" encoding="UTF-8"?>';
 | 
			
		||||
                $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>';
 | 
			
		||||
                // Create TwiML using SDK directly
 | 
			
		||||
                $response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
                $response->say('Connecting your outbound call...', ['voice' => 'alice']);
 | 
			
		||||
                $response->dial($target_number, ['callerId' => $from_number, 'timeout' => 30]);
 | 
			
		||||
                
 | 
			
		||||
                return $this->send_twiml_response($twiml);
 | 
			
		||||
                // If call isn't answered, the TwiML will handle the fallback
 | 
			
		||||
                return $this->send_twiml_response($response->asXML());
 | 
			
		||||
            } else {
 | 
			
		||||
                // Enhanced error message with debugging info
 | 
			
		||||
                $error_msg = 'Unable to process outbound call.';
 | 
			
		||||
@@ -1097,16 +1092,18 @@ class TWP_Webhooks {
 | 
			
		||||
                
 | 
			
		||||
                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);
 | 
			
		||||
                $error_response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
                $error_response->say($error_msg, ['voice' => 'alice']);
 | 
			
		||||
                $error_response->hangup();
 | 
			
		||||
                return $this->send_twiml_response($error_response->asXML());
 | 
			
		||||
            }
 | 
			
		||||
        
 | 
			
		||||
        } 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);
 | 
			
		||||
            $exception_response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
            $exception_response->say('Technical error occurred. Please try again.', ['voice' => 'alice']);
 | 
			
		||||
            $exception_response->hangup();
 | 
			
		||||
            return $this->send_twiml_response($exception_response->asXML());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -341,9 +341,8 @@ class TWP_Workflow {
 | 
			
		||||
        } else if ($routing['action'] === 'forward' && $routing['data']['forward_number']) {
 | 
			
		||||
            // Forward call
 | 
			
		||||
            $twiml = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
            $dial = $twiml->dial();
 | 
			
		||||
            $dial->number($routing['data']['forward_number']);
 | 
			
		||||
            return $twiml;
 | 
			
		||||
            $twiml->dial($routing['data']['forward_number']);
 | 
			
		||||
            return $twiml->asXML();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Fallback to legacy behavior if new routing doesn't work
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										136
									
								
								install-twilio-sdk.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								install-twilio-sdk.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# Script to install Twilio PHP SDK without Composer
 | 
			
		||||
# This is REQUIRED for the plugin to work properly
 | 
			
		||||
 | 
			
		||||
echo "Installing Twilio PHP SDK v8.7.0..."
 | 
			
		||||
echo "IMPORTANT: This SDK is required for the plugin to function."
 | 
			
		||||
 | 
			
		||||
# Check if we can download files
 | 
			
		||||
if ! command -v curl &> /dev/null; then
 | 
			
		||||
    echo "ERROR: curl is required to download the SDK"
 | 
			
		||||
    echo "Please install curl and try again"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if ! command -v tar &> /dev/null; then
 | 
			
		||||
    echo "ERROR: tar is required to extract the SDK"
 | 
			
		||||
    echo "Please install tar and try again"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Create vendor directory if it doesn't exist
 | 
			
		||||
mkdir -p vendor/twilio/sdk
 | 
			
		||||
 | 
			
		||||
# Download the latest release (8.7.0)
 | 
			
		||||
echo "Downloading Twilio SDK from GitHub..."
 | 
			
		||||
if ! curl -L https://github.com/twilio/twilio-php/archive/refs/tags/8.7.0.tar.gz -o twilio-sdk.tar.gz; then
 | 
			
		||||
    echo "ERROR: Failed to download Twilio SDK"
 | 
			
		||||
    echo "Please check your internet connection and try again"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Extract the archive
 | 
			
		||||
echo "Extracting SDK files..."
 | 
			
		||||
if ! tar -xzf twilio-sdk.tar.gz; then
 | 
			
		||||
    echo "ERROR: Failed to extract SDK files"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Check if the extracted directory exists
 | 
			
		||||
if [ ! -d "twilio-php-8.7.0/src" ]; then
 | 
			
		||||
    echo "ERROR: Extracted SDK directory structure is unexpected"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Create the Twilio directory structure
 | 
			
		||||
echo "Setting up SDK directory structure..."
 | 
			
		||||
mkdir -p vendor/twilio
 | 
			
		||||
 | 
			
		||||
# Remove existing SDK if it exists
 | 
			
		||||
if [ -d "vendor/twilio/sdk" ]; then
 | 
			
		||||
    rm -rf vendor/twilio/sdk
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Move the entire src directory to be the sdk
 | 
			
		||||
echo "Installing SDK files..."
 | 
			
		||||
if ! mv twilio-php-8.7.0/src vendor/twilio/sdk; then
 | 
			
		||||
    echo "ERROR: Failed to move SDK files"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Create a comprehensive autoloader
 | 
			
		||||
cat > vendor/autoload.php << 'EOF'
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Twilio SDK v8.7.0 Autoloader
 | 
			
		||||
 * This file loads the Twilio PHP SDK classes
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Prevent multiple registrations
 | 
			
		||||
if (!defined('TWILIO_AUTOLOADER_REGISTERED')) {
 | 
			
		||||
    define('TWILIO_AUTOLOADER_REGISTERED', true);
 | 
			
		||||
    
 | 
			
		||||
    // Register the autoloader
 | 
			
		||||
    spl_autoload_register(function ($class) {
 | 
			
		||||
        // Only handle Twilio classes
 | 
			
		||||
        if (strpos($class, 'Twilio\\') !== 0) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Convert class name to file path
 | 
			
		||||
        $relative_class = substr($class, 7); // Remove 'Twilio\'
 | 
			
		||||
        $file = __DIR__ . '/twilio/sdk/' . str_replace('\\', '/', $relative_class) . '.php';
 | 
			
		||||
        
 | 
			
		||||
        if (file_exists($file)) {
 | 
			
		||||
            require_once $file;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Try to load the SDK's own autoloader if it exists
 | 
			
		||||
    $sdk_autoloader = __DIR__ . '/twilio/sdk/autoload.php';
 | 
			
		||||
    if (file_exists($sdk_autoloader)) {
 | 
			
		||||
        require_once $sdk_autoloader;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Load essential Twilio classes manually to ensure they're available
 | 
			
		||||
    $essential_classes = [
 | 
			
		||||
        __DIR__ . '/twilio/sdk/Rest/Client.php',
 | 
			
		||||
        __DIR__ . '/twilio/sdk/TwiML/VoiceResponse.php',
 | 
			
		||||
        __DIR__ . '/twilio/sdk/Exceptions/TwilioException.php',
 | 
			
		||||
        __DIR__ . '/twilio/sdk/Security/RequestValidator.php'
 | 
			
		||||
    ];
 | 
			
		||||
    
 | 
			
		||||
    foreach ($essential_classes as $class_file) {
 | 
			
		||||
        if (file_exists($class_file)) {
 | 
			
		||||
            require_once $class_file;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
# Clean up
 | 
			
		||||
rm -rf twilio-php-8.7.0
 | 
			
		||||
rm twilio-sdk.tar.gz
 | 
			
		||||
 | 
			
		||||
# Verify installation
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Verifying installation..."
 | 
			
		||||
if [ -f "vendor/autoload.php" ] && [ -d "vendor/twilio/sdk" ]; then
 | 
			
		||||
    echo "✓ Twilio SDK v8.7.0 installed successfully!"
 | 
			
		||||
    echo "✓ Autoloader created"
 | 
			
		||||
    echo "✓ Installation complete"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "The Twilio WordPress Plugin is now ready to use."
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Next steps:"
 | 
			
		||||
    echo "1. Configure your Twilio credentials in WordPress admin"
 | 
			
		||||
    echo "2. Set up your phone numbers and webhooks"
 | 
			
		||||
    echo "3. Test the installation with a sample call"
 | 
			
		||||
else
 | 
			
		||||
    echo "✗ Installation failed - files not found"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										34
									
								
								test-ajax-response.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								test-ajax-response.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Test what the AJAX method actually returns
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Load WordPress
 | 
			
		||||
$wp_load_path = dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-load.php';
 | 
			
		||||
require_once $wp_load_path;
 | 
			
		||||
 | 
			
		||||
// Load the plugin
 | 
			
		||||
require_once __DIR__ . '/includes/class-twp-twilio-api.php';
 | 
			
		||||
 | 
			
		||||
// Create API instance
 | 
			
		||||
$api = new TWP_Twilio_API();
 | 
			
		||||
 | 
			
		||||
// Get phone numbers
 | 
			
		||||
$response = $api->get_phone_numbers();
 | 
			
		||||
 | 
			
		||||
echo "API Response:\n";
 | 
			
		||||
echo "=============\n";
 | 
			
		||||
print_r($response);
 | 
			
		||||
 | 
			
		||||
if ($response['success'] && !empty($response['data']['incoming_phone_numbers'])) {
 | 
			
		||||
    echo "\nFirst phone number data:\n";
 | 
			
		||||
    echo "========================\n";
 | 
			
		||||
    $first = $response['data']['incoming_phone_numbers'][0];
 | 
			
		||||
    foreach ($first as $key => $value) {
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            echo "$key: " . json_encode($value) . "\n";
 | 
			
		||||
        } else {
 | 
			
		||||
            echo "$key: $value\n";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								test-capabilities.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								test-capabilities.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Test script to check PhoneNumberCapabilities methods
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Load the autoloader
 | 
			
		||||
require_once __DIR__ . '/vendor/autoload.php';
 | 
			
		||||
 | 
			
		||||
// Create a mock capabilities object to test available methods
 | 
			
		||||
echo "Testing PhoneNumberCapabilities methods:\n";
 | 
			
		||||
echo "========================================\n";
 | 
			
		||||
 | 
			
		||||
// We'll check what methods are available
 | 
			
		||||
$reflection = new ReflectionClass('Twilio\Base\PhoneNumberCapabilities');
 | 
			
		||||
$methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
 | 
			
		||||
 | 
			
		||||
echo "Public methods available:\n";
 | 
			
		||||
foreach ($methods as $method) {
 | 
			
		||||
    if (!$method->isConstructor() && !$method->isDestructor()) {
 | 
			
		||||
        echo "- " . $method->getName() . "()\n";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo "\nProperties:\n";
 | 
			
		||||
$properties = $reflection->getProperties();
 | 
			
		||||
foreach ($properties as $property) {
 | 
			
		||||
    echo "- " . $property->getName() . " (" . ($property->isPublic() ? 'public' : ($property->isProtected() ? 'protected' : 'private')) . ")\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if we can access via array notation
 | 
			
		||||
echo "\nTesting array access:\n";
 | 
			
		||||
try {
 | 
			
		||||
    // This won't work, but let's see what happens
 | 
			
		||||
    echo "ArrayAccess interface: " . (in_array('ArrayAccess', class_implements('Twilio\Base\PhoneNumberCapabilities')) ? 'YES' : 'NO') . "\n";
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
    echo "Error: " . $e->getMessage() . "\n";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								test-sdk.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								test-sdk.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Simple test script to verify Twilio SDK installation
 | 
			
		||||
 * Run this from command line: php test-sdk.php
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
echo "Twilio SDK Installation Test\n";
 | 
			
		||||
echo "===========================\n\n";
 | 
			
		||||
 | 
			
		||||
// Check if autoloader exists
 | 
			
		||||
$autoloader = __DIR__ . '/vendor/autoload.php';
 | 
			
		||||
echo "1. Checking autoloader...\n";
 | 
			
		||||
echo "   Path: $autoloader\n";
 | 
			
		||||
 | 
			
		||||
if (!file_exists($autoloader)) {
 | 
			
		||||
    echo "   ❌ FAIL: Autoloader not found\n";
 | 
			
		||||
    echo "   Please run: ./install-twilio-sdk.sh\n";
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo "   ✅ OK: Autoloader exists\n\n";
 | 
			
		||||
 | 
			
		||||
// Load autoloader
 | 
			
		||||
echo "2. Loading autoloader...\n";
 | 
			
		||||
require_once $autoloader;
 | 
			
		||||
echo "   ✅ OK: Autoloader loaded\n\n";
 | 
			
		||||
 | 
			
		||||
// Check for Twilio classes
 | 
			
		||||
echo "3. Checking Twilio classes...\n";
 | 
			
		||||
 | 
			
		||||
$classes_to_check = [
 | 
			
		||||
    'Twilio\Rest\Client',
 | 
			
		||||
    'Twilio\TwiML\VoiceResponse', 
 | 
			
		||||
    'Twilio\Exceptions\TwilioException',
 | 
			
		||||
    'Twilio\Security\RequestValidator'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
$all_good = true;
 | 
			
		||||
foreach ($classes_to_check as $class) {
 | 
			
		||||
    if (class_exists($class)) {
 | 
			
		||||
        echo "   ✅ OK: $class\n";
 | 
			
		||||
    } else {
 | 
			
		||||
        echo "   ❌ FAIL: $class not found\n";
 | 
			
		||||
        $all_good = false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo "\n";
 | 
			
		||||
 | 
			
		||||
if ($all_good) {
 | 
			
		||||
    echo "🎉 SUCCESS: Twilio SDK is properly installed!\n";
 | 
			
		||||
    echo "\nYou can now use the WordPress plugin.\n";
 | 
			
		||||
    echo "Don't forget to configure your Twilio credentials in WordPress admin.\n";
 | 
			
		||||
} else {
 | 
			
		||||
    echo "❌ FAILURE: SDK installation incomplete\n";
 | 
			
		||||
    echo "\nPlease run: ./install-twilio-sdk.sh\n";
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Try to create a simple TwiML response
 | 
			
		||||
echo "\n4. Testing TwiML generation...\n";
 | 
			
		||||
try {
 | 
			
		||||
    $response = new \Twilio\TwiML\VoiceResponse();
 | 
			
		||||
    $response->say('Hello from Twilio SDK test!');
 | 
			
		||||
    echo "   ✅ OK: TwiML generation works\n";
 | 
			
		||||
    echo "   Generated: " . substr(str_replace(["\n", "\r"], '', $response->asXML()), 0, 100) . "...\n";
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
    echo "   ❌ FAIL: TwiML generation failed: " . $e->getMessage() . "\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo "\nInstallation test complete!\n";
 | 
			
		||||
@@ -28,6 +28,45 @@ function twp_activate() {
 | 
			
		||||
    TWP_Activator::activate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if Twilio SDK is installed and show admin notice if not
 | 
			
		||||
 */
 | 
			
		||||
function twp_check_sdk_installation() {
 | 
			
		||||
    $autoloader_path = TWP_PLUGIN_DIR . 'vendor/autoload.php';
 | 
			
		||||
    $sdk_installed = false;
 | 
			
		||||
    
 | 
			
		||||
    if (file_exists($autoloader_path)) {
 | 
			
		||||
        // Try to load autoloader and check for classes
 | 
			
		||||
        require_once $autoloader_path;
 | 
			
		||||
        $sdk_installed = class_exists('Twilio\Rest\Client');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (!$sdk_installed) {
 | 
			
		||||
        add_action('admin_notices', 'twp_sdk_missing_notice');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display admin notice for missing SDK
 | 
			
		||||
 */
 | 
			
		||||
function twp_sdk_missing_notice() {
 | 
			
		||||
    ?>
 | 
			
		||||
    <div class="notice notice-error is-dismissible">
 | 
			
		||||
        <h3>Twilio WordPress Plugin - SDK Required</h3>
 | 
			
		||||
        <p><strong>The Twilio PHP SDK is required for this plugin to work.</strong></p>
 | 
			
		||||
        <p>To install the SDK, run this command in your plugin directory:</p>
 | 
			
		||||
        <code>chmod +x install-twilio-sdk.sh && ./install-twilio-sdk.sh</code>
 | 
			
		||||
        <p>Or install via Composer: <code>composer install</code></p>
 | 
			
		||||
        <p><em>Plugin path: <?php echo TWP_PLUGIN_DIR; ?></em></p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <?php
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check SDK installation on admin pages
 | 
			
		||||
if (is_admin()) {
 | 
			
		||||
    add_action('admin_init', 'twp_check_sdk_installation');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin deactivation hook
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user