mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-27 07:00:29 +01:00
606 lines
16 KiB
PHP
606 lines
16 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Xentral\Modules\Sipgate;
|
||
|
|
||
|
use Xentral\Modules\Sipgate\Exception\CurlException;
|
||
|
use Xentral\Modules\Sipgate\Exception\ResponseException;
|
||
|
use Xentral\Modules\Sipgate\Exception\UnauthorizedException;
|
||
|
use Xentral\Modules\Sipgate\Exception\InvalidArgumentException;
|
||
|
|
||
|
/**
|
||
|
* @url https://developer.sipgate.io/rest-api/rtcm/
|
||
|
* @url https://api.sipgate.com/v2/doc#/
|
||
|
*/
|
||
|
class SipgateRequest
|
||
|
{
|
||
|
/** @var string API_BASE We'll use the v2 endpoint. */
|
||
|
const API_BASE = 'https://api.sipgate.com/v2/';
|
||
|
|
||
|
/** @var array The request header stay here as Key -> 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;
|
||
|
}
|
||
|
}
|