account_sid = get_option('twp_twilio_account_sid'); $this->auth_token = get_option('twp_twilio_auth_token'); $this->phone_number = get_option('twp_twilio_phone_number'); } /** * 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'); } 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() )); } /** * 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); } /** * 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); } /** * 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'); } $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']); } $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); } /** * 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); } /** * 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; } 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'; } 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); } /** * 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'; } return $this->make_request('POST', $url, $data); } /** * Make API request */ 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 ); } } /** * 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); } }