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,464 @@
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 array|null $params
101
+     * @param array|null $headers
102
+     *
103
+     * @return array An array whose first element is an API response and second
104
+     *    element is the API key used to make the request.
105
+     * @throws Error\Api
106
+     * @throws Error\Authentication
107
+     * @throws Error\Card
108
+     * @throws Error\InvalidRequest
109
+     * @throws Error\Permission
110
+     * @throws Error\RateLimit
111
+     * @throws Error\Idempotency
112
+     * @throws Error\ApiConnection
113
+     */
114
+    public function request($method, $url, $params = null, $headers = null)
115
+    {
116
+        $params = $params ?: [];
117
+        $headers = $headers ?: [];
118
+        list($rbody, $rcode, $rheaders, $myApiKey) =
119
+        $this->_requestRaw($method, $url, $params, $headers);
120
+        $json = $this->_interpretResponse($rbody, $rcode, $rheaders);
121
+        $resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
122
+        return [$resp, $myApiKey];
123
+    }
124
+
125
+    /**
126
+     * @param string $rbody A JSON string.
127
+     * @param int $rcode
128
+     * @param array $rheaders
129
+     * @param array $resp
130
+     *
131
+     * @throws Error\InvalidRequest if the error is caused by the user.
132
+     * @throws Error\Authentication if the error is caused by a lack of
133
+     *    permissions.
134
+     * @throws Error\Permission if the error is caused by insufficient
135
+     *    permissions.
136
+     * @throws Error\Card if the error is the error code is 402 (payment
137
+     *    required)
138
+     * @throws Error\InvalidRequest if the error is caused by the user.
139
+     * @throws Error\Idempotency if the error is caused by an idempotency key.
140
+     * @throws Error\Permission if the error is caused by insufficient
141
+     *    permissions.
142
+     * @throws Error\RateLimit if the error is caused by too many requests
143
+     *    hitting the API.
144
+     * @throws Error\Api otherwise.
145
+     */
146
+    public function handleErrorResponse($rbody, $rcode, $rheaders, $resp)
147
+    {
148
+        if (!is_array($resp) || !isset($resp['error'])) {
149
+            $msg = "Invalid response object from API: $rbody "
150
+              . "(HTTP response code was $rcode)";
151
+            throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders);
152
+        }
153
+
154
+        $errorData = $resp['error'];
155
+
156
+        #echo $rbody;exit;
157
+
158
+        $error = null;
159
+        if (!$error) {
160
+            $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData);
161
+        }
162
+
163
+        throw $error;
164
+    }
165
+
166
+    /**
167
+     * @static
168
+     *
169
+     * @param string $rbody
170
+     * @param int    $rcode
171
+     * @param array  $rheaders
172
+     * @param array  $resp
173
+     * @param array  $errorData
174
+     *
175
+     * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api
176
+     */
177
+    private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData)
178
+    {
179
+        $msg = isset($errorData['message']) ? $errorData['message'] : null;
180
+        $param = isset($errorData['param']) ? $errorData['param'] : null;
181
+        $code = isset($errorData['code']) ? $errorData['code'] : null;
182
+        $type = isset($errorData['type']) ? $errorData['type'] : null;
183
+
184
+        switch ($rcode) {
185
+            case 400:
186
+                // 'rate_limit' code is deprecated, but left here for backwards compatibility
187
+                // for API versions earlier than 2015-09-08
188
+                if ($code == 'rate_limit') {
189
+                    return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders);
190
+                }
191
+                if ($type == 'idempotency_error') {
192
+                    return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders);
193
+                }
194
+
195
+                // intentional fall-through
196
+                // no break
197
+            case 404:
198
+                return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders);
199
+            case 401:
200
+                return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders);
201
+            case 402:
202
+                return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders);
203
+            case 403:
204
+                return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders);
205
+            case 429:
206
+                return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders);
207
+            default:
208
+                return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders);
209
+        }
210
+    }
211
+
212
+    /**
213
+     * @static
214
+     *
215
+     * @param null|array $appInfo
216
+     *
217
+     * @return null|string
218
+     */
219
+    private static function _formatAppInfo($appInfo)
220
+    {
221
+        if ($appInfo !== null) {
222
+            $string = $appInfo['name'];
223
+            if ($appInfo['version'] !== null) {
224
+                $string .= '/' . $appInfo['version'];
225
+            }
226
+            if ($appInfo['url'] !== null) {
227
+                $string .= ' (' . $appInfo['url'] . ')';
228
+            }
229
+            return $string;
230
+        } else {
231
+            return null;
232
+        }
233
+    }
234
+
235
+    /**
236
+     * @static
237
+     *
238
+     * @param string $apiKey
239
+     * @param null   $clientInfo
240
+     *
241
+     * @return array
242
+     */
243
+    private static function _defaultHeaders($apiKey, $clientInfo = null)
244
+    {
245
+        $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION;
246
+
247
+        $langVersion = phpversion();
248
+        $uname = php_uname();
249
+
250
+        $appInfo = Telnyx::getAppInfo();
251
+        $ua = [
252
+            'bindings_version' => Telnyx::VERSION,
253
+            'lang' => 'php',
254
+            'lang_version' => $langVersion,
255
+            'publisher' => 'telnyx',
256
+            'uname' => $uname,
257
+        ];
258
+        if ($clientInfo) {
259
+            $ua = array_merge($clientInfo, $ua);
260
+        }
261
+        if ($appInfo !== null) {
262
+            $uaString .= ' ' . self::_formatAppInfo($appInfo);
263
+            $ua['application'] = $appInfo;
264
+        }
265
+
266
+        $defaultHeaders = [
267
+            'X-Telnyx-Client-User-Agent' => json_encode($ua),
268
+            'User-Agent' => $uaString,
269
+            'Authorization' => 'Bearer ' . $apiKey,
270
+        ];
271
+        return $defaultHeaders;
272
+    }
273
+
274
+    /**
275
+     * @param string $method
276
+     * @param string $url
277
+     * @param array  $params
278
+     * @param array  $headers
279
+     *
280
+     * @return array
281
+     * @throws Error\Api
282
+     * @throws Error\ApiConnection
283
+     * @throws Error\Authentication
284
+     */
285
+    private function _requestRaw($method, $url, $params, $headers)
286
+    {
287
+        $myApiKey = $this->_apiKey;
288
+        if (!$myApiKey) {
289
+            $myApiKey = Telnyx::$apiKey;
290
+        }
291
+
292
+        if (!$myApiKey) {
293
+            $msg = 'No API key provided.  (HINT: set your API key using '
294
+              . '"Telnyx::setApiKey(<API-KEY>)".  You can generate API keys from '
295
+              . 'the Telnyx web interface.  See https://developers.telnyx.com/docs/v2/development/authentication '
296
+              . 'for details, or email support@telnyx.com if you have any questions.';
297
+            throw new Error\Authentication($msg);
298
+        }
299
+
300
+        // Clients can supply arbitrary additional keys to be included in the
301
+        // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo()
302
+        // method
303
+        $clientUAInfo = null;
304
+        if (method_exists($this->httpClient(), 'getUserAgentInfo')) {
305
+            $clientUAInfo = $this->httpClient()->getUserAgentInfo();
306
+        }
307
+
308
+        $absUrl = $this->_apiBase.$url;
309
+        $params = self::_encodeObjects($params);
310
+        $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo);
311
+        if (Telnyx::$apiVersion) {
312
+            $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion;
313
+        }
314
+
315
+        if (Telnyx::$accountId) {
316
+            $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId;
317
+        }
318
+
319
+        if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) {
320
+            $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry);
321
+        }
322
+
323
+        $hasFile = false;
324
+        $hasCurlFile = class_exists('\CURLFile', false);
325
+        foreach ($params as $k => $v) {
326
+            if (is_resource($v)) {
327
+                $hasFile = true;
328
+                $params[$k] = self::_processResourceParam($v, $hasCurlFile);
329
+            } elseif ($hasCurlFile && $v instanceof \CURLFile) {
330
+                $hasFile = true;
331
+            }
332
+        }
333
+
334
+        if ($hasFile) {
335
+            $defaultHeaders['Content-Type'] = 'multipart/form-data';
336
+        } else {
337
+            $defaultHeaders['Content-Type'] = 'application/json';
338
+        }
339
+
340
+        $combinedHeaders = array_merge($defaultHeaders, $headers);
341
+        $rawHeaders = [];
342
+
343
+        foreach ($combinedHeaders as $header => $value) {
344
+            $rawHeaders[] = $header . ': ' . $value;
345
+        }
346
+
347
+        $requestStartMs = Util\Util::currentTimeMillis();
348
+
349
+        list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
350
+            $method,
351
+            $absUrl,
352
+            $rawHeaders,
353
+            $params,
354
+            $hasFile
355
+        );
356
+
357
+//        if (array_key_exists('request-id', $rheaders)) {
358
+        if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) {
359
+            self::$requestTelemetry = new RequestTelemetry(
360
+                $rheaders['request-id'],
361
+                Util\Util::currentTimeMillis() - $requestStartMs
362
+            );
363
+        }
364
+
365
+        return [$rbody, $rcode, $rheaders, $myApiKey];
366
+    }
367
+
368
+    /**
369
+     * @param resource $resource
370
+     * @param bool     $hasCurlFile
371
+     *
372
+     * @return \CURLFile|string
373
+     * @throws Error\Api
374
+     */
375
+    private function _processResourceParam($resource, $hasCurlFile)
376
+    {
377
+        if (get_resource_type($resource) !== 'stream') {
378
+            throw new Error\Api(
379
+                'Attempted to upload a resource that is not a stream'
380
+            );
381
+        }
382
+
383
+        $metaData = stream_get_meta_data($resource);
384
+        if ($metaData['wrapper_type'] !== 'plainfile') {
385
+            throw new Error\Api(
386
+                'Only plainfile resource streams are supported'
387
+            );
388
+        }
389
+
390
+        if ($hasCurlFile) {
391
+            // We don't have the filename or mimetype, but the API doesn't care
392
+            return new \CURLFile($metaData['uri']);
393
+        } else {
394
+            return '@'.$metaData['uri'];
395
+        }
396
+    }
397
+
398
+    /**
399
+     * @param string $rbody
400
+     * @param int    $rcode
401
+     * @param array  $rheaders
402
+     *
403
+     * @return mixed
404
+     * @throws Error\Api
405
+     * @throws Error\Authentication
406
+     * @throws Error\Card
407
+     * @throws Error\InvalidRequest
408
+     * @throws Error\Permission
409
+     * @throws Error\RateLimit
410
+     * @throws Error\Idempotency
411
+     */
412
+    private function _interpretResponse($rbody, $rcode, $rheaders)
413
+    {
414
+        $resp = json_decode($rbody, true);
415
+
416
+        // Move [data] to the parent node
417
+        if (isset($resp['data'])) {
418
+            $resp = $resp['data'];
419
+        }
420
+
421
+        $jsonError = json_last_error();
422
+        if ($resp === null && $jsonError !== JSON_ERROR_NONE) {
423
+            $msg = "Invalid response body from API: $rbody "
424
+              . "(HTTP response code was $rcode, json_last_error() was $jsonError)";
425
+            throw new Error\Api($msg, $rcode, $rbody);
426
+        }
427
+
428
+        if ($rcode < 200 || $rcode >= 300) {
429
+            $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp);
430
+        }
431
+        return $resp;
432
+    }
433
+
434
+    /**
435
+     * @static
436
+     *
437
+     * @param HttpClient\ClientInterface $client
438
+     */
439
+    public static function setHttpClient($client)
440
+    {
441
+        self::$_httpClient = $client;
442
+    }
443
+
444
+    /**
445
+     * @static
446
+     *
447
+     * Resets any stateful telemetry data
448
+     */
449
+    public static function resetTelemetry()
450
+    {
451
+        self::$requestTelemetry = null;
452
+    }
453
+
454
+    /**
455
+     * @return HttpClient\ClientInterface
456
+     */
457
+    private function httpClient()
458
+    {
459
+        if (!self::$_httpClient) {
460
+            self::$_httpClient = HttpClient\CurlClient::instance();
461
+        }
462
+        return self::$_httpClient;
463
+    }
464
+}