diff --git a/CLAUDE.md b/CLAUDE.md
index 6f79366..d64b30d 100644
--- a/CLAUDE.md
+++ b/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:
diff --git a/README.md b/README.md
index bdf7f87..b50e024 100644
--- a/README.md
+++ b/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.
diff --git a/TWILIO_SDK_MIGRATION.md b/TWILIO_SDK_MIGRATION.md
new file mode 100644
index 0000000..08711cb
--- /dev/null
+++ b/TWILIO_SDK_MIGRATION.md
@@ -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 = '';
+$twiml .= '';
+$twiml .= 'Hello world';
+$twiml .= '';
+```
+
+### 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/)
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..5951cec
--- /dev/null
+++ b/composer.json
@@ -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"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/debug-phone-numbers.php b/debug-phone-numbers.php
new file mode 100644
index 0000000..77ad3dd
--- /dev/null
+++ b/debug-phone-numbers.php
@@ -0,0 +1,83 @@
+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";
\ No newline at end of file
diff --git a/includes/class-twp-agent-manager.php b/includes/class-twp-agent-manager.php
index 108d797..c78e4cd 100644
--- a/includes/class-twp-agent-manager.php
+++ b/includes/class-twp-agent-manager.php
@@ -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']);
}
}
diff --git a/includes/class-twp-callback-manager.php b/includes/class-twp-callback-manager.php
index 5c5f490..7cc6cf6 100644
--- a/includes/class-twp-callback-manager.php
+++ b/includes/class-twp-callback-manager.php
@@ -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'
]);
diff --git a/includes/class-twp-twilio-api.php b/includes/class-twp-twilio-api.php
index 4ded2cd..338e254 100644
--- a/includes/class-twp-twilio-api.php
+++ b/includes/class-twp-twilio-api.php
@@ -1,276 +1,474 @@
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('');
- $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('');
-
- 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('');
-
- $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);
}
}
\ No newline at end of file
diff --git a/includes/class-twp-webhooks.php b/includes/class-twp-webhooks.php
index 515ae6e..d2956b8 100644
--- a/includes/class-twp-webhooks.php
+++ b/includes/class-twp-webhooks.php
@@ -658,11 +658,10 @@ class TWP_Webhooks {
* Send default response
*/
private function send_default_response() {
- $twiml = new SimpleXMLElement('');
- $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 = '';
- $twiml .= '';
- $twiml .= 'Connecting your outbound call...';
- $twiml .= '';
- $twiml .= '' . esc_html($target_number) . '';
- $twiml .= '';
- $twiml .= 'The number you called is not available. Please try again later.';
- $twiml .= '';
+ // 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 = '';
- $twiml .= '' . esc_html($error_msg) . '';
- 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 = '';
- $twiml .= 'Technical error occurred. Please try again.';
- 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());
}
}
}
\ No newline at end of file
diff --git a/includes/class-twp-workflow.php b/includes/class-twp-workflow.php
index 9e5b91c..240fc67 100644
--- a/includes/class-twp-workflow.php
+++ b/includes/class-twp-workflow.php
@@ -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
diff --git a/install-twilio-sdk.sh b/install-twilio-sdk.sh
new file mode 100644
index 0000000..fe293e5
--- /dev/null
+++ b/install-twilio-sdk.sh
@@ -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'
+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";
+ }
+ }
+}
\ No newline at end of file
diff --git a/test-capabilities.php b/test-capabilities.php
new file mode 100644
index 0000000..11223ea
--- /dev/null
+++ b/test-capabilities.php
@@ -0,0 +1,37 @@
+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";
+}
\ No newline at end of file
diff --git a/test-sdk.php b/test-sdk.php
new file mode 100644
index 0000000..f94e3fe
--- /dev/null
+++ b/test-sdk.php
@@ -0,0 +1,71 @@
+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";
\ No newline at end of file
diff --git a/twilio-wp-plugin.php b/twilio-wp-plugin.php
index f724b7d..b0a799e 100644
--- a/twilio-wp-plugin.php
+++ b/twilio-wp-plugin.php
@@ -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() {
+ ?>
+
+
Twilio WordPress Plugin - SDK Required
+
The Twilio PHP SDK is required for this plugin to work.
+
To install the SDK, run this command in your plugin directory:
+
chmod +x install-twilio-sdk.sh && ./install-twilio-sdk.sh
+
Or install via Composer: composer install
+
Plugin path:
+
+