PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.0.94
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.0.94
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Http / Client.php

Client.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 1.0.94, at vendor/wpfluent/framework/src/WPFluent/Http/Client.php

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