Browse code

Added README.md appinfo/info.xml appinfo/signature.json lib/Controller/AuthorApiController.php and the providers directory

DoubleBastionAdmin authored on 20/08/2022 16:33:00
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,436 @@
1
+<?php
2
+
3
+namespace Telnyx;
4
+
5
+/**
6
+ * Class ApiRequestor
7
+ *
8
+ * @package Telnyx
9
+ */
10
+class ApiRequestor
11
+{
12
+    /**
13
+     * @var string|null
14
+     */
15
+    private $_apiKey;
16
+
17
+    /**
18
+     * @var string
19
+     */
20
+    private $_apiBase;
21
+
22
+    /**
23
+     * @var HttpClient\ClientInterface
24
+     */
25
+    private static $_httpClient;
26
+
27
+    /**
28
+     * @var RequestTelemetry
29
+     */
30
+    private static $requestTelemetry;
31
+
32
+    /**
33
+     * ApiRequestor constructor.
34
+     *
35
+     * @param string|null $apiKey
36
+     * @param string|null $apiBase
37
+     */
38
+    public function __construct($apiKey = null, $apiBase = null)
39
+    {
40
+        $this->_apiKey = $apiKey;
41
+        if (!$apiBase) {
42
+            $apiBase = Telnyx::$apiBase;
43
+        }
44
+        $this->_apiBase = $apiBase;
45
+    }
46
+
47
+    /**
48
+     * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers
49
+     * @static
50
+     *
51
+     * @param RequestTelemetry $requestTelemetry
52
+     * @return string
53
+     */
54
+    private static function _telemetryJson($requestTelemetry)
55
+    {
56
+        $payload = array(
57
+            'last_request_metrics' => array(
58
+                'request_id' => $requestTelemetry->requestId,
59
+                'request_duration_ms' => $requestTelemetry->requestDuration,
60
+        ));
61
+
62
+        $result = json_encode($payload);
63
+        if ($result != false) {
64
+            return $result;
65
+        } else {
66
+            Telnyx::getLogger()->error("Serializing telemetry payload failed!");
67
+            return "{}";
68
+        }
69
+    }
70
+
71
+    /**
72
+     * @static
73
+     *
74
+     * @param ApiResource|bool|array|mixed $d
75
+     *
76
+     * @return ApiResource|array|string|mixed
77
+     */
78
+    private static function _encodeObjects($d)
79
+    {
80
+        if ($d instanceof ApiResource) {
81
+            return Util\Util::utf8($d->id);
82
+        } elseif ($d === true) {
83
+            return 'true';
84
+        } elseif ($d === false) {
85
+            return 'false';
86
+        } elseif (is_array($d)) {
87
+            $res = [];
88
+            foreach ($d as $k => $v) {
89
+                $res[$k] = self::_encodeObjects($v);
90
+            }
91
+            return $res;
92
+        } else {
93
+            return Util\Util::utf8($d);
94
+        }
95
+    }
96
+
97
+    /**
98
+     * @param string     $method
99
+     * @param string     $url
100
+     * @param null|array $params
101
+     * @param null|array $headers
102
+     *
103
+     * @throws Exception\ApiErrorException
104
+     *
105
+     * @return array tuple containing (ApiReponse, API key)
106
+     */
107
+    public function request($method, $url, $params = null, $headers = null)
108
+    {
109
+        $params = $params ?: [];
110
+        $headers = $headers ?: [];
111
+        list($rbody, $rcode, $rheaders, $myApiKey) = $this->_requestRaw($method, $url, $params, $headers);
112
+        $json = $this->_interpretResponse($rbody, $rcode, $rheaders);
113
+        $resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
114
+
115
+        return [$resp, $myApiKey];
116
+    }
117
+
118
+    /**
119
+     * @param string $rbody a JSON string
120
+     * @param int $rcode
121
+     * @param array $rheaders
122
+     * @param array $resp
123
+     *
124
+     * @throws Exception\UnexpectedValueException
125
+     * @throws Exception\ApiErrorException
126
+     */
127
+    public function handleErrorResponse($rbody, $rcode, $rheaders, $resp)
128
+    {
129
+        if (!\is_array($resp) || !isset($resp['errors'])) {
130
+            $msg = "Invalid response object from API: {$rbody} "
131
+              . "(HTTP response code was {$rcode})";
132
+
133
+            throw new Exception\UnexpectedValueException($msg);
134
+        }
135
+
136
+        $errorData = $resp['errors'];
137
+
138
+        $error = null;
139
+        if (!$error) {
140
+            $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData);
141
+        }
142
+
143
+        throw $error;
144
+    }
145
+
146
+    /**
147
+     * @static
148
+     *
149
+     * @param string $rbody
150
+     * @param int    $rcode
151
+     * @param array  $rheaders
152
+     * @param array  $resp
153
+     * @param array  $errorData
154
+     *
155
+     * @return Exception\ApiErrorException
156
+     */
157
+    private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData)
158
+    {
159
+        $msg = isset($errorData[0]['detail']) ? $errorData[0]['detail'] : (isset($errorData[0]['title']) ? $errorData[0]['title'] : null) ;
160
+        $param = isset($errorData[0]['param']) ? $errorData[0]['param'] : null;
161
+        $code = isset($errorData[0]['code']) ? $errorData[0]['code'] : null;
162
+        $type = isset($errorData[0]['type']) ? $errorData[0]['type'] : null;
163
+
164
+        switch ($rcode) {
165
+            //case 400:
166
+            //    if ('idempotency_error' === $type) {
167
+            //        return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
168
+            //    }
169
+                // no break
170
+            case 404:
171
+                return Exception\InvalidRequestException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
172
+            case 401:
173
+                return Exception\AuthenticationException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
174
+            case 403:
175
+                return Exception\PermissionException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
176
+            case 429:
177
+                return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
178
+            default:
179
+                return Exception\UnknownApiErrorException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
180
+        }
181
+    }
182
+
183
+    /**
184
+     * @static
185
+     *
186
+     * @param null|array $appInfo
187
+     *
188
+     * @return null|string
189
+     */
190
+    private static function _formatAppInfo($appInfo)
191
+    {
192
+        if ($appInfo !== null) {
193
+            $string = $appInfo['name'];
194
+            if ($appInfo['version'] !== null) {
195
+                $string .= '/' . $appInfo['version'];
196
+            }
197
+            if ($appInfo['url'] !== null) {
198
+                $string .= ' (' . $appInfo['url'] . ')';
199
+            }
200
+            return $string;
201
+        } else {
202
+            return null;
203
+        }
204
+    }
205
+
206
+    /**
207
+     * @static
208
+     *
209
+     * @param string $apiKey
210
+     * @param null   $clientInfo
211
+     *
212
+     * @return array
213
+     */
214
+    private static function _defaultHeaders($apiKey, $clientInfo = null)
215
+    {
216
+        $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION;
217
+
218
+        $langVersion = phpversion();
219
+        $uname = php_uname();
220
+
221
+        $appInfo = Telnyx::getAppInfo();
222
+        $ua = [
223
+            'bindings_version' => Telnyx::VERSION,
224
+            'lang' => 'php',
225
+            'lang_version' => $langVersion,
226
+            'publisher' => 'telnyx',
227
+            'uname' => $uname,
228
+        ];
229
+        if ($clientInfo) {
230
+            $ua = array_merge($clientInfo, $ua);
231
+        }
232
+        if ($appInfo !== null) {
233
+            $uaString .= ' ' . self::_formatAppInfo($appInfo);
234
+            $ua['application'] = $appInfo;
235
+        }
236
+
237
+        $defaultHeaders = [
238
+            'X-Telnyx-Client-User-Agent' => json_encode($ua),
239
+            'User-Agent' => $uaString,
240
+            'Authorization' => 'Bearer ' . $apiKey,
241
+        ];
242
+        return $defaultHeaders;
243
+    }
244
+
245
+    /**
246
+     * @param string $method
247
+     * @param string $url
248
+     * @param array  $params
249
+     * @param array  $headers
250
+     *
251
+     * @throws Exception\AuthenticationException
252
+     * @throws Exception\ApiConnectionException
253
+     *
254
+     * @return array
255
+     */
256
+    private function _requestRaw($method, $url, $params, $headers)
257
+    {
258
+        $myApiKey = $this->_apiKey;
259
+        if (!$myApiKey) {
260
+            $myApiKey = Telnyx::$apiKey;
261
+        }
262
+
263
+        if (!$myApiKey) {
264
+            $msg = 'No API key provided.  (HINT: set your API key using '
265
+              . '"Telnyx::setApiKey(<API-KEY>)".  You can generate API keys from '
266
+              . 'the Telnyx web interface.  See https://developers.telnyx.com/docs/v2/development/authentication '
267
+              . 'for details, or email support@telnyx.com if you have any questions.';
268
+            throw new Exception\AuthenticationException($msg);
269
+        }
270
+
271
+        // Clients can supply arbitrary additional keys to be included in the
272
+        // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo()
273
+        // method
274
+        $clientUAInfo = null;
275
+        if (method_exists($this->httpClient(), 'getUserAgentInfo')) {
276
+            $clientUAInfo = $this->httpClient()->getUserAgentInfo();
277
+        }
278
+
279
+        $absUrl = $this->_apiBase.$url;
280
+
281
+        $params = self::_encodeObjects($params);
282
+        $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo);
283
+        if (Telnyx::$apiVersion) {
284
+            $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion;
285
+        }
286
+
287
+        if (Telnyx::$accountId) {
288
+            $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId;
289
+        }
290
+
291
+        if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) {
292
+            $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry);
293
+        }
294
+
295
+        $hasFile = false;
296
+        $hasCurlFile = class_exists('\CURLFile', false);
297
+        foreach ($params as $k => $v) {
298
+            if (is_resource($v)) {
299
+                $hasFile = true;
300
+                $params[$k] = self::_processResourceParam($v, $hasCurlFile);
301
+            } elseif ($hasCurlFile && $v instanceof \CURLFile) {
302
+                $hasFile = true;
303
+            }
304
+        }
305
+
306
+        if ($hasFile) {
307
+            $defaultHeaders['Content-Type'] = 'multipart/form-data';
308
+        } else {
309
+            $defaultHeaders['Content-Type'] = 'application/json';
310
+        }
311
+
312
+        $combinedHeaders = array_merge($defaultHeaders, $headers);
313
+        $rawHeaders = [];
314
+
315
+        foreach ($combinedHeaders as $header => $value) {
316
+            $rawHeaders[] = $header . ': ' . $value;
317
+        }
318
+
319
+        $requestStartMs = Util\Util::currentTimeMillis();
320
+
321
+        list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
322
+            $method,
323
+            $absUrl,
324
+            $rawHeaders,
325
+            $params,
326
+            $hasFile
327
+        );
328
+
329
+        if (isset($rheaders['request-id'])) {
330
+            self::$requestTelemetry = new RequestTelemetry(
331
+                $rheaders['request-id'],
332
+                Util\Util::currentTimeMillis() - $requestStartMs
333
+            );
334
+        }
335
+
336
+        return [$rbody, $rcode, $rheaders, $myApiKey];
337
+    }
338
+
339
+    /**
340
+     * @param resource $resource
341
+     * @param bool     $hasCurlFile
342
+     *
343
+     * @throws Exception\InvalidArgumentException
344
+     *
345
+     * @return \CURLFile|string
346
+     */
347
+    private function _processResourceParam($resource, $hasCurlFile)
348
+    {
349
+        if (get_resource_type($resource) !== 'stream') {
350
+            throw new Exception\InvalidArgumentException(
351
+                'Attempted to upload a resource that is not a stream'
352
+            );
353
+        }
354
+
355
+        $metaData = stream_get_meta_data($resource);
356
+        if ($metaData['wrapper_type'] !== 'plainfile') {
357
+            throw new Exception\InvalidArgumentException(
358
+                'Only plainfile resource streams are supported'
359
+            );
360
+        }
361
+
362
+        if ($hasCurlFile) {
363
+            // We don't have the filename or mimetype, but the API doesn't care
364
+            return new \CURLFile($metaData['uri']);
365
+        } else {
366
+            return '@'.$metaData['uri'];
367
+        }
368
+    }
369
+
370
+    /**
371
+     * @param string $rbody
372
+     * @param int    $rcode
373
+     * @param array  $rheaders
374
+     *
375
+     * @throws Exception\UnexpectedValueException
376
+     * @throws Exception\ApiErrorException
377
+     *
378
+     * @return array
379
+     */
380
+    private function _interpretResponse($rbody, $rcode, $rheaders)
381
+    {
382
+        $resp = json_decode($rbody, true);
383
+
384
+        if (isset($resp['data'])) {
385
+            // See if this is NOT a \Telnyx\Collection because Collections are not explicitly specified by the API
386
+            if (isset($resp['data']['record_type']) && !isset($resp['data'][0])) {
387
+                $resp = $resp['data']; // Move [data] to the parent node because it is a single entry, not a collection
388
+            }
389
+        }
390
+
391
+        $jsonError = json_last_error();
392
+        if ($resp === null && $jsonError !== JSON_ERROR_NONE) {
393
+            $msg = "Invalid response body from API: $rbody "
394
+              . "(HTTP response code was $rcode, json_last_error() was $jsonError)";
395
+
396
+            throw new Exception\UnexpectedValueException($msg, $rcode);
397
+        }
398
+
399
+        if ($rcode < 200 || $rcode >= 300) {
400
+            $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp);
401
+        }
402
+        
403
+        return $resp;
404
+    }
405
+
406
+    /**
407
+     * @static
408
+     *
409
+     * @param HttpClient\ClientInterface $client
410
+     */
411
+    public static function setHttpClient($client)
412
+    {
413
+        self::$_httpClient = $client;
414
+    }
415
+
416
+    /**
417
+     * @static
418
+     *
419
+     * Resets any stateful telemetry data
420
+     */
421
+    public static function resetTelemetry()
422
+    {
423
+        self::$requestTelemetry = null;
424
+    }
425
+
426
+    /**
427
+     * @return HttpClient\ClientInterface
428
+     */
429
+    private function httpClient()
430
+    {
431
+        if (!self::$_httpClient) {
432
+            self::$_httpClient = HttpClient\CurlClient::instance();
433
+        }
434
+        return self::$_httpClient;
435
+    }
436
+}