Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class CurlException extends RuntimeException implements SipgateExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class InvalidArgumentException extends RuntimeException implements SipgateExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ResponseDecodeException extends RuntimeException implements SipgateExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ResponseException extends RuntimeException implements SipgateExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate\Exception;
|
||||
|
||||
use Xentral\Core\Exception\ModuleExceptionInterface;
|
||||
|
||||
interface SipgateExceptionInterface extends ModuleExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class UnauthorizedException extends RuntimeException implements SipgateExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate;
|
||||
|
||||
use Xentral\Modules\Sipgate\Exception\ResponseException;
|
||||
use Xentral\Modules\Sipgate\Exception\ResponseDecodeException;
|
||||
|
||||
class SipgateResponse
|
||||
{
|
||||
/** @var string $result The response body as string. */
|
||||
private $result = '';
|
||||
|
||||
/** @var array $body The decoded body as array. */
|
||||
private $body = [];
|
||||
|
||||
/** @var array $info The curl info via curl_getinfo() */
|
||||
private $info = [];
|
||||
|
||||
/**
|
||||
* @param string $result
|
||||
* @param array $info
|
||||
*
|
||||
* @throws ResponseDecodeException
|
||||
*/
|
||||
public function __construct($result, $info = [])
|
||||
{
|
||||
$this->result = $result;
|
||||
$this->info = $info;
|
||||
|
||||
$this->body = $this->decode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just a helper method to print out some attributes.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param bool $info
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dump($info = true)
|
||||
{
|
||||
echo '<pre style="border: 5px solid #333;padding: 1em;float: left">';
|
||||
echo PHP_EOL;
|
||||
var_dump($this->body);
|
||||
echo PHP_EOL;
|
||||
if ($info) {
|
||||
var_dump($this->info);
|
||||
echo PHP_EOL;
|
||||
}
|
||||
echo '</pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPlainResult()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $fallback
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getInfo($key, $fallback = null)
|
||||
{
|
||||
return array_key_exists($key, $this->info)
|
||||
? $this->info[$key]
|
||||
: $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status code
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
final public function getStatus()
|
||||
{
|
||||
return $this->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status code
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
final public function getStatusCode()
|
||||
{
|
||||
$status = $this->getInfo('http_code');
|
||||
|
||||
return (int)$status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function getURL()
|
||||
{
|
||||
$url = $this->getInfo('url');
|
||||
|
||||
return (string)$url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the result body.
|
||||
*
|
||||
* @param string $contents
|
||||
*
|
||||
* @throws ResponseDecodeException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function decode($contents)
|
||||
{
|
||||
if (!array_key_exists('content_type', $this->info)) {
|
||||
return [];
|
||||
}
|
||||
$type = $this->info['content_type'];
|
||||
switch ($type) {
|
||||
case 'application/json':
|
||||
$result = json_decode($contents, true);
|
||||
$errno = json_last_error();
|
||||
if ($errno) {
|
||||
$msg = json_last_error_msg();
|
||||
throw new ResponseDecodeException(sprintf(
|
||||
'JSON decode error (Code %s): %s',
|
||||
$errno,
|
||||
$msg
|
||||
));
|
||||
}
|
||||
|
||||
if (array_key_exists('ERROR', $result)) {
|
||||
$status = $this->info['http_code'];
|
||||
$error = $result['ERROR'];
|
||||
|
||||
// @todo Exception Message überdenken; Was passiert genau?
|
||||
throw new ResponseException("Error ({$status}): {$error}");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
$result = [];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\Sipgate;
|
||||
|
||||
use DOMAttr;
|
||||
use \DOMDocument;
|
||||
use \DOMElement;
|
||||
|
||||
/**
|
||||
* Class SipGateWebHook
|
||||
*
|
||||
* Manage incoming requests from sipgate web hook api. This class is only used in
|
||||
* www/pages/callcenter.php in Callcenter::CallcenterCall in switch 'sipgate' to
|
||||
* create the xml response.
|
||||
*
|
||||
* It also provides methods to hang up the call etc.
|
||||
*
|
||||
* @see https://developer.sipgate.io/push-api/api-reference
|
||||
* @see https://github.com/sipgate/sipgate.io/blob/master/examples/php/
|
||||
*
|
||||
* @example new SipGateWebHook($_POST, 'http://localhost:8080');
|
||||
*/
|
||||
class SipgateWebHook
|
||||
{
|
||||
/** @var array $data The call date. By default $_POST */
|
||||
private $data = [];
|
||||
|
||||
/** @var string $url Optional the callback url to listen for following events. */
|
||||
private $url = '';
|
||||
|
||||
/**
|
||||
* @param array $data The call data
|
||||
* @param string The url used as web hook for 'onAnswer' & 'onHangup' events.
|
||||
*/
|
||||
public function __construct($data, $url = '')
|
||||
{
|
||||
$data = (array)$data;
|
||||
$data['timestamp'] = time();
|
||||
$data['date'] = date('Y-m-d H:i:s');
|
||||
$this->data = $data;
|
||||
|
||||
if (is_string($url) && filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$this->url = $url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $fallback
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getData($key, $fallback = '')
|
||||
{
|
||||
return array_key_exists($key, $this->data)
|
||||
? $this->data[$key]
|
||||
: $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the call and alter your caller id (call charges apply).
|
||||
* Calls with direction=in can be redirected to up to 5 targets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dial()
|
||||
{
|
||||
$config = [
|
||||
// 'voicemail' => true,
|
||||
'suppress' => true,
|
||||
'numbers' => [
|
||||
123,
|
||||
456,
|
||||
678,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param DOMDocument $dom
|
||||
* @param DOMElement $parent
|
||||
*
|
||||
* @return DOMElement|null
|
||||
*/
|
||||
$callback = static function ($dom, $parent) use ($config) {
|
||||
|
||||
if (isset($config['voicemail']) && $config['voicemail']) {
|
||||
return $dom->createElement('Voicemail');
|
||||
}
|
||||
|
||||
/*
|
||||
* Suppress phone number
|
||||
*/
|
||||
if ((isset($config['suppress']) && $config['suppress']) ||
|
||||
(isset($config['anonymous']) && $config['anonymous'])) {
|
||||
|
||||
$anonymous = $dom->createAttribute('anonymous');
|
||||
$anonymous->value = 'true';
|
||||
|
||||
$parent->appendChild($anonymous);
|
||||
}
|
||||
|
||||
if (isset($config['number']) && $config['number']) {
|
||||
/*
|
||||
* override 'numbers' with 'number'
|
||||
*/
|
||||
$config['numbers'] = $config['number'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Redirect incoming call to (multiple) destination(s)
|
||||
* Calls with direction=in can be redirected to up to 5 targets.
|
||||
*/
|
||||
if (isset($config['numbers']) && $config['numbers']) {
|
||||
$numbers = (array)$config['numbers'];
|
||||
$numbers = array_filter($numbers);
|
||||
$numbers = array_slice($numbers, 0, 5);
|
||||
|
||||
foreach ($numbers as $number) {
|
||||
$numberElement = $dom->createElement('Number', $number);
|
||||
$parent->appendChild($numberElement);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
$this->createXMLResponse('Dial', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send call to voice mail
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <Response>
|
||||
* <Dial>
|
||||
* <Voicemail />
|
||||
* </Dial>
|
||||
* </Response>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function voiceMail()
|
||||
{
|
||||
/**
|
||||
* @param DOMDocument $dom
|
||||
*
|
||||
* @return DOMElement|null
|
||||
*/
|
||||
$callback = static function ($dom) {
|
||||
return $dom->createElement('Voicemail');
|
||||
};
|
||||
|
||||
$this->createXMLResponse('Dial', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject call signaling busy
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <Response>
|
||||
* <Reject reason="busy" />
|
||||
* </Response>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function busy()
|
||||
{
|
||||
/**
|
||||
* @param DOMDocument $dom
|
||||
*
|
||||
* @return DOMAttr
|
||||
*/
|
||||
$callback = static function ($dom) {
|
||||
$hangupReason = $dom->createAttribute('reason');
|
||||
$hangupReason->value = 'busy';
|
||||
|
||||
return $hangupReason;
|
||||
};
|
||||
|
||||
$this->createXMLResponse('Reject', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject call
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <Response>
|
||||
* <Reject />
|
||||
* </Response>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reject()
|
||||
{
|
||||
$this->createXMLResponse('Reject');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hang up calls
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <Response>
|
||||
* <Hangup />
|
||||
* </Response>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hangUp()
|
||||
{
|
||||
$this->createXMLResponse('Hangup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a sound file
|
||||
*
|
||||
* @see: https://developer.sipgate.io/push-api/api-reference/#play
|
||||
*
|
||||
* Please note:
|
||||
* Currently the sound file needs to be a mono 16bit PCM WAV file with a sampling rate of 8kHz.
|
||||
* You can use conversion tools like the open source audio editor Audacity to convert any sound
|
||||
* file to the correct format. Linux users might want to use mpg123 to convert the file:
|
||||
* $ mpg123 --rate 8000 --mono -w output.wav input.mp3
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <Response>
|
||||
* <Play>
|
||||
* <Url>http://example.com/example.wav</Url>
|
||||
* </Play>
|
||||
* </Response>
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function play($url)
|
||||
{
|
||||
/**
|
||||
* @param DOMDocument $dom
|
||||
*
|
||||
* @return DOMElement
|
||||
*/
|
||||
$callback = static function ($dom) use ($url) {
|
||||
return $dom->createElement('Url', $url);
|
||||
};
|
||||
|
||||
$this->createXMLResponse('Play', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets header to xml and displays output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function listenOnFollowingEvents()
|
||||
{
|
||||
if (headers_sent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// createXMLResponse starts sending headers & display some content
|
||||
$this->createXMLResponse(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create XML like:
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <Response onAnswer="http://localhost" onHangup="http://localhost">
|
||||
* <Reject reason="busy"/>
|
||||
* </Response>
|
||||
*
|
||||
* @param string $childName
|
||||
* @param callable $callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createXMLResponse($childName, $callback = null)
|
||||
{
|
||||
$dom = new DOMDocument('1.0', 'UTF-8');
|
||||
$response = $dom->createElement('Response');
|
||||
|
||||
/*
|
||||
* On new call, set the onAnswer & onHangup flags
|
||||
*/
|
||||
if ($this->url && $this->data['event'] && $this->data['event'] === 'newCall') {
|
||||
$url = $this->url;
|
||||
|
||||
/*
|
||||
* If you set the onAnswer attribute sipgate.io will push an answer-event,
|
||||
* when a call is answered by the other party.
|
||||
*/
|
||||
$response->setAttribute('onAnswer', $url);
|
||||
|
||||
/*
|
||||
* If you set the onHangup attribute sipgate.io will push a hangup-event
|
||||
* when the call ends.
|
||||
*/
|
||||
$response->setAttribute('onHangup', $url);
|
||||
}
|
||||
|
||||
if (is_string($childName) && $childName) {
|
||||
/*
|
||||
* create the child defined in the $childName argument
|
||||
*/
|
||||
$child = $dom->createElement($childName);
|
||||
|
||||
/*
|
||||
* If a callback is given, let's append it's response
|
||||
* to the child.
|
||||
*/
|
||||
if ($callback !== null && is_callable($callback)) {
|
||||
$element = $callback($dom, $child);
|
||||
if ($element) {
|
||||
$child->appendChild($element);
|
||||
}
|
||||
}
|
||||
$response->appendChild($child);
|
||||
}
|
||||
$dom->appendChild($response);
|
||||
|
||||
header('Content-type: application/xml');
|
||||
echo $dom->saveXML();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
/**
|
||||
* Hide the search field and the table footer with export functions.
|
||||
* todo: check functionality and usage!
|
||||
*/
|
||||
#snapaddy_address_filter,
|
||||
#snapaddy_address_paginate,
|
||||
#snapaddy_address_paginate + .dt-buttons{display: none;}
|
||||
|
||||
#sipgate_user_form #api-test{
|
||||
text-decoration: underline;
|
||||
}
|
||||
#sipgate_user_form #api-test:hover{
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#api-test-result.api_fail{
|
||||
color: firebrick;
|
||||
}
|
||||
#api-test-result.api_success{
|
||||
color: darkgreen;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* On mouseover change the password input to text and change back
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
let snapForm = document.getElementById('tabs');
|
||||
if (snapForm) {
|
||||
/*
|
||||
* Find all input fields in 'tabs' div.
|
||||
*/
|
||||
let inputs = snapForm.getElementsByTagName('input');
|
||||
for (let i=0; i<inputs.length; i++) {
|
||||
if (inputs[i].type.toLowerCase() !== "password") {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* On mouseenter, change the type from password to text
|
||||
*/
|
||||
inputs[i].addEventListener("mouseenter", function( event ) {
|
||||
event.target.type = 'text';
|
||||
}, false);
|
||||
/*
|
||||
* On mouseout, change the type from text to password
|
||||
*/
|
||||
inputs[i].addEventListener("mouseout", function( event ) {
|
||||
event.target.type = 'password';
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
$('#sipgate_webhook').val(
|
||||
window.location.href
|
||||
.replace('=sipgate', '=callcenter&provider=sipgate')
|
||||
.replace('=edit', '=call')
|
||||
);
|
||||
|
||||
|
||||
$('#api-test').click(function (e) {
|
||||
e.preventDefault();
|
||||
console.log('api test');
|
||||
let key = $('#api-key').val();
|
||||
let resultField = $('#api-test-result');
|
||||
if (!key) {
|
||||
resultField.html('<span class="api_fail">API Key ist leer.</span>');
|
||||
return;
|
||||
}
|
||||
let url = 'index.php?module=sipgate&action=apicheck';
|
||||
resultField
|
||||
.attr('class', '')
|
||||
.html('Lädt ...');
|
||||
|
||||
$.post(url, {key: key})
|
||||
.done(function(msg, status, xhr){
|
||||
console.log(msg, status, xhr);
|
||||
console.warn(msg.key);
|
||||
console.warn(msg.class);
|
||||
if (msg.key && msg.class) {
|
||||
resultField.append('<span class="' + + '">' + msg.key + '</span>');
|
||||
resultField
|
||||
.attr('class', msg.class)
|
||||
.html(msg.key);
|
||||
} else {
|
||||
resultField
|
||||
.attr('class', 'api_fail')
|
||||
.html('Server Fehler');
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
resultField
|
||||
.attr('class', 'api_fail')
|
||||
.html('Der Test schlug fehl.');
|
||||
console.error(status, error, xhr);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function call(id, dummy)
|
||||
{
|
||||
$.ajax({
|
||||
url: 'index.php?module=sipgate&action=call&id='+id,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {},
|
||||
success: function(data) {
|
||||
if(data)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user