PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.7.1
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.7.1
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Http / Client.php

Client.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.7.1, at vendor/wpfluent/framework/src/WPFluent/Http/Client.php

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