PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.0.13
Kirki – Freeform Page Builder, Website Builder & Customizer v6.0.13
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / libraries / framework / Http / Client / Request.php
kirki / libraries / framework / Http / Client Last commit date
MultipartStream.php 3 weeks ago Request.php 3 weeks ago Response.php 3 weeks ago
Request.php
714 lines
1 <?php
2
3 /**
4 * Fluent HTTP client built on wp_remote_request with chainable headers, body, and auth options.
5 * Supports GET, POST, PUT, PATCH, DELETE and multipart or JSON body formats.
6 * Macroable for extending with custom request helpers.
7 *
8 * @package Framework
9 * @subpackage Http\Client
10 * @since 1.0.0
11 */
12 namespace Kirki\Framework\Http\Client;
13
14 \defined('ABSPATH') || exit;
15 use BadMethodCallException;
16 use Kirki\Framework\Collections\Collection;
17 use Kirki\Framework\Supports\Arr;
18 use Kirki\Framework\Supports\Str;
19 use Kirki\Framework\Supports\Traits\Macroable;
20 use RuntimeException;
21 use function Kirki\Framework\collection;
22 use function Kirki\Framework\Polyfill\str_contains;
23 class Request
24 {
25 /**
26 * The macroable trait.
27 *
28 * @var Macroable
29 */
30 use Macroable {
31 __call as call_macro;
32 }
33 /**
34 * HTTP GET method.
35 */
36 public const GET = 'GET';
37 /**
38 * HTTP POST method.
39 */
40 public const POST = 'POST';
41 /**
42 * HTTP PUT method.
43 */
44 public const PUT = 'PUT';
45 /**
46 * HTTP PATCH method.
47 */
48 public const PATCH = 'PATCH';
49 /**
50 * HTTP DELETE method.
51 */
52 public const DELETE = 'DELETE';
53 /**
54 * HTTP HEAD method.
55 */
56 public const HEAD = 'HEAD';
57 /**
58 * HTTP OPTIONS method.
59 */
60 public const OPTIONS = 'OPTIONS';
61 /**
62 * The base URL for the request.
63 *
64 * @var string
65 *
66 * @since 1.0.0
67 */
68 protected string $base_url = '';
69 /**
70 * The options for the request.
71 *
72 * @var array
73 *
74 * @since 1.0.0
75 */
76 protected array $options = [];
77 /**
78 * The body format for the request.
79 *
80 * @var string
81 *
82 * @since 1.0.0
83 */
84 protected string $body_format;
85 /**
86 * The content for the request.
87 *
88 * @var mixed
89 *
90 * @since 1.0.0
91 */
92 protected $content = null;
93 /**
94 * The files for the request.
95 *
96 * @var array
97 *
98 * @since 1.0.0
99 */
100 protected $files = [];
101 /**
102 * The HTTP methods for the request.
103 *
104 * @var array
105 *
106 * @since 1.0.0
107 */
108 protected array $methods = [self::GET, self::POST, self::PUT, self::PATCH, self::DELETE, self::HEAD, self::OPTIONS];
109 /**
110 * The constructor.
111 *
112 * @return void
113 *
114 * @since 1.0.0
115 */
116 public function __construct()
117 {
118 $this->options = ['method' => 'GET', 'timeout' => 30, 'redirection' => 5, 'httpversion' => '1.0', 'reject_unsafe_url' => \false, 'blocking' => \true, 'cookies' => [], 'body' => null, 'compress' => \false, 'decompress' => \true, 'sslverify' => \true, 'stream' => \false, 'filename' => null, 'limit_response_size' => null, 'headers' => []];
119 $this->as_json();
120 }
121 /**
122 * Set the base URL for the request.
123 *
124 * @param string $url The url.
125 *
126 * @return $this
127 *
128 * @since 1.0.0
129 */
130 public function base_url(string $url)
131 {
132 $this->base_url = $url;
133 return $this;
134 }
135 /**
136 * Get the base URL for the request.
137 *
138 * @return string
139 *
140 * @since 1.0.0
141 */
142 public function get_base_url()
143 {
144 return $this->base_url;
145 }
146 /**
147 * Set the headers for the request.
148 *
149 * @param array $headers The headers.
150 *
151 * @return $this
152 *
153 * @since 1.0.0
154 */
155 public function with_headers(array $headers)
156 {
157 $this->options = \array_merge_recursive($this->options, ['headers' => $headers]);
158 return $this;
159 }
160 /**
161 * Replace the headers for the request.
162 *
163 * @param array $headers The headers.
164 *
165 * @return $this
166 *
167 * @since 1.0.0
168 */
169 public function replace_headers(array $headers)
170 {
171 $this->options['headers'] = \array_merge($this->options['headers'] ?? [], $headers);
172 return $this;
173 }
174 /**
175 * Set the token for the request.
176 *
177 * @param string $token The token.
178 * @param mixed $type The type.
179 *
180 * @return $this
181 *
182 * @since 1.0.0
183 */
184 public function with_token(string $token, $type = 'Bearer')
185 {
186 return $this->with_headers(['Authorization' => \trim($type . ' ' . $token)]);
187 }
188 /**
189 * Set the user agent for the request.
190 *
191 * @param mixed $user_agent The user agent.
192 *
193 * @return $this
194 *
195 * @since 1.0.0
196 */
197 public function with_user_agent($user_agent)
198 {
199 return $this->with_headers(['User-Agent' => \trim($user_agent)]);
200 }
201 /**
202 * Disable SSL verification for the request.
203 *
204 * @return $this
205 *
206 * @since 1.0.0
207 */
208 public function without_verifying()
209 {
210 $this->options['sslverify'] = \false;
211 return $this;
212 }
213 /**
214 * Set the method for the request.
215 *
216 * @param mixed $method The method name.
217 *
218 * @return $this
219 *
220 * @since 1.0.0
221 */
222 public function method($method)
223 {
224 $this->options['method'] = $method;
225 return $this;
226 }
227 /**
228 * Set the accept header for the request.
229 *
230 * @param mixed $value The value.
231 *
232 * @return $this
233 *
234 * @since 1.0.0
235 */
236 public function accept($value)
237 {
238 return $this->with_headers(['Accept' => $value]);
239 }
240 /**
241 * Set the accept header for the request to JSON.
242 *
243 * @return $this
244 *
245 * @since 1.0.0
246 */
247 public function accept_json()
248 {
249 return $this->accept('application/json');
250 }
251 /**
252 * Set the body format for the request.
253 *
254 * @param string $format The format.
255 *
256 * @return $this
257 *
258 * @since 1.0.0
259 */
260 public function body_format(string $format)
261 {
262 $this->body_format = $format;
263 return $this;
264 }
265 /**
266 * Set the body for the request.
267 *
268 * @param mixed $content The content.
269 * @param mixed $type The type.
270 *
271 * @return $this
272 *
273 * @since 1.0.0
274 */
275 public function with_body($content, $type = 'application/json')
276 {
277 $this->body_format = 'body';
278 $this->content = $content;
279 $this->content_type($type);
280 return $this;
281 }
282 /**
283 * Set the body format for the request to JSON.
284 *
285 * @return $this
286 *
287 * @since 1.0.0
288 */
289 public function as_json()
290 {
291 return $this->body_format('json')->content_type('application/json');
292 }
293 /**
294 * Set the body format for the request to form.
295 *
296 * @return $this
297 *
298 * @since 1.0.0
299 */
300 public function as_form()
301 {
302 return $this->body_format('form')->content_type('application/x-www-form-urlencoded');
303 }
304 /**
305 * Set the body format for the request to multipart.
306 *
307 * @return $this
308 *
309 * @since 1.0.0
310 */
311 public function as_multipart()
312 {
313 return $this->body_format('multipart')->content_type('multipart/form-data');
314 }
315 /**
316 * Attach a file to the request.
317 *
318 * @param mixed $name The name.
319 * @param mixed $contents The contents.
320 * @param mixed $filename The filename.
321 * @param array $headers The headers.
322 *
323 * @return $this
324 *
325 * @since 1.0.0
326 */
327 public function attach($name, $contents = '', $filename = null, array $headers = [])
328 {
329 if (\is_array($name)) {
330 foreach ($name as $file) {
331 $this->attach(...$file);
332 }
333 return $this;
334 }
335 $this->as_multipart();
336 $this->files[] = \array_filter(['name' => $name, 'contents' => $contents, 'headers' => $headers, 'filename' => $filename]);
337 return $this;
338 }
339 /**
340 * Set the content type for the request.
341 *
342 * @param mixed $type The type.
343 *
344 * @return $this
345 *
346 * @since 1.0.0
347 */
348 public function content_type($type)
349 {
350 $this->options['headers'] ??= [];
351 $this->options['headers']['Content-Type'] = $type;
352 return $this;
353 }
354 /**
355 * Set the timeout for the request.
356 *
357 * @param mixed $seconds The seconds.
358 *
359 * @return $this
360 *
361 * @since 1.0.0
362 */
363 public function timeout($seconds)
364 {
365 $this->options['timeout'] = $seconds;
366 return $this;
367 }
368 /**
369 * Set the options for the request.
370 *
371 * @param array $options The options array.
372 *
373 * @return $this
374 *
375 * @since 1.0.0
376 */
377 public function with_options(array $options)
378 {
379 $this->options = \array_replace_recursive(\array_merge_recursive($this->options, Arr::only($options, ['headers', 'json', 'multipart', 'query', 'cookies'])), $options);
380 return $this;
381 }
382 /**
383 * Set the body for the request.
384 *
385 * @param mixed $data The data payload.
386 *
387 * @return $this
388 *
389 * @since 1.0.0
390 */
391 protected function with_data($data)
392 {
393 $this->options['body'] = $data;
394 return $this;
395 }
396 /**
397 * Send a GET request.
398 *
399 * @param string $url The url.
400 * @param mixed $query The query builder instance.
401 *
402 * @return Response
403 *
404 * @since 1.0.0
405 */
406 public function get(string $url, $query = null)
407 {
408 return $this->send('GET', $url, \func_num_args() === 1 ? [] : ['query' => $query]);
409 }
410 /**
411 * Send a HEAD request.
412 *
413 * @param string $url The url.
414 * @param mixed $query The query builder instance.
415 *
416 * @return Response
417 *
418 * @since 1.0.0
419 */
420 public function head(string $url, $query = null)
421 {
422 return $this->send('HEAD', $url, \func_num_args() === 1 ? [] : ['query' => $query]);
423 }
424 /**
425 * Send a POST request.
426 *
427 * @param string $url The url.
428 * @param mixed $data The data payload.
429 *
430 * @return Response
431 *
432 * @since 1.0.0
433 */
434 public function post(string $url, $data = [])
435 {
436 return $this->send('POST', $url, \func_num_args() === 1 ? [] : [$this->body_format => $data]);
437 }
438 /**
439 * Send a PUT request.
440 *
441 * @param string $url The url.
442 * @param mixed $data The data payload.
443 *
444 * @return Response
445 *
446 * @since 1.0.0
447 */
448 public function put(string $url, $data = [])
449 {
450 return $this->send('PUT', $url, \func_num_args() === 1 ? [] : [$this->body_format => $data]);
451 }
452 /**
453 * Send a PATCH request.
454 *
455 * @param string $url The url.
456 * @param mixed $data The data payload.
457 *
458 * @return Response
459 *
460 * @since 1.0.0
461 */
462 public function patch(string $url, $data = [])
463 {
464 return $this->send('PATCH', $url, \func_num_args() === 1 ? [] : [$this->body_format => $data]);
465 }
466 /**
467 * Send a DELETE request.
468 *
469 * @param string $url The url.
470 * @param mixed $data The data payload.
471 *
472 * @return Response
473 *
474 * @since 1.0.0
475 */
476 public function delete(string $url, $data = [])
477 {
478 return $this->send('DELETE', $url, \func_num_args() === 1 ? [] : [$this->body_format => $data]);
479 }
480 /**
481 * Send an OPTIONS request.
482 *
483 * @param string $url The url.
484 * @param mixed $data The data payload.
485 *
486 * @return Response
487 *
488 * @since 1.0.0
489 */
490 public function options(string $url, $data = [])
491 {
492 return $this->send('OPTIONS', $url, \func_num_args() === 1 ? [] : [$this->body_format => $data]);
493 }
494 /**
495 * Send a request.
496 *
497 * @param string $method The method name.
498 * @param string $url The url.
499 * @param array $options The options array.
500 *
501 * @return Response
502 *
503 * @since 1.0.0
504 */
505 public function send(string $method, string $url, array $options = [])
506 {
507 if (!Str::starts_with($url, ['http', 'https'])) {
508 $url = \ltrim(\rtrim($this->base_url, '/') . '/' . \ltrim($url, '/'), '/');
509 }
510 $this->method($method);
511 return $this->send_request($method, $url, $options);
512 }
513 /**
514 * Send a request.
515 *
516 * @param string $method The method name.
517 * @param string $url The url.
518 * @param array $options The options array.
519 *
520 * @return Response
521 *
522 * @throws \RuntimeException
523 *
524 * @since 1.0.0
525 */
526 protected function send_request(string $method, string $url, array $options = [])
527 {
528 if (!$this->is_valid_method($method)) {
529 throw new RuntimeException(\sprintf('Invalid HTTP method: %s', $method));
530 }
531 $data = $this->parse_request_data($method, $url, $options);
532 $url = $this->prepare_request_url($method, $url, $data);
533 $body = $this->prepare_request_body($method, $data);
534 $args = $this->prepare_request_args($body);
535 $response = wp_remote_request($url, $args);
536 return $this->new_response($response);
537 }
538 /**
539 * Prepare request url.
540 *
541 * @param string $method The method name.
542 * @param string $url The url.
543 * @param array $data The data payload.
544 *
545 * @return void
546 *
547 * @since 1.0.0
548 */
549 protected function prepare_request_url(string $method, string $url, array $data)
550 {
551 if (!\in_array($method, ['GET', 'HEAD'])) {
552 return $url;
553 }
554 if (empty($data)) {
555 return $url;
556 }
557 return $url . '?' . \http_build_query($data);
558 }
559 /**
560 * Create a new response instance.
561 *
562 * @param mixed $response The response instance.
563 *
564 * @return Response
565 *
566 * @since 1.0.0
567 */
568 protected function new_response($response)
569 {
570 return new Response($response);
571 }
572 /**
573 * Parse the request data.
574 *
575 * @param string $method The method name.
576 * @param string $url The url.
577 * @param array $options The options array.
578 *
579 * @return array
580 *
581 * @since 1.0.0
582 */
583 protected function parse_request_data(string $method, string $url, array $options = [])
584 {
585 if ($this->body_format === 'body') {
586 return [];
587 }
588 $request_data = $options[$this->body_format] ?? $options['query'] ?? [];
589 if (empty($request_data) && $method === 'GET' && str_contains($url, '?')) {
590 $request_data = \substr($url, \strpos($url, '?') + 1);
591 }
592 if (\is_string($request_data)) {
593 \parse_str($request_data, $parsed_data);
594 $request_data = \is_array($parsed_data) ? $parsed_data : [];
595 }
596 return \is_array($request_data) ? $request_data : [];
597 }
598 /**
599 * Prepare the request body.
600 *
601 * @param string $method The method name.
602 * @param array $data The data payload.
603 *
604 * @return string|array
605 *
606 * @throws \RuntimeException
607 *
608 * @since 1.0.0
609 */
610 protected function prepare_request_body(string $method, array $data)
611 {
612 if (\in_array($method, ['GET', 'HEAD'])) {
613 return [];
614 }
615 switch ($this->body_format) {
616 case 'json':
617 return empty($data) ? '' : Arr::json_encode($data);
618 case 'body':
619 return $this->content ?? [];
620 case 'form':
621 return $data;
622 case 'multipart':
623 return $this->make_multipart_body($data);
624 default:
625 throw new RuntimeException(\sprintf('Invalid body format: %s', $this->body_format));
626 }
627 }
628 /**
629 * Make a multipart body.
630 *
631 * @param array $data The data payload.
632 *
633 * @return string
634 *
635 * @since 1.0.0
636 */
637 protected function make_multipart_body(array $data)
638 {
639 $data = $this->prepare_multipart_data($data);
640 $stream = new MultipartStream($data);
641 $boundary = $stream->boundary();
642 $this->content_type("multipart/form-data; boundary={$boundary}");
643 return $stream->payload();
644 }
645 /**
646 * Prepare the multipart data.
647 *
648 * @param array $data The data payload.
649 *
650 * @return array
651 *
652 * @since 1.0.0
653 */
654 protected function prepare_multipart_data(array $data)
655 {
656 return collection($data)->flat_map(function ($item, $key) {
657 if (\is_array($item)) {
658 if (isset($item['name']) && isset($item['contents'])) {
659 return $item;
660 }
661 return collection($item)->map(function ($value) use($key) {
662 return ['name' => $key . '[]', 'contents' => $value];
663 })->all();
664 }
665 return [['name' => $key, 'contents' => $item]];
666 })->values()->merge($this->files ?? [])->all();
667 }
668 /**
669 * Prepare the request arguments.
670 *
671 * @param mixed $data The data payload.
672 *
673 * @return array
674 *
675 * @since 1.0.0
676 */
677 protected function prepare_request_args($data)
678 {
679 return \array_merge($this->options, ['body' => $data ?: null]);
680 }
681 /**
682 * Check if the method is valid.
683 *
684 * @param string $method The method name.
685 *
686 * @return bool
687 *
688 * @since 1.0.0
689 */
690 protected function is_valid_method(string $method)
691 {
692 return \in_array(\strtoupper($method), $this->methods, \true);
693 }
694 /**
695 * Call a macro.
696 *
697 * @param mixed $method The method name.
698 * @param mixed $parameters The parameters array.
699 *
700 * @return mixed
701 *
702 * @throws \BadMethodCallException
703 *
704 * @since 1.0.0
705 */
706 public function __call($method, $parameters)
707 {
708 if (static::has_macro($method)) {
709 return $this->call_macro($method, $parameters);
710 }
711 throw new BadMethodCallException(\sprintf('Call to undefined method %s::%s', static::class, $method));
712 }
713 }
714