PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.1.2
Fluent Support – Helpdesk & Customer Support Ticket System v2.1.2
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
fluent-support / vendor / wpfluent / framework / src / WPFluent / Http / Client.php

Client.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.1.2, at vendor/wpfluent/framework/src/WPFluent/Http/Client.php

631 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\Framework\Http;
4
5 use Exception;
6 use BadMethodCallException;
7 use FluentSupport\Framework\Support\Arr;
8 use FluentSupport\Framework\Support\Str;
9 use FluentSupport\Framework\Foundation\App;
10 use FluentSupport\Framework\Http\Request\File;
11
12 /**
13 * @method mixed get(string $url, array $params = []) Send a GET request.
14 * @method mixed post(string $url, array $params = []) Send a POST request.
15 * @method mixed put(string $url, array $params = []) Send a PUT request.
16 * @method mixed patch(string $url, array $params = []) Send a PATCH request.
17 * @method mixed delete(string $url, array $params = []) Send a DELETE request.
18 * @method mixed head(string $url, array $params = []) Send a HEAD request.
19 * @method mixed options(string $url, array $params = []) Send an OPTIONS request.
20 * @method File download(string|File $url) Download a remote file.
21 * @method mixed upload(string $url, string $path, array $fields = [], string $name = 'file') Upload a file to a remote server.
22 */
23
24
25 /**
26 * Some examples:
27 *
28 * // Using an instance:
29 * $client = Client::make('https://example.com');
30 *
31 * $response1 = $client->get('/users');
32 * $response2 = $client->post('/users', ['body' => ['name' => 'Heera']]);
33 * $response3 = $client->put('/users/1', ['body' => ['name' => 'Updated']]);
34 * $response4 = $client->patch('/users/1', [
35 * 'body' => ['email' => 'updated@example.com']]
36 * );
37 * $response5 = $client->delete('/users/1');
38 * $response6 = $client->head('/users');
39 * $response7 = $client->options('/users');
40 *
41 * // Using statically (default base URL is empty):
42 * $response8 = Client::get('https://example.com/users');
43 * $response9 = Client::post('https://example.com/users', [
44 * 'body' => ['name' => 'Static']]
45 * );
46 */
47
48
49 class Client
50 {
51 /**
52 * Base URl for the request.
53 *
54 * @var string
55 */
56 protected $baseUrl = '';
57
58 /**
59 * Cookies to send with the request.
60 *
61 * @var array
62 */
63 protected $cookies = [];
64
65 /**
66 * Headers to send with the request.
67 *
68 * @var array
69 */
70 protected $headers = [];
71
72 /**
73 * Options to set for the request.
74 *
75 * @var array
76 */
77 protected $options = [];
78
79 /**
80 * Request body|Data|params to set in the request.
81 *
82 * @var array
83 */
84 protected $body = [];
85
86 /**
87 * Request query params to pass with the url.
88 *
89 * @var array
90 */
91 protected $query = [];
92
93 /**
94 * Stores args temporarily for then().
95 *
96 * @var null|array
97 */
98 private $args = null;
99
100 /**
101 * Create a new HTTP client.
102 *
103 * @param string $baseUrl
104 * @param array $args
105 */
106 public function __construct($baseUrl = '', $args = [])
107 {
108 $this->baseUrl = rtrim($baseUrl, '/');
109 $this->cookies = $args['cookies'] ?? [];
110 $this->headers = $args['headers'] ?? [];
111 $this->options = $args['options'] ?? [];
112 }
113
114 /**
115 * Create a new HTTP client.
116 *
117 * @param string $baseUrl
118 * @param array $args
119 */
120 public static function make($baseUrl = '', $args = [])
121 {
122 $args['cookies'] = $args['cookies'] ?? [];
123 $args['headers'] = $args['headers'] ?? [];
124 $args['options'] = $args['options'] ?? [];
125 return new static($baseUrl, $args);
126 }
127
128 /**
129 * Sets one or more options.
130 *
131 * @return self
132 */
133 public function withOption($key, $value = null)
134 {
135 $options = is_array($key) ? $key : [$key => $value];
136
137 foreach ($options as $key => $value) {
138 $this->options[$key] = $value;
139 }
140
141 return $this;
142 }
143
144 /**
145 * Sets the blocking option to false (non-blocking).
146 *
147 * @return self
148 */
149 public function async()
150 {
151 return $this->withOption('blocking', false);
152 }
153
154 /**
155 * Sets the sslverify option.
156 *
157 * @return self
158 */
159 public function secure($verify = true)
160 {
161 return $this->withOption('sslverify', $verify);
162 }
163
164 /**
165 * Sets one or more headers.
166 *
167 * @return self
168 */
169 public function withHeader($key, $value = null)
170 {
171 $headers = is_array($key) ? $key : [$key => $value];
172
173 foreach ($headers as $key => $value) {
174 $this->headers[$key] = $value;
175 }
176
177 return $this;
178 }
179
180 /**
181 * Sets one or more headers.
182 *
183 * @param array $headers
184 * @return self
185 */
186 public function withHeaders(array $headers)
187 {
188 return $this->withHeader($headers);
189 }
190
191 /**
192 * Sets the Authorization header.
193 *
194 * @param string $token
195 * @param string $type
196 * @return self
197 */
198 public function withToken($token, $type = 'Bearer')
199 {
200 return $this->withHeader([
201 'Authorization' => $type . ' ' . $token,
202 ]);
203 }
204
205 /**
206 * Sets one or more cookies.
207 *
208 * @return self
209 */
210 public function withCookie($key, $value = null)
211 {
212 $cookies = is_array($key) ? $key : [$key => $value];
213
214 foreach ($cookies as $key => $value) {
215 $this->cookies[$key] = $value;
216 }
217
218 return $this;
219 }
220
221 /**
222 * Sets one or more request body param.
223 *
224 * @return self
225 */
226 public function withData($key, $value = null)
227 {
228 $data = is_array($key) ? $key : [$key => $value];
229
230 foreach ($data as $key => $value) {
231 $this->body[$key] = $value;
232 }
233
234 return $this;
235 }
236
237 /**
238 * Sets one or more request body param.
239 *
240 * @return self
241 */
242 public function withBody($key, $value = null)
243 {
244 return $this->withData($key, $value);
245 }
246
247 /**
248 * Sets one or more request body param.
249 *
250 * @return self
251 */
252 public function withParam($key, $value = null)
253 {
254 return $this->withData($key, $value);
255 }
256
257 /**
258 * Sets one or more request body param.
259 *
260 * @return self
261 */
262 public function withQuery($key, $value = null)
263 {
264 $data = is_array($key) ? $key : [$key => $value];
265
266 foreach ($data as $key => $value) {
267 $this->query[$key] = $value;
268 }
269
270 return $this;
271 }
272
273 /**
274 * Allows users to enable streaming on their requests.
275 *
276 * @return self
277 */
278 public function withStreaming($callback = null)
279 {
280 return $this->withOption(
281 'stream', true
282 )->withOption('stream_callback', $callback);
283 }
284
285 /**
286 * Build the request arguments.
287 *
288 * @param array $params
289 * @param string $method
290 * @return array
291 */
292 protected function buildRequestArgs($params, $method)
293 {
294 $defaultParams = [
295 'body' => [],
296 'cookies' => [],
297 'headers' => [],
298 ];
299
300 $params = wp_parse_args($params[0] ?? [], $defaultParams);
301
302 $options = array_merge($this->options, $params['options'] ?? []);
303
304 if (Str::isJson($params['body'])) {
305 $this->withHeader('Content-Type', 'application/json');
306 $params['body'] = json_decode($params['body'], true);
307 }
308
309 $params = [
310 'method' => strtoupper($method),
311 'body' => array_merge($this->body, $params['body']),
312 'cookies' => array_merge($this->cookies, $params['cookies']),
313 'headers' => array_merge($this->headers, $params['headers']),
314 ];
315
316 foreach($options as $key => $value) {
317 $params[$key] = $value;
318 }
319
320 return $params;
321 }
322
323 /**
324 * Send the request.
325 *
326 * @param string $url
327 * @param array $args
328 * @return \FluentSupport\Framework\Http\Response
329 */
330 protected function request($url, $args = [])
331 {
332 return $this->dispatch(
333 $this->resolveUrl($url),
334 $this->filterArgs($args)
335 );
336 }
337
338 /**
339 * Build the URL.
340 *
341 * @param string $url
342 * @return string
343 */
344 protected function resolveUrl($url)
345 {
346 $q = $this->query;
347
348 $parsedUrl = parse_url($url);
349
350 $delimiter = isset($parsedUrl['query']) ? '&' : '?';
351
352 return $url . ($q ? $delimiter . http_build_query($q) : '');
353
354 }
355
356 /**
357 * Filter the args before sending the request.
358 *
359 * @param array $args
360 * @return array
361 */
362 protected function filterArgs($args)
363 {
364 if ($this->shouldBeJson($args)) {
365 $args['body'] = json_encode($args['body']);
366 }
367
368 return $args;
369 }
370
371 /**
372 * Encode the body if Content-Type is JSON.
373 *
374 * @param array $params
375 * @return bool
376 */
377 protected function shouldBeJson($params)
378 {
379 return isset(
380 $params['headers']['Content-Type']
381 ) && $params['headers']['Content-Type'] === 'application/json';
382 }
383
384 /**
385 * Dispatch the request.
386 *
387 * @param string $url
388 * @param array $args
389 * @return array (response)
390 */
391 protected function dispatch($url, $args)
392 {
393 $response = wp_remote_request($url, $args);
394
395 if (is_wp_error($response)) {
396 throw new Exception($response->get_error_message(), 500);
397 }
398
399 $this->mergeCookies($response);
400
401 if ($this->isStreamEnabled($args)) {
402 return $this->makeStreamResponse($response);
403 }
404
405 return $this->makeResponse($response);
406 }
407
408 /**
409 * Merge the coolkies (useful for stateful request).
410 *
411 * @return void
412 */
413 protected function mergeCookies($response)
414 {
415 $this->cookies = array_merge(
416 $this->cookies,
417 wp_remote_retrieve_cookies($response)
418 );
419 }
420
421 /**
422 * Check if the stream is enabled.
423 * @return boolean
424 */
425 protected function isStreamEnabled($args)
426 {
427 return isset($args['stream']) && $args['stream'];
428 }
429
430 /**
431 * Build a response object from an anonymous class.
432 *
433 * @param array $response
434 * @return \FluentSupport\Framework\Http\Response
435 */
436 protected function makeResponse($response)
437 {
438 return new Response($response);
439 }
440
441 /**
442 * Handle the streaming response and process chunks.
443 *
444 * @param array $response
445 * @return \FluentSupport\Framework\Http\Response|null
446 */
447 protected function makeStreamResponse($response)
448 {
449 $response['body'] = $response['filename'];
450
451 return new class($response) extends Response implements \ArrayAccess {
452 public function flush() {
453 $source = fopen($this->response['body'], 'rb');
454
455 $fp = fopen('php://output', 'wb');
456
457 while (!feof($source)) {
458 $data = fread($source, 8192);
459 fwrite($fp, $data);
460 ob_flush();
461 flush();
462 }
463
464 fclose($source);
465 fclose($fp);
466 }
467
468 #[ReturnTypeWillChange]
469 public function offsetGet($offset) {
470 return $this->response[$offset] ?? null;
471 }
472 #[ReturnTypeWillChange]
473 public function offsetSet($offset, $value) {
474 $this->response[$offset] = $value;
475 }
476 #[ReturnTypeWillChange]
477 public function offsetExists($offset) {}
478 #[ReturnTypeWillChange]
479 public function offsetUnset($offset) {}
480 };
481 }
482
483 /**
484 * Download a remote file.
485 *
486 * @param string $url
487 * @return \FluentSupport\Framework\Http\Request\File
488 * @throws \Exception
489 */
490 public function downloadFile($url)
491 {
492 if (!function_exists('download_url')) {
493 require_once ABSPATH . 'wp-admin/includes/file.php';
494 }
495
496 $parsed = parse_url($url);
497
498 if (!isset($parsed['scheme'])) {
499 $url = trim($this->baseUrl, '/') . '/' . trim($url, '/');
500 }
501
502 if (is_wp_error($file = download_url($url))) {
503 throw new Exception($file->get_error_message(), 500);
504 }
505
506 add_action('shutdown', function () use ($file) {
507 @unlink($file);
508 });
509
510 return new File(
511 $file,
512 basename($url),
513 mime_content_type($file) ?: 'application/octet-stream',
514 filesize($file),
515 UPLOAD_ERR_OK
516 );
517 }
518
519 /**
520 * Upload a file to a remote server.
521 *
522 * @param string $url
523 * @param string $path
524 * @param array $fields
525 * @param string $name
526 * @return \FluentSupport\Framework\Http\Response
527 */
528 public function uploadFile($url, $path, $fields = [], $name = 'file')
529 {
530 $path = $path instanceof File ? $path->getPathname() : $path;
531
532 if (!file_exists($path)) {
533 throw new Exception('File does not exist.', 500);
534 }
535
536 // Auto prepend base URL if $url is relative
537 if (strpos($url, 'http') !== 0) {
538 $url = rtrim($this->baseUrl, '/') . '/' . ltrim($url, '/');
539 }
540
541 $boundary = wp_generate_password(24, false);
542
543 $headers = array_merge(
544 $this->headers,
545 [
546 'Accept' => '*/*',
547 'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
548 ]
549 );
550
551 $fileName = basename($path);
552 $content = file_get_contents($path);
553 $mime = mime_content_type($path);
554
555 $body = '';
556
557 foreach ($fields as $key => $value) {
558 $body .= "--" . $boundary . "\r\n";
559 $body .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n\r\n";
560 $body .= $value . "\r\n";
561 }
562
563 $body .= "--" . $boundary . "\r\n";
564 $body .= 'Content-Disposition: form-data; name="'.$name.'"; filename="' . $fileName . '"' . "\r\n";
565 $body .= 'Content-Type: ' . $mime . "\r\n\r\n";
566 $body .= $content . "\r\n";
567 $body .= "--" . $boundary . "--\r\n";
568
569 $response = wp_remote_post($url, [
570 'headers' => $headers,
571 'body' => $body,
572 'timeout' => 60,
573 ]);
574
575 return $this->makeResponse($response);
576 }
577
578 protected function checkIfValidHttpMethod($method)
579 {
580 $validHttpMethods = [
581 'get', 'post', 'put', 'delete', 'patch', 'options', 'head'
582 ];
583
584 if (!in_array(strtolower($method), $validHttpMethods)) {
585 throw new BadMethodCallException("Method $method does not exist.");
586 }
587 }
588
589 /**
590 * Handles the dynamic calls.
591 *
592 * @param string $method
593 * @param array $args
594 * @return mixed
595 */
596 public function __call($method, $args)
597 {
598 if ($method === 'download') {
599 return $this->downloadFile(...$args);
600 }
601
602 // Handles dynamic method calls like:
603 // get, post and so on.
604 $url = array_shift($args);
605
606 $parsed = parse_url($url);
607
608 if (!isset($parsed['scheme'])) {
609 $url = trim($this->baseUrl, '/') . '/' . trim($url, '/');
610 }
611
612 $this->checkIfValidHttpMethod($method);
613
614 return $this->request(
615 $url, $this->buildRequestArgs($args, $method)
616 );
617 }
618
619 /**
620 * Handle the static dynamic calls.
621 *
622 * @param string $method
623 * @param array $args
624 * @return self
625 */
626 public static function __callStatic($method, $args)
627 {
628 return static::make()->$method(...$args);
629 }
630 }
631