value pairs. */ private $headers = []; /** @var string Basic auth string. */ private $auth = ''; /** * Uses Basic auth! * * @param string $username * @param string $password */ public function __construct($username, $password) { $this->auth = 'Basic ' . base64_encode("{$username}:{$password}"); } /** * @throws ResponseException * * @return bool */ public function ping() { $response = $this->getPingResponse(); if ($response->getStatusCode() !== 200) { throw new ResponseException('API nicht erreichbar.'); } $pong = $response->getBody(); if (!is_array($pong) || !array_key_exists('ping', $pong) || $pong['ping'] !== 'pong') { throw new ResponseException('API nicht erreicht.'); } return true; } /** * Check if API is reachable * * Expect: * status: 200 * body: * { * "ping": "pong" * } * * @throws InvalidArgumentException * @throws CurlException * * @return SipgateResponse */ public function getPingResponse() { return $this->curl([ CURLOPT_URL => 'ping', ]); } /** * @return SipgateResponse */ public function getAccountResponse() { return $this->curl([ CURLOPT_URL => 'account', ]); } /** * @param bool $checkVerified * * @throws ResponseException * @throws CurlException * @throws InvalidArgumentException * * @return array Example: * [ * "company" => "Xentral ERP Software GmbH", * "mainProductType" => "TEAM", * "logoUrl" => "" * "verified" => true * ]; */ public function getAccount($checkVerified = true) { $response = $this->getAccountResponse(); if ($response->getStatusCode() === 401) { throw new ResponseException('Zugangsdaten sind ungültig.'); } if ($response->getStatusCode() === 404) { throw new ResponseException('Account nicht gefunden.'); } $account = $response->getBody(); if (!is_array($account)) { $type = gettype($account); throw new ResponseException(sprintf('Expected array, got %s', $type)); } if (!array_key_exists('verified', $account)) { throw new ResponseException('Verified field is missing.'); } if ($checkVerified && !$account['verified']) { throw new ResponseException('Account ist nicht verifiziert.'); } return $account; } /** * @deprecated * * @param array $arguments * @param string $url * * @throws InvalidArgumentException * @throws CurlException * * @return SipgateResponse */ public function getRequest($url, $arguments = []) { if ($arguments) { $arguments = (array)$arguments; $arguments = array_filter($arguments, 'is_string'); $arguments = array_filter($arguments, 'is_string', ARRAY_FILTER_USE_KEY); $argumentString = http_build_query($arguments); if (!empty($argumentString)) { $url = $url . '?' . $argumentString; } } return $this->curl([ CURLOPT_URL => $url, ]); } /** * Initiate a new call * * DeviceId is only required if the caller parameter is a phone number and not a * deviceId itself. * * Use callerId to set a custom number that will be displayed to the callee. * * @see: https://api.sipgate.com/v2/doc#/sessions/newCall * * body: * { * "deviceId": "e0", * "caller": "e0", * "callee": "+4915799912345", * "callerId": "+4915799912345" * } * * returns: * 200: * { * "sessionId": "string" * } * 400: * User supplied invalid callee number * User supplied invalid caller number * DeviceId is required if caller is a phone number * 402: * Insufficient funds * 403: * User is not allowed to initiate call with given parameters * * @param string $caller * @param string $callee * @param array $optional * * @throws InvalidArgumentException * @throws CurlException * @throws ResponseException * * @return string */ public function startCall($caller, $callee, $optional = []) { $response = $this->startCallResponse($caller, $callee, $optional); if ($response->getStatusCode() !== 200) { $msg = $response->getPlainResult(); throw new ResponseException($msg); } $body = $response->getBody(); return $body['sessionId']; } /** * @param $caller * @param $callee * @param array $optional * * @return SipgateResponse */ public function startCallResponse($caller, $callee, $optional = []) { $optional = (array)$optional; $optional = array_filter($optional, 'is_string'); $optional = array_filter($optional, 'is_string', ARRAY_FILTER_USE_KEY); $allowed = ['deviceId', 'callerId']; $optional = array_intersect_key($optional, array_flip($allowed)); $callee = preg_replace('/[^0-9+]/', '', $callee); $config = [ 'caller' => $caller, 'callee' => $callee, ]; $body = array_merge($optional, $config); return $this->curl([ CURLOPT_URL => '/sessions/calls', CURLOPT_POSTFIELDS => $body, ]); } /** * @param $config * * @throws InvalidArgumentException * @throws CurlException * * @return SipgateResponse */ public function getHistory($config) { /* * If the value is an array, it's used as white list */ $allowed = [ 'types' => ['CALL', 'VOICEMAIL', 'SMS', 'FAX'], 'directions' => ['INCOMING', 'OUTGOING', 'MISSED_INCOMING', 'MISSED_OUTGOING'], 'offset' => 0, 'limit' => 10, 'archived' => false, ]; $config = array_intersect_key($config, array_flip(array_keys($allowed))); $query = []; foreach ($config as $key => $value) { $value = (array)$value; foreach ($value as $val) { if (!is_array($allowed[$key]) || in_array($val, $allowed[$key], true)) { $query[] = urlencode($key) . '=' . urlencode($val); } } } $query = implode('&', $query); return $this->curl([ CURLOPT_URL => '/history' . '?' . $query, ]); } /** * @return SipgateResponse */ public function getMissedCalls() { $query = [ 'types' => 'CALL', 'directions' => [ 'MISSED_INCOMING', 'MISSED_OUTGOING', ], 'offset' => 0, 'limit' => 10, 'archived' => false, ]; return $this->getHistory($query); } /** * @return SipgateResponse */ public function getBalanceResponse() { return $this->curl([ CURLOPT_URL => '/balance', ]); } /** * @throws CurlException * @throws ResponseException * @throws InvalidArgumentException * * @return string like 3.50 Euro */ public function getBalance() { $response = $this->getBalanceResponse(); if ($response->getStatusCode() !== 200) { throw new ResponseException($response->getPlainResult()); } $data = $response->getBody(); if (!in_array('amount', $data, true)) { throw new ResponseException('Amount is missing.'); } if (!in_array('currency', $data, true)) { throw new ResponseException('Currency is missing.'); } $amount = $data['amount']; $amount = (int)$amount / 10000; $amount = round($amount, 2, PHP_ROUND_HALF_UP); $currency = $data['currency']; return sprintf('%s %s', $amount, $currency); } /** * @param $url * * @return SipgateResponse */ public function registerWebHookUrlResponse($url) { if (!filter_var($url, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException('URL is not valid'); } $body = [ 'incomingUrl' => $url, 'outgoingUrl' => $url, 'log' => true, ]; return $this->curl([ CURLOPT_URL => '/settings/sipgateio', CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $body, ]); } /** * Register an endpoint to Sipgate.io * * @param string $url * * @throws InvalidArgumentException * * @return SipgateResponse */ public function registerWebHookUrl($url) { $response = $this->registerWebHookUrlResponse($url); // status code 204: no content if (!in_array($response->getStatusCode(), [200, 204], true)) { $msg = $response->getPlainResult(); throw new ResponseException($msg); } return $response; } /** * { * "data": [ { * "callId": "ABCDEF0123456789", * "muted": "false", * "recording": "false", * "hold": "false", * "participants": [ * { * "participantId": "ABCDEF0123456789", * "phoneNumber": "+4915799912345", * "muted": "false", * "hold": "false", * "owner": "false" * } * ] * } ] * } * * @throws InvalidArgumentException * @throws CurlException * * @return SipgateResponse */ public function getCurrentCallsResponse() { return $this->curl([ CURLOPT_URL => '/calls/', ]); } /** * @deprecated * * @return SipgateResponse */ public function getCurrentCalls() { return $this->getCurrentCallsResponse(); } /** * @return SipgateResponse */ public function getUsersResponse() { return $this->curl([ CURLOPT_URL => '/users/', ]); } /** * @return array */ public function getUsers() { $response = $this->getUsersResponse(); if ($response->getStatusCode() === 401) { throw new UnauthorizedException('Zugangsdaten sind ungültig'); } $users = $response->getBody(); if (!array_key_exists('items', $users) || !is_array($users['items']) || !$users['items']) { throw new ResponseException('Kein API User gefunden'); } $users = $users['items']; return $users; } /** * Run the http request. * * Requires at least the 'CURLOPT_URL' option set to the api path. * The API base is not required. * * If the body is set (CURLOPT_POSTFIELDS) it's required as string * or array. If it's an array, it will be encoded via json_encode. * * @param array $options * * @throws InvalidArgumentException * @throws CurlException * * @return SipgateResponse */ protected function curl($options = []) { /* * Extract the url from the given options. */ if (!array_key_exists(CURLOPT_URL, $options)) { throw new InvalidArgumentException('No URL given.'); } $url = $options[CURLOPT_URL]; $url = ltrim($url, '/'); $url = self::API_BASE . $url; unset($options[CURLOPT_URL]); /* * If a body is set: * -> json_encode the array * -> append the content length. * -> set method to POST if no custom post is defined. */ if (array_key_exists(CURLOPT_POSTFIELDS, $options)) { /* * 1. encode */ $data = $options[CURLOPT_POSTFIELDS]; if (is_array($data)) { $data = json_encode($data); if (json_last_error()) { throw new InvalidArgumentException(json_last_error_msg()); } $options[CURLOPT_POSTFIELDS] = $data; $options[CURLOPT_HTTPHEADER]['Content-Type'] = 'application/json'; } if (!is_string($data)) { $type = gettype($data); throw new InvalidArgumentException('Body is required as string, got ' . $type); } /* * 2. Set content length */ $options[CURLOPT_HTTPHEADER]['Content-Length'] = strlen($data); /* * 3. Set request type */ if (!array_key_exists(CURLOPT_CUSTOMREQUEST, $options)) { $options[CURLOPT_POST] = true; } } /* * Extract the headers from given options. */ $headers = $this->headers; if (array_key_exists(CURLOPT_HTTPHEADER, $options)) { $add = $options[CURLOPT_HTTPHEADER]; unset($options[CURLOPT_HTTPHEADER]); $headers = $headers + (array)$add; unset($add); } $headers['accept'] = 'application/json'; $headers['Authorization'] = $this->auth; $headers = $this->mergeHeader($headers); if (!function_exists('curl_init')) { throw new CurlException('Curl is not available'); } $ch = curl_init(); if (!$ch) { throw new CurlException('Cannot initialize curl'); } $default = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_TIMEOUT => 25, CURLOPT_CONNECTTIMEOUT => 15, ]; $final = [ CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, ]; curl_setopt_array($ch, $default); curl_setopt_array($ch, $options); curl_setopt_array($ch, $final); $result = curl_exec($ch); $info = curl_getinfo($ch); $errno = curl_errno($ch); $error = curl_error($ch); curl_close($ch); if ($errno || $error) { throw new CurlException("Curl ({$errno}): {$error}"); } return new SipgateResponse($result, $info); } /** * Combine array keys with their value using $glue between * key & value. Used in 'curl' method to create the auth * header fields * * @param array $opt * @param string $glue * * @return array */ private function mergeHeader($opt, $glue = ': ') { $tmp = []; $opt = (array)$opt; $glue = (string)$glue; foreach ($opt as $key => $value) { $tmp[] = "{$key}{$glue}{$value}"; } return $tmp; } }