code push
This commit is contained in:
@@ -1,276 +1,474 @@
|
||||
<?php
|
||||
/**
|
||||
* Twilio API integration class
|
||||
* Twilio API integration class using official Twilio PHP SDK
|
||||
*/
|
||||
class TWP_Twilio_API {
|
||||
|
||||
private $account_sid;
|
||||
private $auth_token;
|
||||
private $client;
|
||||
private $phone_number;
|
||||
private $api_base = 'https://api.twilio.com/2010-04-01';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->account_sid = get_option('twp_twilio_account_sid');
|
||||
$this->auth_token = get_option('twp_twilio_auth_token');
|
||||
$this->init_sdk_client();
|
||||
$this->phone_number = get_option('twp_twilio_phone_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Twilio SDK client
|
||||
*/
|
||||
private function init_sdk_client() {
|
||||
// Check if autoloader exists
|
||||
$autoloader_path = TWP_PLUGIN_DIR . 'vendor/autoload.php';
|
||||
if (!file_exists($autoloader_path)) {
|
||||
error_log('TWP Plugin: Autoloader not found at: ' . $autoloader_path);
|
||||
throw new Exception('Twilio SDK not found. Please run: ./install-twilio-sdk.sh');
|
||||
}
|
||||
|
||||
// Load the autoloader
|
||||
require_once $autoloader_path;
|
||||
|
||||
// Give more detailed error information
|
||||
if (!class_exists('Twilio\Rest\Client')) {
|
||||
$sdk_path = TWP_PLUGIN_DIR . 'vendor/twilio/sdk';
|
||||
$client_file = $sdk_path . '/Rest/Client.php';
|
||||
|
||||
error_log('TWP Plugin: Twilio SDK classes not found.');
|
||||
error_log('TWP Plugin: Looking for SDK at: ' . $sdk_path);
|
||||
error_log('TWP Plugin: Client.php exists: ' . (file_exists($client_file) ? 'YES' : 'NO'));
|
||||
error_log('TWP Plugin: SDK directory contents: ' . print_r(scandir($sdk_path), true));
|
||||
|
||||
throw new Exception('Twilio SDK classes not available. Please reinstall with: ./install-twilio-sdk.sh');
|
||||
}
|
||||
|
||||
$account_sid = get_option('twp_twilio_account_sid');
|
||||
$auth_token = get_option('twp_twilio_auth_token');
|
||||
|
||||
if (empty($account_sid) || empty($auth_token)) {
|
||||
throw new Exception('Twilio credentials not configured. Please check your WordPress admin settings.');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->client = new \Twilio\Rest\Client($account_sid, $auth_token);
|
||||
error_log('TWP Plugin: Twilio SDK initialized successfully');
|
||||
} catch (Exception $e) {
|
||||
error_log('TWP Plugin: Failed to initialize Twilio client: ' . $e->getMessage());
|
||||
throw new Exception('Failed to initialize Twilio SDK: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a phone call
|
||||
*/
|
||||
public function make_call($to_number, $twiml_url, $status_callback = null, $from_number = null) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/Calls.json';
|
||||
|
||||
$data = array(
|
||||
'To' => $to_number,
|
||||
'From' => $from_number ?: $this->phone_number,
|
||||
'Url' => $twiml_url
|
||||
);
|
||||
|
||||
if ($status_callback) {
|
||||
$data['StatusCallback'] = $status_callback;
|
||||
$data['StatusCallbackEvent'] = array('initiated', 'ringing', 'answered', 'completed');
|
||||
try {
|
||||
$params = [
|
||||
'url' => $twiml_url,
|
||||
'from' => $from_number ?: $this->phone_number,
|
||||
'to' => $to_number
|
||||
];
|
||||
|
||||
if ($status_callback) {
|
||||
$params['statusCallback'] = $status_callback;
|
||||
$params['statusCallbackEvent'] = ['initiated', 'ringing', 'answered', 'completed'];
|
||||
}
|
||||
|
||||
$call = $this->client->calls->create(
|
||||
$to_number,
|
||||
$from_number ?: $this->phone_number,
|
||||
$params
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $call->sid,
|
||||
'status' => $call->status,
|
||||
'from' => $call->from,
|
||||
'to' => $call->to,
|
||||
'direction' => $call->direction,
|
||||
'price' => $call->price,
|
||||
'priceUnit' => $call->priceUnit
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
|
||||
return $this->make_request('POST', $url, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward a call
|
||||
*/
|
||||
public function forward_call($call_sid, $to_number) {
|
||||
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||
$dial = $twiml->addChild('Dial');
|
||||
$dial->addChild('Number', $to_number);
|
||||
|
||||
return $this->update_call($call_sid, array(
|
||||
'Twiml' => $twiml->asXML()
|
||||
));
|
||||
try {
|
||||
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||||
$twiml->dial($to_number);
|
||||
|
||||
$call = $this->client->calls($call_sid)->update([
|
||||
'twiml' => $twiml->asXML()
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $call->sid,
|
||||
'status' => $call->status
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an active call
|
||||
*/
|
||||
public function update_call($call_sid, $params) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/Calls/' . $call_sid . '.json';
|
||||
return $this->make_request('POST', $url, $params);
|
||||
try {
|
||||
$call = $this->client->calls($call_sid)->update($params);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $call->sid,
|
||||
'status' => $call->status,
|
||||
'from' => $call->from,
|
||||
'to' => $call->to
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get call details
|
||||
*/
|
||||
public function get_call($call_sid) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/Calls/' . $call_sid . '.json';
|
||||
return $this->make_request('GET', $url);
|
||||
try {
|
||||
$call = $this->client->calls($call_sid)->fetch();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $call->sid,
|
||||
'status' => $call->status,
|
||||
'from' => $call->from,
|
||||
'to' => $call->to,
|
||||
'direction' => $call->direction,
|
||||
'duration' => $call->duration,
|
||||
'price' => $call->price,
|
||||
'priceUnit' => $call->priceUnit
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create TwiML for queue
|
||||
*/
|
||||
public function create_queue_twiml($queue_name, $wait_url = null, $wait_message = null) {
|
||||
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||
|
||||
if ($wait_message) {
|
||||
$say = $twiml->addChild('Say', $wait_message);
|
||||
$say->addAttribute('voice', 'alice');
|
||||
try {
|
||||
$response = new \Twilio\TwiML\VoiceResponse();
|
||||
|
||||
if ($wait_message) {
|
||||
$response->say($wait_message, ['voice' => 'alice']);
|
||||
}
|
||||
|
||||
$enqueue = $response->enqueue($queue_name);
|
||||
|
||||
if ($wait_url) {
|
||||
$enqueue->waitUrl($wait_url);
|
||||
}
|
||||
|
||||
return $response->asXML();
|
||||
} catch (Exception $e) {
|
||||
error_log('TWP Plugin: Failed to create queue TwiML: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$enqueue = $twiml->addChild('Enqueue', $queue_name);
|
||||
|
||||
if ($wait_url) {
|
||||
$enqueue->addAttribute('waitUrl', $wait_url);
|
||||
}
|
||||
|
||||
return $twiml->asXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create TwiML for IVR menu
|
||||
*/
|
||||
public function create_ivr_twiml($message, $options = array()) {
|
||||
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||
|
||||
$gather = $twiml->addChild('Gather');
|
||||
$gather->addAttribute('numDigits', '1');
|
||||
$gather->addAttribute('timeout', '10');
|
||||
|
||||
if (!empty($options['action_url'])) {
|
||||
$gather->addAttribute('action', $options['action_url']);
|
||||
try {
|
||||
$response = new \Twilio\TwiML\VoiceResponse();
|
||||
|
||||
$gather = $response->gather([
|
||||
'numDigits' => 1,
|
||||
'timeout' => 10,
|
||||
'action' => isset($options['action_url']) ? $options['action_url'] : null
|
||||
]);
|
||||
|
||||
$gather->say($message, ['voice' => 'alice']);
|
||||
|
||||
if (!empty($options['no_input_message'])) {
|
||||
$response->say($options['no_input_message'], ['voice' => 'alice']);
|
||||
}
|
||||
|
||||
return $response->asXML();
|
||||
} catch (Exception $e) {
|
||||
error_log('TWP Plugin: Failed to create IVR TwiML: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$say = $gather->addChild('Say', $message);
|
||||
$say->addAttribute('voice', 'alice');
|
||||
|
||||
// Fallback if no input
|
||||
if (!empty($options['no_input_message'])) {
|
||||
$say_fallback = $twiml->addChild('Say', $options['no_input_message']);
|
||||
$say_fallback->addAttribute('voice', 'alice');
|
||||
}
|
||||
|
||||
return $twiml->asXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send SMS
|
||||
*/
|
||||
public function send_sms($to_number, $message) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/Messages.json';
|
||||
|
||||
$data = array(
|
||||
'To' => $to_number,
|
||||
'From' => $this->phone_number,
|
||||
'Body' => $message
|
||||
);
|
||||
|
||||
return $this->make_request('POST', $url, $data);
|
||||
public function send_sms($to_number, $message, $from_number = null) {
|
||||
try {
|
||||
$sms = $this->client->messages->create(
|
||||
$to_number,
|
||||
[
|
||||
'from' => $from_number ?: $this->phone_number,
|
||||
'body' => $message
|
||||
]
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $sms->sid,
|
||||
'status' => $sms->status,
|
||||
'from' => $sms->from,
|
||||
'to' => $sms->to,
|
||||
'body' => $sms->body,
|
||||
'price' => $sms->price,
|
||||
'priceUnit' => $sms->priceUnit
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available phone numbers
|
||||
*/
|
||||
public function get_phone_numbers() {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers.json';
|
||||
return $this->make_request('GET', $url);
|
||||
try {
|
||||
$numbers = $this->client->incomingPhoneNumbers->read([], 50);
|
||||
|
||||
$numbers_data = [];
|
||||
foreach ($numbers as $number) {
|
||||
$numbers_data[] = [
|
||||
'sid' => $number->sid ?: '',
|
||||
'phoneNumber' => $number->phoneNumber ?: '',
|
||||
'friendlyName' => $number->friendlyName ?: $number->phoneNumber ?: 'Unknown',
|
||||
'voiceUrl' => $number->voiceUrl ?: '',
|
||||
'smsUrl' => $number->smsUrl ?: '',
|
||||
'capabilities' => [
|
||||
'voice' => $number->capabilities ? (bool)$number->capabilities->getVoice() : false,
|
||||
'sms' => $number->capabilities ? (bool)$number->capabilities->getSms() : false,
|
||||
'mms' => $number->capabilities ? (bool)$number->capabilities->getMms() : false
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'incoming_phone_numbers' => $numbers_data
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for available phone numbers
|
||||
*/
|
||||
public function search_available_numbers($country_code = 'US', $area_code = null, $contains = null, $limit = 20) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/AvailablePhoneNumbers/' . $country_code . '/Local.json';
|
||||
|
||||
$params = array('Limit' => $limit);
|
||||
|
||||
if ($area_code) {
|
||||
$params['AreaCode'] = $area_code;
|
||||
try {
|
||||
$params = ['limit' => $limit];
|
||||
|
||||
if ($area_code) {
|
||||
$params['areaCode'] = $area_code;
|
||||
}
|
||||
|
||||
if ($contains) {
|
||||
$params['contains'] = $contains;
|
||||
}
|
||||
|
||||
$numbers = $this->client->availablePhoneNumbers($country_code)
|
||||
->local
|
||||
->read($params, $limit);
|
||||
|
||||
$numbers_data = [];
|
||||
foreach ($numbers as $number) {
|
||||
$numbers_data[] = [
|
||||
'phoneNumber' => $number->phoneNumber ?: '',
|
||||
'friendlyName' => $number->friendlyName ?: $number->phoneNumber ?: 'Available Number',
|
||||
'locality' => $number->locality ?: '',
|
||||
'region' => $number->region ?: '',
|
||||
'postalCode' => $number->postalCode ?: '',
|
||||
'capabilities' => [
|
||||
'voice' => $number->capabilities ? (bool)$number->capabilities->getVoice() : false,
|
||||
'sms' => $number->capabilities ? (bool)$number->capabilities->getSms() : false,
|
||||
'mms' => $number->capabilities ? (bool)$number->capabilities->getMms() : false
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'available_phone_numbers' => $numbers_data
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
|
||||
if ($contains) {
|
||||
$params['Contains'] = $contains;
|
||||
}
|
||||
|
||||
$url .= '?' . http_build_query($params);
|
||||
|
||||
return $this->make_request('GET', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purchase a phone number
|
||||
*/
|
||||
public function purchase_phone_number($phone_number, $voice_url = null, $sms_url = null) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers.json';
|
||||
|
||||
$data = array(
|
||||
'PhoneNumber' => $phone_number
|
||||
);
|
||||
|
||||
if ($voice_url) {
|
||||
$data['VoiceUrl'] = $voice_url;
|
||||
$data['VoiceMethod'] = 'POST';
|
||||
try {
|
||||
$params = ['phoneNumber' => $phone_number];
|
||||
|
||||
if ($voice_url) {
|
||||
$params['voiceUrl'] = $voice_url;
|
||||
$params['voiceMethod'] = 'POST';
|
||||
}
|
||||
|
||||
if ($sms_url) {
|
||||
$params['smsUrl'] = $sms_url;
|
||||
$params['smsMethod'] = 'POST';
|
||||
}
|
||||
|
||||
$number = $this->client->incomingPhoneNumbers->create($params);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $number->sid,
|
||||
'phoneNumber' => $number->phoneNumber,
|
||||
'friendlyName' => $number->friendlyName,
|
||||
'voiceUrl' => $number->voiceUrl,
|
||||
'smsUrl' => $number->smsUrl
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
|
||||
if ($sms_url) {
|
||||
$data['SmsUrl'] = $sms_url;
|
||||
$data['SmsMethod'] = 'POST';
|
||||
}
|
||||
|
||||
return $this->make_request('POST', $url, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a phone number
|
||||
*/
|
||||
public function release_phone_number($phone_number_sid) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers/' . $phone_number_sid . '.json';
|
||||
return $this->make_request('DELETE', $url);
|
||||
try {
|
||||
$this->client->incomingPhoneNumbers($phone_number_sid)->delete();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'message' => 'Phone number released successfully'
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure phone number webhook
|
||||
*/
|
||||
public function configure_phone_number($phone_sid, $voice_url, $sms_url = null) {
|
||||
$url = $this->api_base . '/Accounts/' . $this->account_sid . '/IncomingPhoneNumbers/' . $phone_sid . '.json';
|
||||
|
||||
$data = array(
|
||||
'VoiceUrl' => $voice_url,
|
||||
'VoiceMethod' => 'POST'
|
||||
);
|
||||
|
||||
if ($sms_url) {
|
||||
$data['SmsUrl'] = $sms_url;
|
||||
$data['SmsMethod'] = 'POST';
|
||||
try {
|
||||
$params = [
|
||||
'voiceUrl' => $voice_url,
|
||||
'voiceMethod' => 'POST'
|
||||
];
|
||||
|
||||
if ($sms_url) {
|
||||
$params['smsUrl'] = $sms_url;
|
||||
$params['smsMethod'] = 'POST';
|
||||
}
|
||||
|
||||
$number = $this->client->incomingPhoneNumbers($phone_sid)->update($params);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'sid' => $number->sid,
|
||||
'phoneNumber' => $number->phoneNumber,
|
||||
'voiceUrl' => $number->voiceUrl,
|
||||
'smsUrl' => $number->smsUrl
|
||||
]
|
||||
];
|
||||
} catch (\Twilio\Exceptions\TwilioException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
];
|
||||
}
|
||||
|
||||
return $this->make_request('POST', $url, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make API request
|
||||
* Create TwiML helper - returns SDK VoiceResponse
|
||||
*/
|
||||
private function make_request($method, $url, $data = array()) {
|
||||
$args = array(
|
||||
'method' => $method,
|
||||
'headers' => array(
|
||||
'Authorization' => 'Basic ' . base64_encode($this->account_sid . ':' . $this->auth_token),
|
||||
'Content-Type' => 'application/x-www-form-urlencoded'
|
||||
),
|
||||
'timeout' => 30
|
||||
);
|
||||
|
||||
if ($method === 'POST' && !empty($data)) {
|
||||
$args['body'] = $data;
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$response = wp_remote_get($url, $args);
|
||||
} else {
|
||||
$response = wp_remote_post($url, $args);
|
||||
}
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'error' => $response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$decoded = json_decode($body, true);
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code($response);
|
||||
|
||||
if ($status_code >= 200 && $status_code < 300) {
|
||||
return array(
|
||||
'success' => true,
|
||||
'data' => $decoded
|
||||
);
|
||||
} else {
|
||||
return array(
|
||||
'success' => false,
|
||||
'error' => isset($decoded['message']) ? $decoded['message'] : 'API request failed',
|
||||
'code' => $status_code
|
||||
);
|
||||
}
|
||||
public function create_twiml() {
|
||||
return new \Twilio\TwiML\VoiceResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Twilio client instance
|
||||
*/
|
||||
public function get_client() {
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate webhook signature
|
||||
*/
|
||||
public function validate_webhook_signature($url, $params, $signature) {
|
||||
$data = $url;
|
||||
|
||||
if (is_array($params) && !empty($params)) {
|
||||
ksort($params);
|
||||
foreach ($params as $key => $value) {
|
||||
$data .= $key . $value;
|
||||
}
|
||||
}
|
||||
|
||||
$computed_signature = base64_encode(hash_hmac('sha1', $data, $this->auth_token, true));
|
||||
|
||||
return hash_equals($signature, $computed_signature);
|
||||
$validator = new \Twilio\Security\RequestValidator(get_option('twp_twilio_auth_token'));
|
||||
return $validator->validate($signature, $url, $params);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user