_apiKey = $apiKey; if (!$apiBase) { $apiBase = Telnyx::$apiBase; } $this->_apiBase = $apiBase; } /** * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers * @static * * @param RequestTelemetry $requestTelemetry * @return string */ private static function _telemetryJson($requestTelemetry) { $payload = array( 'last_request_metrics' => array( 'request_id' => $requestTelemetry->requestId, 'request_duration_ms' => $requestTelemetry->requestDuration, )); $result = json_encode($payload); if ($result != false) { return $result; } else { Telnyx::getLogger()->error("Serializing telemetry payload failed!"); return "{}"; } } /** * @static * * @param ApiResource|bool|array|mixed $d * * @return ApiResource|array|string|mixed */ private static function _encodeObjects($d) { if ($d instanceof ApiResource) { return Util\Util::utf8($d->id); } elseif ($d === true) { return 'true'; } elseif ($d === false) { return 'false'; } elseif (is_array($d)) { $res = []; foreach ($d as $k => $v) { $res[$k] = self::_encodeObjects($v); } return $res; } else { return Util\Util::utf8($d); } } /** * @param string $method * @param string $url * @param array|null $params * @param array|null $headers * * @return array An array whose first element is an API response and second * element is the API key used to make the request. * @throws Error\Api * @throws Error\Authentication * @throws Error\Card * @throws Error\InvalidRequest * @throws Error\Permission * @throws Error\RateLimit * @throws Error\Idempotency * @throws Error\ApiConnection */ public function request($method, $url, $params = null, $headers = null) { $params = $params ?: []; $headers = $headers ?: []; list($rbody, $rcode, $rheaders, $myApiKey) = $this->_requestRaw($method, $url, $params, $headers); $json = $this->_interpretResponse($rbody, $rcode, $rheaders); $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); return [$resp, $myApiKey]; } /** * @param string $rbody A JSON string. * @param int $rcode * @param array $rheaders * @param array $resp * * @throws Error\InvalidRequest if the error is caused by the user. * @throws Error\Authentication if the error is caused by a lack of * permissions. * @throws Error\Permission if the error is caused by insufficient * permissions. * @throws Error\Card if the error is the error code is 402 (payment * required) * @throws Error\InvalidRequest if the error is caused by the user. * @throws Error\Idempotency if the error is caused by an idempotency key. * @throws Error\Permission if the error is caused by insufficient * permissions. * @throws Error\RateLimit if the error is caused by too many requests * hitting the API. * @throws Error\Api otherwise. */ public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) { if (!is_array($resp) || !isset($resp['error'])) { $msg = "Invalid response object from API: $rbody " . "(HTTP response code was $rcode)"; throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); } $errorData = $resp['error']; #echo $rbody;exit; $error = null; if (!$error) { $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); } throw $error; } /** * @static * * @param string $rbody * @param int $rcode * @param array $rheaders * @param array $resp * @param array $errorData * * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api */ private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) { $msg = isset($errorData['message']) ? $errorData['message'] : null; $param = isset($errorData['param']) ? $errorData['param'] : null; $code = isset($errorData['code']) ? $errorData['code'] : null; $type = isset($errorData['type']) ? $errorData['type'] : null; switch ($rcode) { case 400: // 'rate_limit' code is deprecated, but left here for backwards compatibility // for API versions earlier than 2015-09-08 if ($code == 'rate_limit') { return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); } if ($type == 'idempotency_error') { return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); } // intentional fall-through // no break case 404: return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); case 401: return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); case 402: return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); case 403: return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); case 429: return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); default: return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); } } /** * @static * * @param null|array $appInfo * * @return null|string */ private static function _formatAppInfo($appInfo) { if ($appInfo !== null) { $string = $appInfo['name']; if ($appInfo['version'] !== null) { $string .= '/' . $appInfo['version']; } if ($appInfo['url'] !== null) { $string .= ' (' . $appInfo['url'] . ')'; } return $string; } else { return null; } } /** * @static * * @param string $apiKey * @param null $clientInfo * * @return array */ private static function _defaultHeaders($apiKey, $clientInfo = null) { $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; $langVersion = phpversion(); $uname = php_uname(); $appInfo = Telnyx::getAppInfo(); $ua = [ 'bindings_version' => Telnyx::VERSION, 'lang' => 'php', 'lang_version' => $langVersion, 'publisher' => 'telnyx', 'uname' => $uname, ]; if ($clientInfo) { $ua = array_merge($clientInfo, $ua); } if ($appInfo !== null) { $uaString .= ' ' . self::_formatAppInfo($appInfo); $ua['application'] = $appInfo; } $defaultHeaders = [ 'X-Telnyx-Client-User-Agent' => json_encode($ua), 'User-Agent' => $uaString, 'Authorization' => 'Bearer ' . $apiKey, ]; return $defaultHeaders; } /** * @param string $method * @param string $url * @param array $params * @param array $headers * * @return array * @throws Error\Api * @throws Error\ApiConnection * @throws Error\Authentication */ private function _requestRaw($method, $url, $params, $headers) { $myApiKey = $this->_apiKey; if (!$myApiKey) { $myApiKey = Telnyx::$apiKey; } if (!$myApiKey) { $msg = 'No API key provided. (HINT: set your API key using ' . '"Telnyx::setApiKey()". You can generate API keys from ' . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' . 'for details, or email support@telnyx.com if you have any questions.'; throw new Error\Authentication($msg); } // Clients can supply arbitrary additional keys to be included in the // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() // method $clientUAInfo = null; if (method_exists($this->httpClient(), 'getUserAgentInfo')) { $clientUAInfo = $this->httpClient()->getUserAgentInfo(); } $absUrl = $this->_apiBase.$url; $params = self::_encodeObjects($params); $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); if (Telnyx::$apiVersion) { $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; } if (Telnyx::$accountId) { $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; } if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); } $hasFile = false; $hasCurlFile = class_exists('\CURLFile', false); foreach ($params as $k => $v) { if (is_resource($v)) { $hasFile = true; $params[$k] = self::_processResourceParam($v, $hasCurlFile); } elseif ($hasCurlFile && $v instanceof \CURLFile) { $hasFile = true; } } if ($hasFile) { $defaultHeaders['Content-Type'] = 'multipart/form-data'; } else { $defaultHeaders['Content-Type'] = 'application/json'; } $combinedHeaders = array_merge($defaultHeaders, $headers); $rawHeaders = []; foreach ($combinedHeaders as $header => $value) { $rawHeaders[] = $header . ': ' . $value; } $requestStartMs = Util\Util::currentTimeMillis(); list($rbody, $rcode, $rheaders) = $this->httpClient()->request( $method, $absUrl, $rawHeaders, $params, $hasFile ); // if (array_key_exists('request-id', $rheaders)) { if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { self::$requestTelemetry = new RequestTelemetry( $rheaders['request-id'], Util\Util::currentTimeMillis() - $requestStartMs ); } return [$rbody, $rcode, $rheaders, $myApiKey]; } /** * @param resource $resource * @param bool $hasCurlFile * * @return \CURLFile|string * @throws Error\Api */ private function _processResourceParam($resource, $hasCurlFile) { if (get_resource_type($resource) !== 'stream') { throw new Error\Api( 'Attempted to upload a resource that is not a stream' ); } $metaData = stream_get_meta_data($resource); if ($metaData['wrapper_type'] !== 'plainfile') { throw new Error\Api( 'Only plainfile resource streams are supported' ); } if ($hasCurlFile) { // We don't have the filename or mimetype, but the API doesn't care return new \CURLFile($metaData['uri']); } else { return '@'.$metaData['uri']; } } /** * @param string $rbody * @param int $rcode * @param array $rheaders * * @return mixed * @throws Error\Api * @throws Error\Authentication * @throws Error\Card * @throws Error\InvalidRequest * @throws Error\Permission * @throws Error\RateLimit * @throws Error\Idempotency */ private function _interpretResponse($rbody, $rcode, $rheaders) { $resp = json_decode($rbody, true); // Move [data] to the parent node if (isset($resp['data'])) { $resp = $resp['data']; } $jsonError = json_last_error(); if ($resp === null && $jsonError !== JSON_ERROR_NONE) { $msg = "Invalid response body from API: $rbody " . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; throw new Error\Api($msg, $rcode, $rbody); } if ($rcode < 200 || $rcode >= 300) { $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); } return $resp; } /** * @static * * @param HttpClient\ClientInterface $client */ public static function setHttpClient($client) { self::$_httpClient = $client; } /** * @static * * Resets any stateful telemetry data */ public static function resetTelemetry() { self::$requestTelemetry = null; } /** * @return HttpClient\ClientInterface */ private function httpClient() { if (!self::$_httpClient) { self::$_httpClient = HttpClient\CurlClient::instance(); } return self::$_httpClient; } }