PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
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
← All changes | vendor/wpfluent/framework/src/WPFluent/Http/Client.php +232 -29 1.10.02 → 2.5.0 View file →
@@ -4,8 +4,9 @@
4 4
5 5 use Exception;
6 6 use BadMethodCallException;
7 7 use FluentBooking\Framework\Support\Arr;
8 +use FluentBooking\Framework\Support\Str;
8 9 use FluentBooking\Framework\Foundation\App;
9 10 use FluentBooking\Framework\Http\Request\File;
10 11
11 12 /**
@@ -83,16 +84,27 @@
83 84 protected $body = [];
84 85
85 86 /**
86 87 * Request query params to pass with the url.
87 - *
88 + *
88 89 * @var array
89 90 */
90 91 protected $query = [];
91 92
92 93 /**
94 + * Whether to reject unsafe URLs via wp_safe_remote_request.
95 + * When true, dispatch() uses wp_safe_remote_request() instead of
96 + * wp_remote_request(), which runs the URL through wp_http_validate_url()
97 + * and refuses loopback addresses, private IP ranges, and non-standard
98 + * ports. Off by default for backwards compatibility with internal calls.
99 + *
100 + * @var bool
101 + */
102 + protected $useSafeRemote = false;
103 +
104 + /**
93 105 * Stores args temporarily for then().
94 - *
106 + *
95 107 * @var null|array
96 108 */
97 109 private $args = null;
98 110
@@ -151,9 +163,9 @@
151 163 }
152 164
153 165 /**
154 166 * Sets the sslverify option.
155 - *
167 + *
156 168 * @return self
157 169 */
158 170 public function secure($verify = true)
159 171 {
@@ -160,8 +172,27 @@
160 172 return $this->withOption('sslverify', $verify);
161 173 }
162 174
163 175 /**
176 + * Route dispatch through wp_safe_remote_request() instead of
177 + * wp_remote_request() — mirrors WordPress's wp_safe_remote_* family.
178 + *
179 + * Runs the URL through wp_http_validate_url() first, which refuses
180 + * loopback addresses, private IP ranges, and non-standard ports. Use
181 + * when the target URL is user/admin-configurable and you need SSRF
182 + * protection. Standard external HTTPS endpoints on ports 80/443/8080
183 + * are unaffected.
184 + *
185 + * @param bool $enabled
186 + * @return self
187 + */
188 + public function safe($enabled = true)
189 + {
190 + $this->useSafeRemote = $enabled;
191 + return $this;
192 + }
193 +
194 + /**
164 195 * Sets one or more headers.
165 196 *
166 197 * @return self
167 198 */
@@ -176,8 +207,33 @@
176 207 return $this;
177 208 }
178 209
179 210 /**
211 + * Sets one or more headers.
212 + *
213 + * @param array $headers
214 + * @return self
215 + */
216 + public function withHeaders(array $headers)
217 + {
218 + return $this->withHeader($headers);
219 + }
220 +
221 + /**
222 + * Sets the Authorization header.
223 + *
224 + * @param string $token
225 + * @param string $type
226 + * @return self
227 + */
228 + public function withToken($token, $type = 'Bearer')
229 + {
230 + return $this->withHeader([
231 + 'Authorization' => $type . ' ' . $token,
232 + ]);
233 + }
234 +
235 + /**
180 236 * Sets one or more cookies.
181 237 *
182 238 * @return self
183 239 */
@@ -244,8 +300,20 @@
244 300 return $this;
245 301 }
246 302
247 303 /**
304 + * Allows users to enable streaming on their requests.
305 + *
306 + * @return self
307 + */
308 + public function withStreaming($callback = null)
309 + {
310 + return $this->withOption(
311 + 'stream', true
312 + )->withOption('stream_callback', $callback);
313 + }
314 +
315 + /**
248 316 * Build the request arguments.
249 317 *
250 318 * @param array $params
251 319 * @param string $method
@@ -262,8 +330,13 @@
262 330 $params = wp_parse_args($params[0] ?? [], $defaultParams);
263 331
264 332 $options = array_merge($this->options, $params['options'] ?? []);
265 333
334 + if (Str::isJson($params['body'])) {
335 + $this->withHeader('Content-Type', 'application/json');
336 + $params['body'] = json_decode($params['body'], true);
337 + }
338 +
266 339 $params = [
267 340 'method' => strtoupper($method),
268 341 'body' => array_merge($this->body, $params['body']),
269 342 'cookies' => array_merge($this->cookies, $params['cookies']),
@@ -269,9 +342,8 @@
269 342 'cookies' => array_merge($this->cookies, $params['cookies']),
270 343 'headers' => array_merge($this->headers, $params['headers']),
271 344 ];
272 345
273 -
274 346 foreach($options as $key => $value) {
275 347 $params[$key] = $value;
276 348 }
277 349
@@ -286,27 +358,161 @@
286 358 * @return \FluentBooking\Framework\Http\Response
287 359 */
288 360 protected function request($url, $args = [])
289 361 {
362 + return $this->dispatch(
363 + $this->resolveUrl($url),
364 + $this->filterArgs($args)
365 + );
366 + }
367 +
368 + /**
369 + * Build the URL.
370 + *
371 + * @param string $url
372 + * @return string
373 + */
374 + protected function resolveUrl($url)
375 + {
376 + $q = $this->query;
377 +
290 378 $parsedUrl = parse_url($url);
379 +
291 380 $delimiter = isset($parsedUrl['query']) ? '&' : '?';
292 - $url .= $this->query ? $delimiter . http_build_query($this->query) : '';
381 +
382 + return $url . ($q ? $delimiter . http_build_query($q) : '');
293 383
294 - $response = wp_remote_request($url, $args);
384 + }
295 385
296 - if (is_wp_error($response)) {
297 - throw new Exception($response->get_error_message(), 500);
298 - }
299 -
386 + /**
387 + * Filter the args before sending the request.
388 + *
389 + * @param array $args
390 + * @return array
391 + */
392 + protected function filterArgs($args)
393 + {
394 + if ($this->shouldBeJson($args)) {
395 + $args['body'] = json_encode($args['body']);
396 + }
397 +
398 + return $args;
399 + }
400 +
401 + /**
402 + * Encode the body if Content-Type is JSON.
403 + *
404 + * @param array $params
405 + * @return bool
406 + */
407 + protected function shouldBeJson($params)
408 + {
409 + return isset(
410 + $params['headers']['Content-Type']
411 + ) && $params['headers']['Content-Type'] === 'application/json';
412 + }
413 +
414 + /**
415 + * Dispatch the request.
416 + *
417 + * @param string $url
418 + * @param array $args
419 + * @return array (response)
420 + */
421 + protected function dispatch($url, $args)
422 + {
423 + $fn = $this->useSafeRemote ? 'wp_safe_remote_request' : 'wp_remote_request';
424 + $response = $fn($url, $args);
425 +
426 + if (is_wp_error($response)) {
427 + throw new Exception($response->get_error_message(), 500);
428 + }
429 +
430 + $this->mergeCookies($response);
431 +
432 + if ($this->isStreamEnabled($args)) {
433 + return $this->makeStreamResponse($response);
434 + }
435 +
436 + return $this->makeResponse($response);
437 + }
438 +
439 + /**
440 + * Merge the coolkies (useful for stateful request).
441 + *
442 + * @return void
443 + */
444 + protected function mergeCookies($response)
445 + {
300 446 $this->cookies = array_merge(
301 - $this->cookies,
302 - wp_remote_retrieve_cookies($response)
303 - );
447 + $this->cookies,
448 + wp_remote_retrieve_cookies($response)
449 + );
450 + }
304 451
305 - return $this->makeResponse($response);
452 + /**
453 + * Check if the stream is enabled.
454 + * @return boolean
455 + */
456 + protected function isStreamEnabled($args)
457 + {
458 + return isset($args['stream']) && $args['stream'];
306 459 }
307 460
308 461 /**
462 + * Build a response object from an anonymous class.
463 + *
464 + * @param array $response
465 + * @return \FluentBooking\Framework\Http\Response
466 + */
467 + protected function makeResponse($response)
468 + {
469 + return new Response($response);
470 + }
471 +
472 + /**
473 + * Handle the streaming response and process chunks.
474 + *
475 + * @param array $response
476 + * @return \FluentBooking\Framework\Http\Response|null
477 + */
478 + protected function makeStreamResponse($response)
479 + {
480 + $response['body'] = $response['filename'];
481 +
482 + return new class($response) extends Response implements \ArrayAccess {
483 + public function flush() {
484 + $source = fopen($this->response['body'], 'rb');
485 +
486 + $fp = fopen('php://output', 'wb');
487 +
488 + while (!feof($source)) {
489 + $data = fread($source, 8192);
490 + fwrite($fp, $data);
491 + ob_flush();
492 + flush();
493 + }
494 +
495 + fclose($source);
496 + fclose($fp);
497 + }
498 +
499 + #[ReturnTypeWillChange]
500 + public function offsetGet($offset) {
501 + return $this->response[$offset] ?? null;
502 + }
503 + #[ReturnTypeWillChange]
504 + public function offsetSet($offset, $value) {
505 + $this->response[$offset] = $value;
506 + }
507 + #[ReturnTypeWillChange]
508 + public function offsetExists($offset) {}
509 + #[ReturnTypeWillChange]
510 + public function offsetUnset($offset) {}
511 + };
512 + }
513 +
514 + /**
309 515 * Download a remote file.
310 516 *
311 517 * @param string $url
312 518 * @return \FluentBooking\Framework\Http\Request\File
@@ -357,14 +563,22 @@
357 563 if (!file_exists($path)) {
358 564 throw new Exception('File does not exist.', 500);
359 565 }
360 566
567 + // Auto prepend base URL if $url is relative
568 + if (strpos($url, 'http') !== 0) {
569 + $url = rtrim($this->baseUrl, '/') . '/' . ltrim($url, '/');
570 + }
571 +
361 572 $boundary = wp_generate_password(24, false);
362 573
363 - $headers = [
364 - 'Accept' => '*/*',
365 - 'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
366 - ];
574 + $headers = array_merge(
575 + $this->headers,
576 + [
577 + 'Accept' => '*/*',
578 + 'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
579 + ]
580 + );
367 581
368 582 $fileName = basename($path);
369 583 $content = file_get_contents($path);
370 584 $mime = mime_content_type($path);
@@ -389,19 +603,8 @@
389 603 'timeout' => 60,
390 604 ]);
391 605
392 606 return $this->makeResponse($response);
393 - }
394 -
395 - /**
396 - * Build a response object from an anonymous class.
397 - *
398 - * @param array $response
399 - * @return \FluentBooking\Framework\Http\Response
400 - */
401 - protected function makeResponse($response)
402 - {
403 - return new Response($response);
404 607 }
405 608
406 609 protected function checkIfValidHttpMethod($method)
407 610 {