4 Commits

Author SHA1 Message Date
f8919af31a Fix SDK autoloader path for Twilio namespace
All checks were successful
Create Release / build (push) Successful in 3s
The SDK files are at twilio/sdk/Twilio/Rest/Client.php but the
autoloader was looking at twilio/sdk/Rest/Client.php. Fixed by
using the full class name in the path instead of stripping the
Twilio\ prefix.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 18:32:19 -08:00
3e4dff5c4e Add SDK persistence and configurable edge location
All checks were successful
Create Release / build (push) Successful in 4s
- Add external SDK installation (wp-content/twilio-sdk/) that survives
  WordPress plugin updates
- Add install-twilio-sdk-external.sh script for external SDK setup
- Update SDK loading to check external location first, internal fallback
- Add post-update detection hook to warn if SDK was deleted
- Add configurable Twilio Edge Location setting (default: roaming)
- Fix US calls failing due to hardcoded Sydney edge location

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 18:17:25 -08:00
f0806d7e67 Add comprehensive Android tablet debugging guide
All checks were successful
Create Release / build (push) Successful in 4s
Added detailed debugging documentation for troubleshooting browser phone
issues on Android tablets. Covers USB debugging setup, Chrome DevTools
connection, common issues, error codes, and testing procedures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 08:37:26 -08:00
61beadcd06 Fix browser phone connection and audio issues on Android tablets
All checks were successful
Create Release / build (push) Successful in 6s
Resolves issues where browser phone PWA failed to connect and calls would
immediately hang up when answered on Android tablets. Adds proper mobile
audio handling, device connection monitoring, and PWA notifications for
incoming calls.

Key changes:
- Add AudioContext initialization with mobile unlock for autoplay support
- Add Android-specific WebRTC constraints (echo cancellation, ICE restart)
- Add device connection state monitoring and auto-reconnection
- Add incoming call ringtone with vibration fallback
- Add PWA service worker notifications for background calls
- Add Page Visibility API for background call detection
- Improve call answer handler with connection state validation
- Add touch event support for mobile dialpad

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 13:21:29 -08:00
12 changed files with 1297 additions and 56 deletions

View File

@@ -6,6 +6,7 @@
- **URL**: `https://phone.cloud-hosting.io/`
- **Deployment**: rsync to Docker (remote server only, not local)
- **SDK**: Twilio PHP SDK v8.7.0
- **External SDK**: `wp-content/twilio-sdk/` (survives plugin updates)
## Phone Variable Names
**Use**: `incoming_number`, `agent_number`, `customer_number`, `workflow_number`, `queue_number`, `default_number`
@@ -47,6 +48,25 @@ $api->update_call($customer_call_sid, ['twiml' => $twiml_xml]);
- Firefox support added
- 1-min agent status auto-revert
## SDK Installation
- **External SDK (Recommended)**: Use `install-twilio-sdk-external.sh` to install SDK to `wp-content/twilio-sdk/`
- Survives WordPress plugin updates
- SDK location defined by `TWP_EXTERNAL_SDK_DIR` constant
- Loading priority: External first, then internal `vendor/` fallback
- **Internal SDK (Alternative)**: Use `install-twilio-sdk.sh` to install to `vendor/`
- Will be deleted when WordPress updates the plugin
- Requires reinstallation after each plugin update
- **SDK Loading**: Plugin checks external location first via autoloader, falls back to internal
- **Post-Update Detection**: Hook on `upgrader_process_complete` checks SDK status and shows warning
## Browser Phone Configuration
- **Edge Location Setting**: Configurable via Settings → Twilio Edge Location
- Default: `roaming` (auto-select closest edge)
- Options: ashburn, umatilla, dublin, frankfurt, singapore, sydney, tokyo, sao-paulo
- Stored in: `twp_twilio_edge` option
- Used by: Browser phone JavaScript for WebRTC connection
- Critical: Wrong edge causes immediate call failures (e.g., US calls with Sydney edge)
## Development Notes
- **API**: E.164 format (+1XXXXXXXXXX)
- **Database**: Use `$wpdb`, prepared statements
@@ -61,5 +81,28 @@ $api->update_call($customer_call_sid, ['twiml' => $twiml_xml]);
- ElevenLabs TTS with Alice fallback
- 68 AJAX actions, 26 REST endpoints
## Recent Technical Changes (v2.8.9)
### SDK Persistence Between Plugin Updates
- **Problem**: WordPress plugin updates delete entire plugin folder including `vendor/` SDK
- **Solution**: External SDK installation at `wp-content/twilio-sdk/` survives updates
- **Implementation**:
- New constant: `TWP_EXTERNAL_SDK_DIR` points to `wp-content/twilio-sdk/`
- Loading priority in `twp_check_sdk_installation()`: External first, internal fallback
- Classes updated: `TWP_Twilio_API`, `TWP_Webhooks` constructors check external location first
- New script: `install-twilio-sdk-external.sh` automates external installation
- Post-update hook: `twp_check_sdk_after_update()` detects missing SDK after updates
- Admin notices: `twp_sdk_missing_notice()` shows both installation options
- Warning system: `twp_show_sdk_update_warning()` via transient after plugin updates
### US Calls Failing Fix (Browser Phone)
- **Problem**: Browser phone had hardcoded `edge: 'sydney'`, causing US calls to fail with immediate HANGUP
- **Solution**: Configurable edge location via WordPress settings
- **Implementation**:
- New setting: `twp_twilio_edge` with default value `roaming`
- Settings UI: Dropdown in admin settings with 8 edge options
- Browser phone JS: Uses `get_option('twp_twilio_edge', 'roaming')` instead of hardcoded value
- Edge options: roaming, ashburn, umatilla, dublin, frankfurt, singapore, sydney, tokyo, sao-paulo
---
*Updated: Sept 2025*
*Updated: Jan 2026*

427
DEBUGGING-TABLET.md Normal file
View File

@@ -0,0 +1,427 @@
# Debugging Browser Phone on Android Tablet
This guide explains how to debug the Twilio WordPress Plugin browser phone on Android tablets (Samsung and other devices).
## Prerequisites
- Android tablet with Chrome browser
- USB cable to connect tablet to computer
- Computer with Chrome browser installed
- USB debugging enabled on tablet
---
## Part 1: Enable USB Debugging on Android Tablet
### Step 1: Enable Developer Options
1. Open **Settings** on your Android tablet
2. Scroll down to **About tablet** (or **About device**)
3. Find **Build number** (may be under "Software information")
4. Tap **Build number** 7 times rapidly
5. You'll see a message: "You are now a developer!"
### Step 2: Enable USB Debugging
1. Go back to **Settings**
2. Scroll down to **Developer options** (newly appeared)
3. Toggle **Developer options** to ON
4. Find **USB debugging** in the list
5. Toggle **USB debugging** to ON
6. Confirm the prompt if asked
### Step 3: Trust Your Computer
1. Connect tablet to computer via USB cable
2. Unlock your tablet screen
3. A popup will appear: "Allow USB debugging?"
4. Check **Always allow from this computer**
5. Tap **OK** or **Allow**
---
## Part 2: Connect Chrome DevTools
### On Your Computer
1. Open **Chrome browser** on your computer
2. Navigate to: `chrome://inspect`
3. You should see your tablet device listed under "Remote Target"
4. Wait a few seconds for the device to appear
### Inspect the Browser Phone Page
1. On your tablet, open Chrome and navigate to:
```
https://phone.cloud-hosting.io/wp-admin/admin.php?page=twilio-wp-browser-phone
```
2. On your computer's Chrome DevTools (`chrome://inspect`), you'll see the page listed
3. Click **inspect** next to the browser phone page
4. A DevTools window will open showing your tablet's browser
---
## Part 3: Debug Browser Phone Issues
### Check Console Logs
In the **Console** tab, look for key messages:
#### Successful Connection
```
Twilio SDK loaded successfully
Setting up Twilio Device...
Device detection - Android: true, Mobile: true
AudioContext created, state: running
Device registered successfully
Device connection state: connected
```
#### Connection Issues
```
Device not connected, state: disconnected
Failed to register device
Connection error: 31005
```
#### Call Issues
```
Answer button clicked
Device connection state: connected
Accepting call...
Call accepted and connected
```
### Monitor Network Requests
1. Switch to **Network** tab in DevTools
2. Filter by: `twp_` or `twilio`
3. Check for failed requests (red status codes)
4. Look for:
- `twp_generate_capability_token` - Should return 200
- `twp_get_phone_numbers` - Should return 200
- WebSocket connections to Twilio
### Check Device Registration
In the **Console** tab, type:
```javascript
device
```
This shows the current Twilio Device object. Check:
- `device.state` - Should be "registered"
- `device.token` - Should exist (long string)
### Check AudioContext State
In the **Console** tab, type:
```javascript
audioContext
```
Check:
- Should exist (not null)
- `audioContext.state` - Should be "running" (not "suspended")
---
## Part 4: Common Issues & Solutions
### Issue 1: "Device not connected" Error
**Symptoms**: Status shows "Disconnected", calls hang up immediately
**Debugging**:
```javascript
// In console, check:
deviceConnectionState
// Should be: "connected"
```
**Solutions**:
1. Check network connection (try WiFi vs cellular)
2. Refresh the page with cache cleared
3. Check console for token errors
4. Verify Twilio credentials in plugin settings
### Issue 2: Call Hangs Up Immediately When Answered
**Symptoms**: Click "Answer" button, call disconnects instantly
**Debugging**:
```javascript
// Check device state before answering:
deviceConnectionState
// Check for errors in call handler
```
**Look for console errors**:
- `31005` - Connection/network error
- `31201` - ICE connection failure
- `31208` - Media connection error
**Solutions**:
1. Grant microphone permissions (Settings > Site settings > Microphone)
2. Check AudioContext is running: `audioContext.state`
3. Try switching between WiFi and cellular data
4. Clear Chrome cache and reload
### Issue 3: No Sound/Ringtone on Incoming Call
**Symptoms**: Call arrives but no audio plays, no vibration
**Debugging**:
```javascript
// Check audio setup:
ringtoneAudio
audioContext.state
```
**Solutions**:
1. Tap screen to unlock AudioContext (mobile restriction)
2. Check if ringtone file exists (optional, vibration is fallback)
3. Verify device supports Vibration API: `'vibrate' in navigator`
4. Check browser volume settings
### Issue 4: No Browser Notifications
**Symptoms**: No notification when call arrives in background
**Debugging**:
```javascript
// Check notification permission:
Notification.permission
// Should be: "granted"
// Check service worker:
navigator.serviceWorker.controller
// Should exist
```
**Solutions**:
1. Grant notification permission: Settings > Site settings > Notifications
2. Check service worker registration in **Application** tab of DevTools
3. Look for service worker logs in console
4. Reinstall PWA if installed
### Issue 5: Microphone Permission Denied
**Symptoms**: Error message about microphone access
**Solutions**:
1. Chrome Settings > Site settings > Microphone
2. Find `phone.cloud-hosting.io`
3. Change permission to **Allow**
4. Refresh the browser phone page
---
## Part 5: Tablet-Specific Checks
### Check User Agent
In console:
```javascript
navigator.userAgent
```
Should include "Android" and "Chrome"
### Check WebRTC Support
```javascript
// Check getUserMedia support:
navigator.mediaDevices.getUserMedia
// Should be: function
// Check Notification support:
'Notification' in window
// Should be: true
// Check Service Worker support:
'serviceWorker' in navigator
// Should be: true
```
### Check Audio Constraints
Look in console for:
```
Twilio Device created with audio constraints: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
googEchoCancellation: true,
...
}
```
### Test Vibration
In console:
```javascript
navigator.vibrate([300, 200, 300])
```
Tablet should vibrate if supported.
---
## Part 6: Advanced Debugging
### Monitor Twilio Device Events
In console, add event listeners:
```javascript
device.on('registered', () => console.log('Device registered!'));
device.on('error', (error) => console.error('Device error:', error));
device.on('incoming', (call) => console.log('Incoming call:', call));
device.on('unregistered', () => console.log('Device unregistered'));
```
### Check Call State During Active Call
```javascript
// When in a call:
currentCall
currentCall.status()
currentCall.parameters
```
### Test Audio Context Resume
```javascript
// Try to resume manually:
audioContext.resume().then(() => {
console.log('AudioContext state:', audioContext.state);
});
```
### Check Token Expiry
```javascript
// See when token expires:
new Date(tokenExpiry)
```
---
## Part 7: Logging Important Information
### Collect Debugging Info
Run this in console to get a debug report:
```javascript
console.log('=== Browser Phone Debug Report ===');
console.log('User Agent:', navigator.userAgent);
console.log('Device State:', deviceConnectionState);
console.log('Device Registered:', device ? device.state : 'no device');
console.log('AudioContext:', audioContext ? audioContext.state : 'no context');
console.log('Notification Permission:', Notification.permission);
console.log('Service Worker:', navigator.serviceWorker.controller ? 'active' : 'inactive');
console.log('Current Call:', currentCall ? 'in call' : 'no call');
console.log('Token Expiry:', tokenExpiry ? new Date(tokenExpiry) : 'unknown');
```
### Check WordPress AJAX Responses
In **Network** tab:
1. Filter: `admin-ajax.php`
2. Click on request
3. Check **Preview** tab for response
4. Look for `success: true` or error messages
---
## Part 8: Testing Checklist
After fixing issues, test these scenarios:
- [ ] Device shows "Connected" status
- [ ] Can make outbound call successfully
- [ ] Can receive incoming call (see notification)
- [ ] Can answer incoming call without hang-up
- [ ] Ringtone plays or tablet vibrates
- [ ] Call audio is clear (echo cancellation working)
- [ ] Can switch between WiFi and cellular during call
- [ ] Browser notification appears when app in background
- [ ] Service worker logs appear in console
- [ ] No errors in console during call lifecycle
- [ ] Can put call on hold
- [ ] Can transfer call
- [ ] Call timer updates correctly
---
## Troubleshooting Specific Error Codes
### Twilio Error 31005
**Meaning**: WebSocket connection failed
**Causes**:
- Network connectivity issues
- Firewall blocking WebSocket
- Mobile network switching (WiFi ↔ cellular)
**Solutions**:
- Check internet connection
- Try different network
- Wait for ICE restart (enabled in config)
### Twilio Error 31201
**Meaning**: ICE connection failed
**Causes**:
- Restrictive NAT/firewall
- Mobile network issues
**Solutions**:
- Try different network
- Check WebRTC connectivity
- Enable mobile data if on WiFi only
### Twilio Error 31204
**Meaning**: Connection error
**Causes**:
- Media connection setup failed
- Signaling timeout
**Solutions**:
- Refresh page
- Check microphone permissions
- Verify audio constraints applied
### Twilio Error 31208
**Meaning**: Media connection failed
**Causes**:
- Microphone access denied
- Audio device issues
**Solutions**:
- Grant microphone permission
- Check device audio settings
- Restart browser
---
## Additional Resources
- Twilio Voice SDK Errors: https://www.twilio.com/docs/voice/sdks/javascript/errors
- Chrome DevTools Remote Debugging: https://developer.chrome.com/docs/devtools/remote-debugging/
- WebRTC Troubleshooting: https://webrtc.github.io/samples/
- Service Worker Debugging: https://developer.chrome.com/docs/workbox/
---
## Contact & Support
If issues persist after following this guide:
1. Collect debug report (see Part 7)
2. Take screenshots of console errors
3. Note tablet model and Android version
4. Report issue with collected information
**Important Files**:
- Browser phone implementation: `admin/class-twp-admin.php` (lines 7400-8300)
- Service worker: `assets/js/twp-service-worker.js`
- CLAUDE.md: Quick reference guide

View File

@@ -8,11 +8,20 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will
## Quick Installation
1. **Install the Twilio SDK** (Required):
1. **Install the Twilio SDK** (Required - Recommended Method):
```bash
chmod +x install-twilio-sdk-external.sh
./install-twilio-sdk-external.sh
```
This installs the SDK to `wp-content/twilio-sdk/` which **survives WordPress plugin updates**. The plugin will automatically detect and use this external SDK.
**Alternative Method** (SDK will be deleted during plugin updates):
```bash
chmod +x install-twilio-sdk.sh
./install-twilio-sdk.sh
```
This installs the SDK inside the plugin folder. You'll need to reinstall the SDK after each plugin update.
2. **Test the SDK installation**:
```bash
@@ -23,6 +32,7 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will
- Go to **Twilio** → **Settings**
- Enter Account SID and Auth Token
- Configure default phone numbers
- Set Twilio Edge Location (for browser phone - see Browser Phone Setup below)
4. **Set up Phone Numbers** in Twilio Console:
- Configure webhook URLs for voice and SMS
@@ -333,8 +343,20 @@ Comprehensive redesign of hold, transfer, and requeue functionality with profess
2. **Configure in WordPress**:
- Go to **Twilio** → **Settings**
- Enter TwiML App SID
- **Set Twilio Edge Location**: Select the edge location closest to your users (IMPORTANT)
- **Auto-select closest (Recommended)**: Automatically selects the best edge
- **US East (Ashburn)**: For East Coast USA users
- **US West (Umatilla)**: For West Coast USA users
- **Europe - Ireland (Dublin)**: For European users
- **Europe - Germany (Frankfurt)**: For Central European users
- **Singapore**: For Southeast Asian users
- **Sydney**: For Australian users
- **Tokyo**: For Japanese users
- **Sao Paulo**: For South American users
- Save settings
**Note**: Selecting the wrong edge location can cause calls to fail immediately. If you're experiencing browser phone connection issues, verify your edge location is appropriate for your region.
3. **Access Browser Phone** (Admin Only):
- Navigate to **WordPress Admin** → **Twilio** → **Browser Phone**
- Select caller ID from available numbers
@@ -432,14 +454,34 @@ Access the full browser phone interface at: **WordPress Admin → Twilio → Bro
- **Login Required**: Users must be logged in to access browser phone functionality
- Check TwiML App SID is configured in WordPress admin settings
#### Browser Phone Calls Failing Immediately
If browser phone calls disconnect immediately or show HANGUP errors:
- **Check Edge Location Setting**: Go to **Twilio** → **Settings** → **Twilio Edge Location**
- **US Users**: Should use "Auto-select closest" (roaming), "US East (Ashburn)", or "US West (Umatilla)"
- **International Users**: Select the edge location closest to your region
- **Problem**: Wrong edge location causes gateway to immediately reject calls
- **Solution**: Change edge location setting and try the call again (no restart needed)
#### "Twilio SDK classes not available"
**Recommended Solution** (SDK survives plugin updates):
```bash
# Reinstall SDK
# Install SDK to external location
./install-twilio-sdk-external.sh
# Test installation
php test-sdk.php
```
**Alternative Solution** (will need reinstall after plugin updates):
```bash
# Install SDK inside plugin folder
./install-twilio-sdk.sh
# Test installation
php test-sdk.php
```
**After WordPress Plugin Update**: If you get this error after updating the plugin and used the internal SDK method, you'll need to reinstall the SDK. This won't happen if you use the external SDK method.
#### Calls Not Routing to Queues
- Verify queue exists and is active
- Check agent group has members
@@ -551,7 +593,19 @@ All webhooks are REST API endpoints under `/wp-json/twilio-webhook/v1/`:
## Version History
### v2.3.0 (Current - September 2025) - ENTERPRISE READY
### v2.8.9 (Current - January 2026) - SDK PERSISTENCE & BROWSER PHONE FIXES
- **SDK PERSISTENCE**: External SDK installation option that survives WordPress plugin updates
- New installation script: `install-twilio-sdk-external.sh` installs SDK to `wp-content/twilio-sdk/`
- Automatic detection: Plugin checks external SDK location first, falls back to internal
- Post-update warnings: Notifies if SDK was deleted during plugin update
- Zero downtime: Phone system continues working through plugin updates
- **BROWSER PHONE FIX**: Resolved US calls failing immediately with HANGUP errors
- Made Twilio Edge Location configurable (was hardcoded to Sydney)
- New setting: Twilio Edge Location with 8 options (roaming/auto-select, ashburn, umatilla, dublin, frankfurt, singapore, sydney, tokyo, sao-paulo)
- Default: "roaming" (auto-select closest edge for optimal performance)
- Critical fix: US users can now make calls successfully (were failing with Sydney edge)
### v2.3.0 (September 2025) - ENTERPRISE READY
- **SECURITY ENHANCEMENT**: Removed frontend browser phone interface, moved to admin-only access for enhanced security
- **ASSET REDUCTION**: Eliminated 108KB of frontend assets (browser-phone-frontend.js and CSS files)
- **SHORTCODE SECURITY**: Browser phone shortcode now provides secure redirect with authentication checks
@@ -611,4 +665,4 @@ This plugin integrates with Twilio services and requires a Twilio account.
---
**Enterprise Ready v2.3.0** - Extension transfers, browser phone compatibility, and automatic agent management now production-ready with comprehensive reliability improvements.
**Production Ready v2.8.9** - SDK persistence through plugin updates and configurable edge locations ensure zero-downtime phone operations.

View File

@@ -347,6 +347,25 @@ class TWP_Admin {
</td>
</tr>
<tr>
<th scope="row">Twilio Edge Location</th>
<td>
<?php $current_edge = get_option('twp_twilio_edge', 'roaming'); ?>
<select name="twp_twilio_edge" class="regular-text">
<option value="roaming" <?php selected($current_edge, 'roaming'); ?>>Auto-select closest (Recommended)</option>
<option value="ashburn" <?php selected($current_edge, 'ashburn'); ?>>US East (Ashburn)</option>
<option value="umatilla" <?php selected($current_edge, 'umatilla'); ?>>US West (Umatilla)</option>
<option value="dublin" <?php selected($current_edge, 'dublin'); ?>>Europe - Ireland (Dublin)</option>
<option value="frankfurt" <?php selected($current_edge, 'frankfurt'); ?>>Europe - Germany (Frankfurt)</option>
<option value="singapore" <?php selected($current_edge, 'singapore'); ?>>Asia Pacific (Singapore)</option>
<option value="sydney" <?php selected($current_edge, 'sydney'); ?>>Australia (Sydney)</option>
<option value="tokyo" <?php selected($current_edge, 'tokyo'); ?>>Japan (Tokyo)</option>
<option value="sao-paulo" <?php selected($current_edge, 'sao-paulo'); ?>>South America (Sao Paulo)</option>
</select>
<p class="description">Edge location for browser phone calls. Use "Auto-select closest" for best call quality, or select a specific region.</p>
</td>
</tr>
</table>
<h2>Eleven Labs API Settings</h2>
@@ -3818,6 +3837,7 @@ class TWP_Admin {
register_setting('twilio-wp-settings-group', 'twp_twilio_account_sid');
register_setting('twilio-wp-settings-group', 'twp_twilio_auth_token');
register_setting('twilio-wp-settings-group', 'twp_twiml_app_sid');
register_setting('twilio-wp-settings-group', 'twp_twilio_edge');
register_setting('twilio-wp-settings-group', 'twp_elevenlabs_api_key');
register_setting('twilio-wp-settings-group', 'twp_elevenlabs_voice_id');
register_setting('twilio-wp-settings-group', 'twp_elevenlabs_model_id');
@@ -6996,6 +7016,7 @@ class TWP_Admin {
<div class="phone-interface">
<div class="phone-display">
<div id="phone-status">Ready</div>
<div id="device-connection-status" style="font-size: 12px; color: #999; margin-top: 5px;">Connecting...</div>
<div id="phone-number-display"></div>
<div id="call-timer" style="display: none;">00:00</div>
</div>
@@ -7434,6 +7455,210 @@ class TWP_Admin {
var callStartTime = null;
var tokenRefreshTimer = null;
var tokenExpiry = null;
var audioContext = null;
var ringtoneAudio = null;
var isPageVisible = true;
var deviceConnectionState = 'disconnected'; // disconnected, connecting, connected
var serviceWorkerRegistration = null;
// Initialize AudioContext for mobile audio playback
function initializeAudioContext() {
try {
if (!audioContext) {
// Create AudioContext with compatibility
var AudioContextClass = window.AudioContext || window.webkitAudioContext;
audioContext = new AudioContextClass();
console.log('AudioContext created, state:', audioContext.state);
}
// Resume AudioContext if suspended (required on mobile)
if (audioContext.state === 'suspended') {
audioContext.resume().then(function() {
console.log('AudioContext resumed successfully');
}).catch(function(err) {
console.error('Failed to resume AudioContext:', err);
});
}
return true;
} catch (error) {
console.error('Failed to initialize AudioContext:', error);
return false;
}
}
// Create and setup ringtone audio element
function setupRingtone() {
if (!ringtoneAudio) {
ringtoneAudio = new Audio();
// Use a simple sine wave tone or default ringtone
// For now, we'll use a data URI for a simple beep tone
ringtoneAudio.loop = true;
ringtoneAudio.volume = 0.7;
// Create a simple ringtone using Web Audio API
createRingtone();
}
}
// Create ringtone using Web Audio API for better mobile support
function createRingtone() {
// Use a simple base64-encoded beep tone (short MP3)
// This is a simple 1-second beep at 800Hz
// You can replace this with a custom ringtone file URL if you upload one
// For now, use a simple approach: HTML5 Audio with error fallback
// Note: On mobile, audio playback may be restricted, so we rely heavily on vibration
var ringtoneUrl = '<?php echo plugins_url('assets/sounds/ringtone.mp3', dirname(__FILE__)); ?>';
// Try to load the ringtone file
ringtoneAudio.src = ringtoneUrl;
// Fallback: if ringtone file fails to load, we'll just use vibration
ringtoneAudio.addEventListener('error', function(e) {
console.log('Ringtone file not found (this is normal), using vibration only for mobile');
// Don't show error - vibration is sufficient for mobile
}, { once: true });
// Try to preload
ringtoneAudio.load();
}
// Play ringtone for incoming call
function playRingtone() {
try {
// Initialize AudioContext on user interaction
initializeAudioContext();
if (ringtoneAudio) {
var playPromise = ringtoneAudio.play();
if (playPromise !== undefined) {
playPromise.then(function() {
console.log('Ringtone playing');
}).catch(function(error) {
console.error('Ringtone play failed:', error);
// Fallback: vibrate on mobile
vibrateDevice([300, 200, 300, 200, 300]);
});
}
}
// Always vibrate on mobile for better notification
vibrateDevice([300, 200, 300, 200, 300]);
} catch (error) {
console.error('Error playing ringtone:', error);
}
}
// Stop ringtone
function stopRingtone() {
try {
if (ringtoneAudio) {
ringtoneAudio.pause();
ringtoneAudio.currentTime = 0;
}
} catch (error) {
console.error('Error stopping ringtone:', error);
}
}
// Vibrate device (mobile)
function vibrateDevice(pattern) {
if ('vibrate' in navigator) {
navigator.vibrate(pattern);
}
}
// Register service worker for PWA notifications
function registerServiceWorker() {
if ('serviceWorker' in navigator) {
var swPath = '<?php echo plugins_url('assets/js/twp-service-worker.js', dirname(__FILE__)); ?>';
navigator.serviceWorker.register(swPath)
.then(function(registration) {
console.log('Service Worker registered:', registration);
serviceWorkerRegistration = registration;
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission().then(function(permission) {
console.log('Notification permission:', permission);
});
}
})
.catch(function(error) {
console.error('Service Worker registration failed:', error);
});
}
}
// Send notification via service worker
function sendIncomingCallNotification(callerNumber) {
// Try browser notification first
if ('Notification' in window && Notification.permission === 'granted') {
if (serviceWorkerRegistration && serviceWorkerRegistration.active) {
serviceWorkerRegistration.active.postMessage({
type: 'SHOW_NOTIFICATION',
title: 'Incoming Call',
body: 'Call from ' + (callerNumber || 'Unknown Number'),
icon: '<?php echo plugins_url('assets/images/phone-icon.png', dirname(__FILE__)); ?>',
tag: 'incoming-call',
requireInteraction: true
});
} else {
// Fallback: show notification directly
new Notification('Incoming Call', {
body: 'Call from ' + (callerNumber || 'Unknown Number'),
icon: '<?php echo plugins_url('assets/images/phone-icon.png', dirname(__FILE__)); ?>',
tag: 'incoming-call',
requireInteraction: true
});
}
}
}
// Monitor page visibility for background call handling
function setupPageVisibility() {
document.addEventListener('visibilitychange', function() {
isPageVisible = !document.hidden;
console.log('Page visibility changed:', isPageVisible ? 'visible' : 'hidden');
// If page becomes visible, resume audio context
if (isPageVisible && audioContext) {
initializeAudioContext();
}
});
}
// Update device connection status in UI
function updateConnectionStatus(state) {
deviceConnectionState = state;
var statusText = '';
var statusColor = '';
switch(state) {
case 'connected':
statusText = 'Connected';
statusColor = '#4CAF50';
break;
case 'connecting':
statusText = 'Connecting...';
statusColor = '#FF9800';
break;
case 'disconnected':
statusText = 'Disconnected';
statusColor = '#f44336';
break;
default:
statusText = 'Unknown';
statusColor = '#999';
}
// Update status indicator (we'll add this to the UI)
$('#device-connection-status').text(statusText).css('color', statusColor);
}
// Wait for SDK to load
function waitForTwilioSDK(callback) {
@@ -7450,6 +7675,18 @@ class TWP_Admin {
// Initialize the browser phone
function initializeBrowserPhone() {
$('#phone-status').text('Initializing...');
updateConnectionStatus('connecting');
// Initialize audio and PWA features
setupRingtone();
registerServiceWorker();
setupPageVisibility();
// Initialize AudioContext on first user interaction
$(document).one('click touchstart', function() {
console.log('User interaction detected, initializing AudioContext');
initializeAudioContext();
});
// Wait for SDK before proceeding
waitForTwilioSDK(function() {
@@ -7468,9 +7705,11 @@ class TWP_Admin {
// WordPress wp_send_json_error sends the error message as response.data
var errorMsg = response.data || response.error || 'Unknown error';
showError('Failed to initialize: ' + errorMsg);
updateConnectionStatus('disconnected');
}
}).fail(function() {
showError('Failed to connect to server');
updateConnectionStatus('disconnected');
});
});
}
@@ -7518,36 +7757,77 @@ class TWP_Admin {
throw new Error('Twilio Voice SDK not loaded');
}
console.log('Setting up Twilio Device...');
updateConnectionStatus('connecting');
// Request media permissions before setting up device
const hasPermissions = await requestMediaPermissions();
if (!hasPermissions) {
updateConnectionStatus('disconnected');
return; // Stop setup if permissions denied
}
// Clean up existing device if any
if (device) {
console.log('Destroying existing device');
await device.destroy();
}
// Detect if we're on Android/mobile for specific settings
var isAndroid = /Android/i.test(navigator.userAgent);
var isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
console.log('Device detection - Android:', isAndroid, 'Mobile:', isMobile);
// Android-specific audio constraints for better WebRTC performance
var audioConstraints = {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
};
// Additional Android-specific settings
if (isAndroid) {
audioConstraints.googEchoCancellation = true;
audioConstraints.googNoiseSuppression = true;
audioConstraints.googAutoGainControl = true;
audioConstraints.googHighpassFilter = true;
}
// Setup Twilio Voice SDK v2 Device
// Note: Voice SDK v2 uses Twilio.Device directly, not Twilio.Voice.Device
device = new Twilio.Device(token, {
logLevel: 1, // 0 = TRACE, 1 = DEBUG
codecPreferences: ['opus', 'pcmu'],
edge: 'sydney' // Or closest edge location
edge: '<?php echo esc_js(get_option('twp_twilio_edge', 'roaming')); ?>',
enableIceRestart: true, // Important for mobile network switching
audioConstraints: audioConstraints,
maxCallSignalingTimeoutMs: 30000, // 30 seconds timeout for mobile
closeProtection: true // Warn before closing during call
});
console.log('Twilio Device created with audio constraints:', audioConstraints);
// Set up event handlers BEFORE registering
// Device registered and ready
device.on('registered', function() {
console.log('Device registered successfully');
$('#phone-status').text('Ready').css('color', '#4CAF50');
$('#call-btn').prop('disabled', false);
updateConnectionStatus('connected');
});
// Device unregistered
device.on('unregistered', function() {
console.log('Device unregistered');
updateConnectionStatus('disconnected');
});
// Handle errors
device.on('error', function(error) {
console.error('Twilio Device Error:', error);
console.error('Error code:', error.code, 'Message:', error.message);
updateConnectionStatus('disconnected');
var errorMsg = error.message || error.toString();
@@ -7560,6 +7840,15 @@ class TWP_Admin {
errorMsg = 'Token error: ' + errorMsg + ' - The page will automatically try to refresh the token.';
// Try to reinitialize after token error
setTimeout(initializeBrowserPhone, 5000);
} else if (errorMsg.includes('31005') || errorMsg.includes('Connection error')) {
errorMsg = 'Connection error: Check your internet connection. If on mobile, try switching between WiFi and cellular data.';
// Retry connection
setTimeout(function() {
if (device) {
console.log('Attempting to reconnect device...');
device.register();
}
}, 3000);
}
showError(errorMsg);
@@ -7567,16 +7856,32 @@ class TWP_Admin {
// Handle incoming calls
device.on('incoming', function(call) {
console.log('Incoming call from:', call.parameters.From);
console.log('Call SID:', call.parameters.CallSid);
console.log('Device connection state:', deviceConnectionState);
currentCall = call;
var callerNumber = call.parameters.From || 'Unknown Number';
$('#phone-status').text('Incoming Call').css('color', '#FF9800');
$('#phone-number-display').text(call.parameters.From || 'Unknown Number');
$('#phone-number-display').text(callerNumber);
$('#call-btn').hide();
$('#answer-btn').show();
// Play ringtone and show notification
playRingtone();
// If page is in background, send notification
if (!isPageVisible) {
console.log('Page in background, sending notification');
sendIncomingCallNotification(callerNumber);
}
// Setup call event handlers
setupCallHandlers(call);
if ($('#auto-answer').is(':checked')) {
console.log('Auto-answer enabled, accepting call');
call.accept();
}
});
@@ -7599,6 +7904,8 @@ class TWP_Admin {
function setupCallHandlers(call) {
// Call accepted/connected
call.on('accept', function() {
console.log('Call accepted and connected');
stopRingtone();
$('#phone-status').text('Connected').css('color', '#2196F3');
$('#call-btn').hide();
$('#answer-btn').hide();
@@ -7610,6 +7917,8 @@ class TWP_Admin {
// Call disconnected
call.on('disconnect', function() {
console.log('Call disconnected');
stopRingtone();
currentCall = null;
$('#phone-status').text('Ready').css('color', '#4CAF50');
$('#hangup-btn').hide();
@@ -7627,6 +7936,8 @@ class TWP_Admin {
// Call rejected
call.on('reject', function() {
console.log('Call rejected');
stopRingtone();
currentCall = null;
$('#phone-status').text('Ready').css('color', '#4CAF50');
$('#answer-btn').hide();
@@ -7635,6 +7946,8 @@ class TWP_Admin {
// Call cancelled (by caller before answer)
call.on('cancel', function() {
console.log('Call cancelled by caller');
stopRingtone();
currentCall = null;
$('#phone-status').text('Missed Call').css('color', '#FF9800');
$('#answer-btn').hide();
@@ -7643,6 +7956,26 @@ class TWP_Admin {
$('#phone-status').text('Ready').css('color', '#4CAF50');
}, 3000);
});
// Call error
call.on('error', function(error) {
console.error('Call error:', error);
console.error('Error code:', error.code, 'Message:', error.message);
stopRingtone();
var errorMsg = error.message || error.toString();
// Specific error handling for Android/mobile
if (error.code === 31005) {
errorMsg = 'Connection failed: Check your network connection. Try switching between WiFi and cellular data.';
} else if (error.code === 31201 || error.code === 31204) {
errorMsg = 'Call setup failed: Please try again. If the problem persists, refresh the page.';
} else if (error.code === 31208) {
errorMsg = 'Media connection failed: Check microphone permissions and try again.';
}
showError('Call error: ' + errorMsg);
});
}
function refreshToken() {
@@ -7753,11 +8086,15 @@ class TWP_Admin {
$('#caller-id-select').html('<option value="">Error loading numbers</option>');
});
// Dialpad functionality
$('.dialpad-btn').on('click', function() {
// Dialpad functionality (support both click and touch events)
$('.dialpad-btn').on('click touchend', function(e) {
e.preventDefault(); // Prevent duplicate events
var digit = $(this).data('digit');
var currentVal = $('#phone-number-input').val();
$('#phone-number-input').val(currentVal + digit);
// Initialize AudioContext on user interaction (mobile requirement)
initializeAudioContext();
});
// Call button
@@ -7818,9 +8155,46 @@ class TWP_Admin {
// Answer button
$('#answer-btn').on('click', function() {
console.log('Answer button clicked');
console.log('Device connection state:', deviceConnectionState);
console.log('Current call:', currentCall);
if (!currentCall) {
console.error('No current call to answer');
showError('No incoming call to answer');
return;
}
// Check device connection state
if (deviceConnectionState !== 'connected') {
console.error('Device not connected, state:', deviceConnectionState);
showError('Phone not connected. Reconnecting...');
// Try to reconnect
if (device) {
device.register().then(function() {
console.log('Device reconnected, answering call');
if (currentCall) {
currentCall.accept();
}
}).catch(function(err) {
console.error('Failed to reconnect device:', err);
showError('Failed to reconnect. Please refresh the page.');
});
}
return;
}
// Initialize AudioContext before accepting (important for mobile)
initializeAudioContext();
try {
console.log('Accepting call...');
currentCall.accept();
} catch (error) {
console.error('Error accepting call:', error);
showError('Failed to answer call: ' + error.message);
}
});
// Mute button

42
assets/images/README.md Normal file
View File

@@ -0,0 +1,42 @@
# Browser Phone Images
## Required Images for PWA Notifications
Place the following image files in this directory for browser push notifications:
### phone-icon.png
- **Purpose**: Main notification icon
- **Size**: 192x192 pixels (recommended)
- **Format**: PNG with transparency
- **Usage**: Shown in browser notifications for incoming calls
### phone-badge.png
- **Purpose**: Small badge icon for notifications
- **Size**: 96x96 pixels or 72x72 pixels
- **Format**: PNG with transparency
- **Usage**: Displayed in the notification badge area (Android)
## Fallback Behavior
If these images are not present, the browser will:
- Use a default browser notification icon
- Still display text-based notifications
- Functionality will not be affected
## Image Creation Tips
- Use a simple, recognizable phone icon
- Ensure good contrast for visibility
- Test on both light and dark backgrounds
- Transparent backgrounds work best
- Use a consistent color scheme with your brand
## Example Resources
- Google Material Icons: https://fonts.google.com/icons
- Font Awesome: https://fontawesome.com/
- Flaticon: https://www.flaticon.com/
- Or create custom icons using tools like:
- Figma
- Adobe Illustrator
- Inkscape (free)

View File

@@ -72,7 +72,23 @@ self.addEventListener('push', function(event) {
// Handle messages from the main script
self.addEventListener('message', function(event) {
console.log('TWP Service Worker: Message received', event.data);
if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
self.registration.showNotification(event.data.title, event.data.options);
const options = {
body: event.data.body || 'You have a new call waiting',
icon: event.data.icon || '/wp-content/plugins/twilio-wp-plugin/assets/images/phone-icon.png',
badge: '/wp-content/plugins/twilio-wp-plugin/assets/images/phone-badge.png',
vibrate: [300, 200, 300, 200, 300],
tag: event.data.tag || 'incoming-call',
requireInteraction: event.data.requireInteraction !== undefined ? event.data.requireInteraction : true,
data: event.data.data || {}
};
console.log('TWP Service Worker: Showing notification', event.data.title, options);
event.waitUntil(
self.registration.showNotification(event.data.title || 'Incoming Call', options)
);
}
});

36
assets/sounds/README.md Normal file
View File

@@ -0,0 +1,36 @@
# Ringtone Audio Files
## Custom Ringtone
To add a custom ringtone for incoming calls in the browser phone:
1. Place your ringtone audio file in this directory
2. Name it: `ringtone.mp3`
3. Recommended format: MP3, under 5 seconds, loopable
## Fallback Behavior
If no ringtone file is present, the browser phone will:
- Use device vibration on mobile devices (Android, iOS)
- Rely on browser notifications to alert users
- Display visual indicators in the browser interface
## Supported Audio Formats
- **MP3** (recommended) - Best compatibility across browsers
- **OGG** - Good for Firefox, Chrome
- **WAV** - Larger file size but universal support
## File Requirements
- Maximum file size: 100KB recommended
- Duration: 1-5 seconds (will loop)
- Sample rate: 44.1kHz or 48kHz
- Bitrate: 128kbps or higher
## Example Ringtone Sources
You can find free ringtone files at:
- https://freesound.org/
- https://incompetech.com/
- Or create your own using Audacity or similar tools

View File

@@ -41,11 +41,26 @@ class TWP_Twilio_API {
* 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');
// Check for SDK autoloader - external location first (survives plugin updates)
$autoloader_path = null;
// Priority 1: External SDK location (recommended)
$external_autoloader = TWP_EXTERNAL_SDK_DIR . 'autoload.php';
if (file_exists($external_autoloader)) {
$autoloader_path = $external_autoloader;
}
// Priority 2: Internal vendor directory (fallback)
if (!$autoloader_path) {
$internal_autoloader = TWP_PLUGIN_DIR . 'vendor/autoload.php';
if (file_exists($internal_autoloader)) {
$autoloader_path = $internal_autoloader;
}
}
if (!$autoloader_path) {
error_log('TWP Plugin: Autoloader not found. Checked: ' . $external_autoloader . ' and ' . TWP_PLUGIN_DIR . 'vendor/autoload.php');
throw new Exception('Twilio SDK not found. Please run: ./install-twilio-sdk-external.sh');
}
// Load the autoloader

View File

@@ -9,9 +9,25 @@ class TWP_Webhooks {
*/
public function __construct() {
// Load Twilio SDK if not already loaded
// Check external location first (survives plugin updates), then internal
if (!class_exists('\Twilio\Rest\Client')) {
$autoloader_path = plugin_dir_path(dirname(__FILE__)) . 'vendor/autoload.php';
if (file_exists($autoloader_path)) {
$autoloader_path = null;
// Priority 1: External SDK location
$external_autoloader = dirname(dirname(plugin_dir_path(dirname(__FILE__)))) . '/twilio-sdk/autoload.php';
if (file_exists($external_autoloader)) {
$autoloader_path = $external_autoloader;
}
// Priority 2: Internal vendor directory
if (!$autoloader_path) {
$internal_autoloader = plugin_dir_path(dirname(__FILE__)) . 'vendor/autoload.php';
if (file_exists($internal_autoloader)) {
$autoloader_path = $internal_autoloader;
}
}
if ($autoloader_path) {
require_once $autoloader_path;
}
}

158
install-twilio-sdk-external.sh Executable file
View File

@@ -0,0 +1,158 @@
#!/bin/bash
# Script to install Twilio PHP SDK to an external location
# This prevents SDK from being deleted when WordPress updates the plugin
#
# Location: wp-content/twilio-sdk/ (outside plugin folder)
echo "Installing Twilio PHP SDK v8.7.0 to external location..."
echo "This will install the SDK outside the plugin folder to survive plugin updates."
# 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
# Get the script directory (plugin directory)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Calculate wp-content directory (two levels up from plugin)
# Plugin is at: wp-content/plugins/twilio-wp-plugin/
# We want: wp-content/twilio-sdk/
WP_CONTENT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
SDK_DIR="$WP_CONTENT_DIR/twilio-sdk"
echo "Plugin directory: $SCRIPT_DIR"
echo "SDK will be installed to: $SDK_DIR"
# Create SDK directory
mkdir -p "$SDK_DIR/twilio/sdk"
# Download the latest release (8.7.0)
echo "Downloading Twilio SDK from GitHub..."
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
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"
rm -rf "$TEMP_DIR"
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"
rm -rf "$TEMP_DIR"
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"
rm -rf "$TEMP_DIR"
exit 1
fi
# Remove existing SDK if it exists
if [ -d "$SDK_DIR/twilio/sdk" ]; then
echo "Removing existing SDK installation..."
rm -rf "$SDK_DIR/twilio/sdk"
mkdir -p "$SDK_DIR/twilio/sdk"
fi
# Move the entire src directory to be the sdk
echo "Installing SDK files..."
if ! mv twilio-php-8.7.0/src/* "$SDK_DIR/twilio/sdk/"; then
echo "ERROR: Failed to move SDK files"
rm -rf "$TEMP_DIR"
exit 1
fi
# Create a comprehensive autoloader
cat > "$SDK_DIR/autoload.php" << 'EOF'
<?php
/**
* Twilio SDK v8.7.0 Autoloader (External Installation)
* This file loads the Twilio PHP SDK classes
*
* Location: wp-content/twilio-sdk/autoload.php
* This location survives WordPress plugin updates
*/
// 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
// The SDK structure is: twilio/sdk/Twilio/Rest/Client.php for Twilio\Rest\Client
$file = __DIR__ . '/twilio/sdk/' . str_replace('\\', '/', $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/Twilio/Rest/Client.php',
__DIR__ . '/twilio/sdk/Twilio/TwiML/VoiceResponse.php',
__DIR__ . '/twilio/sdk/Twilio/Exceptions/TwilioException.php',
__DIR__ . '/twilio/sdk/Twilio/Security/RequestValidator.php'
];
foreach ($essential_classes as $class_file) {
if (file_exists($class_file)) {
require_once $class_file;
}
}
}
EOF
# Clean up temp directory
cd "$SCRIPT_DIR"
rm -rf "$TEMP_DIR"
# Verify installation
echo ""
echo "Verifying installation..."
if [ -f "$SDK_DIR/autoload.php" ] && [ -d "$SDK_DIR/twilio/sdk" ]; then
echo "=============================================="
echo "Twilio SDK v8.7.0 installed successfully!"
echo "=============================================="
echo ""
echo "Installation location: $SDK_DIR"
echo ""
echo "This SDK is installed OUTSIDE the plugin folder,"
echo "so it will NOT be deleted when WordPress updates the plugin."
echo ""
echo "The plugin will automatically detect this external SDK."
else
echo "Installation failed - files not found"
exit 1
fi

View File

@@ -79,8 +79,8 @@ if (!defined('TWILIO_AUTOLOADER_REGISTERED')) {
}
// Convert class name to file path
$relative_class = substr($class, 7); // Remove 'Twilio\'
$file = __DIR__ . '/twilio/sdk/' . str_replace('\\', '/', $relative_class) . '.php';
// The SDK structure is: twilio/sdk/Twilio/Rest/Client.php for Twilio\Rest\Client
$file = __DIR__ . '/twilio/sdk/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) {
require_once $file;
@@ -98,10 +98,10 @@ if (!defined('TWILIO_AUTOLOADER_REGISTERED')) {
// 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'
__DIR__ . '/twilio/sdk/Twilio/Rest/Client.php',
__DIR__ . '/twilio/sdk/Twilio/TwiML/VoiceResponse.php',
__DIR__ . '/twilio/sdk/Twilio/Exceptions/TwilioException.php',
__DIR__ . '/twilio/sdk/Twilio/Security/RequestValidator.php'
];
foreach ($essential_classes as $class_file) {

View File

@@ -20,6 +20,8 @@ define('TWP_DB_VERSION', '1.6.2'); // Track database version separately
define('TWP_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('TWP_PLUGIN_URL', plugin_dir_url(__FILE__));
define('TWP_PLUGIN_BASENAME', plugin_basename(__FILE__));
// External SDK location - survives plugin updates (wp-content/twilio-sdk/)
define('TWP_EXTERNAL_SDK_DIR', dirname(dirname(TWP_PLUGIN_DIR)) . '/twilio-sdk/');
/**
* Plugin activation hook
@@ -31,17 +33,27 @@ function twp_activate() {
/**
* Check if Twilio SDK is installed and show admin notice if not
* Checks external location first (survives plugin updates), then internal fallback
*/
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;
// Priority 1: Check external SDK location (survives plugin updates)
$external_autoloader = TWP_EXTERNAL_SDK_DIR . 'autoload.php';
if (file_exists($external_autoloader)) {
require_once $external_autoloader;
$sdk_installed = class_exists('Twilio\Rest\Client');
}
// Priority 2: Fall back to internal vendor directory
if (!$sdk_installed) {
$internal_autoloader = TWP_PLUGIN_DIR . 'vendor/autoload.php';
if (file_exists($internal_autoloader)) {
require_once $internal_autoloader;
$sdk_installed = class_exists('Twilio\Rest\Client');
}
}
if (!$sdk_installed) {
add_action('admin_notices', 'twp_sdk_missing_notice');
}
@@ -55,10 +67,12 @@ 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>
<p><strong>Recommended:</strong> Install SDK to external location (survives plugin updates):</p>
<code>chmod +x install-twilio-sdk-external.sh && ./install-twilio-sdk-external.sh</code>
<p style="margin-top: 10px;"><strong>Alternative:</strong> Install SDK inside plugin folder:</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>
<p style="margin-top: 10px;"><em>Plugin path: <?php echo esc_html(TWP_PLUGIN_DIR); ?></em></p>
<p><em>External SDK path: <?php echo esc_html(TWP_EXTERNAL_SDK_DIR); ?></em></p>
</div>
<?php
}
@@ -126,6 +140,52 @@ function twp_deactivate() {
register_activation_hook(__FILE__, 'twp_activate');
register_deactivation_hook(__FILE__, 'twp_deactivate');
/**
* Check SDK status after plugin updates
* Shows warning if SDK was deleted during update and external SDK not available
*/
function twp_check_sdk_after_update($upgrader_object, $options) {
// Only run for plugin updates
if ($options['action'] !== 'update' || $options['type'] !== 'plugin') {
return;
}
// Check if this plugin was updated
$updated_plugins = isset($options['plugins']) ? $options['plugins'] : array();
if (!in_array(TWP_PLUGIN_BASENAME, $updated_plugins)) {
return;
}
// Check if SDK is available
$external_sdk = file_exists(TWP_EXTERNAL_SDK_DIR . 'autoload.php');
$internal_sdk = file_exists(TWP_PLUGIN_DIR . 'vendor/autoload.php');
if (!$external_sdk && !$internal_sdk) {
// Set a transient to show warning on next admin page load
set_transient('twp_sdk_update_warning', true, 60 * 5);
}
}
add_action('upgrader_process_complete', 'twp_check_sdk_after_update', 10, 2);
/**
* Show SDK update warning
*/
function twp_show_sdk_update_warning() {
if (get_transient('twp_sdk_update_warning')) {
delete_transient('twp_sdk_update_warning');
?>
<div class="notice notice-warning is-dismissible">
<h3>Twilio WordPress Plugin - SDK Reinstall Required</h3>
<p><strong>The plugin was updated and the Twilio SDK needs to be reinstalled.</strong></p>
<p>To prevent this in the future, install the SDK to the external location:</p>
<code>cd <?php echo esc_html(TWP_PLUGIN_DIR); ?> && ./install-twilio-sdk-external.sh</code>
<p style="margin-top: 10px;">The external SDK at <code><?php echo esc_html(TWP_EXTERNAL_SDK_DIR); ?></code> survives plugin updates.</p>
</div>
<?php
}
}
add_action('admin_notices', 'twp_show_sdk_update_warning');
/**
* Core plugin class
*/