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