PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
← All changes | vendor/wpfluent/framework/src/WPFluent/Http/URL.php +340 -112 1.412.1.0 View file →
@@ -2,23 +2,154 @@
2 2
3 3 namespace FluentBoards\Framework\Http;
4 4
5 5 use Exception;
6 +use InvalidArgumentException;
6 7 use FluentBoards\Framework\Support\Util;
7 8 use FluentBoards\Framework\Foundation\App;
8 -use FluentBoards\Framework\Support\DateTime;
9 9 use FluentBoards\Framework\Support\UriTemplate;
10 10
11 11 class URL
12 12 {
13 - protected $encrypter = null;
13 + /**
14 + * The full URL string.
15 + *
16 + * @var string
17 + */
18 + protected $url = '';
14 19
15 - public function __construct($encrypter = null)
20 + /**
21 + * URL generator instance used for signing and route generation.
22 + *
23 + * @var UrlGenerator|null
24 + */
25 + protected $generator = null;
26 +
27 + /**
28 + * URL scheme (e.g., 'http', 'https').
29 + *
30 + * @var string
31 + */
32 + protected $scheme = '';
33 +
34 + /**
35 + * URL host (e.g., 'example.com').
36 + *
37 + * @var string|null
38 + */
39 + protected $host = null;
40 +
41 + /**
42 + * URL port (e.g., 80, 443).
43 + *
44 + * @var int|null
45 + */
46 + protected $port = null;
47 +
48 + /**
49 + * URL path (e.g., '/users/view').
50 + *
51 + * @var string
52 + */
53 + protected $path = '';
54 +
55 + /**
56 + * URL query parameters as an associative array.
57 + *
58 + * @var array
59 + */
60 + protected $query = [];
61 +
62 + /**
63 + * URL fragment (e.g., 'top' in '#top').
64 + *
65 + * @var string|null
66 + */
67 + protected $fragment = null;
68 +
69 + /**
70 + * URL username for authentication (if any).
71 + *
72 + * @var string|null
73 + */
74 + protected $user = null;
75 +
76 + /**
77 + * URL password for authentication (if any).
78 + *
79 + * @var string|null
80 + */
81 + protected $pass = null;
82 +
83 + /**
84 + * Constructor.
85 + *
86 + * @param string|null $url Optional URL to initialize. Defaults to current URL.
87 + * @param UrlGenerator|null $generator Optional generator instance for signing/routes.
88 + *
89 + * @throws InvalidArgumentException If the given URL is invalid.
90 + */
91 + public function __construct($url = '', $generator = null)
16 92 {
17 - $this->encrypter = $encrypter ?: App::make('encrypter');
93 + $this->prepareRfcProperties(
94 + $this->url = $url ?: $this->current()
95 + );
96 +
97 + $this->generator = $generator ?: new UrlGenerator();
18 98 }
19 99
20 100 /**
101 + * Parse a URL according to RFC 3986 and populate internal components.
102 + *
103 + * @param string $url The URL to parse.
104 + *
105 + * @return void
106 + *
107 + * @throws InvalidArgumentException If the URL cannot be parsed.
108 + */
109 + protected function prepareRfcProperties($url)
110 + {
111 + // --- Parse URL according to RFC3986 ---
112 + $parts = parse_url($url);
113 +
114 + if ($parts === false) {
115 + throw new InvalidArgumentException("Invalid URL: {$url}");
116 + }
117 +
118 + $this->scheme = $parts['scheme'] ?? '';
119 + $this->host = $parts['host'] ?? null;
120 + $this->port = $parts['port'] ?? null;
121 + $this->path = $parts['path'] ?? null;
122 + $this->user = $parts['user'] ?? null;
123 + $this->pass = $parts['pass'] ?? null;
124 + $this->fragment = $parts['fragment'] ?? null;
125 +
126 + if (isset($parts['query'])) {
127 + parse_str($parts['query'], $query);
128 + $this->query = $query;
129 + } else {
130 + $this->query = [];
131 + }
132 + }
133 +
134 + /**
135 + * Create a new URL instance.
136 + *
137 + * @param string $uri
138 + * @return static
139 + */
140 + public static function of($uri)
141 + {
142 + $parts = parse_url($uri);
143 +
144 + if ($parts === false) {
145 + throw new InvalidArgumentException("Invalid URI: {$uri}");
146 + }
147 +
148 + return new static($uri);
149 + }
150 +
151 + /**
21 152 * Get the current URL
22 153 *
23 154 * @return string
24 155 */
@@ -23,9 +154,9 @@
23 154 * @return string
24 155 */
25 156 public function current()
26 157 {
27 - return get_site_url() . rtrim($_SERVER['REQUEST_URI'], '/');
158 + return rtrim(get_site_url(), '/') . $_SERVER['REQUEST_URI'];
28 159 }
29 160
30 161 /**
31 162 * Parse a url from uncompiled route
@@ -53,120 +184,28 @@
53 184 /**
54 185 * Sign a URL
55 186 *
56 187 * @param string $url
188 + * @param array $params
57 189 * @return string
58 190 */
59 191 public function sign($url, $params = [])
60 192 {
61 - if (!preg_match('#^(http|https)://#', $url)) {
62 - $config = App::config();
63 - $slug = $config->get('app.slug');
64 - $version = $config->get('app.rest_version');
65 - $url = sprintf('%s%s/%s/%s',rest_url(), $slug, $version, $url);
66 - }
67 -
68 - $parts = parse_url($url);
69 -
70 - $url = $parts['scheme'] . '://' . $parts['host'] . $parts['path'];
71 -
72 - parse_str($parts['query'] ?? '', $query);
73 -
74 - $params = $this->validateExpiryTime($params + $query);
75 -
76 - $params = $this->encrypter->encrypt(http_build_query($params));
77 -
78 - $signature = hash_hmac('sha256', $params, $this->encrypter->getKey());
79 -
80 - return $url . '?_data=' . $params . '&_signature=' . $signature;
193 + return $this->generator->sign($url, $params);
81 194 }
82 195
83 196 /**
84 - * Normalize the expiry time.
85 - *
86 - * @param array $params
87 - * @return array
88 - */
89 - public function validateExpiryTime($params)
90 - {
91 - if (isset($params['expires_at'])) {
92 - if (is_string($params['expires_at'])) {
93 - $params['expires_at'] = strtotime($params['expires_at']);
94 - } elseif ($params['expires_at'] instanceof DateTime) {
95 - $params['expires_at'] = $params['expires_at']->getTimestamp();
96 - } elseif (is_numeric($params['expires_at'])) {
97 - if ($params['expires_at'] <= time()) {
98 - $params['expires_at'] = time() + (int) $params['expires_at'];
99 - }
100 - } else {
101 - throw new Exception('The expiry time is invalid or has passed.');
102 - }
103 - }
197 + * Validate a URL
198 + *
199 + * @param string $url
200 + * @return mixed (false or array)
201 + */
202 + public function validate($url)
203 + {
204 + return $this->generator->validate($url);
205 + }
104 206
105 - return $params;
106 - }
107 -
108 207 /**
109 - * Validate a URL
110 - *
111 - * @param string $url
112 - * @return mixed (false or array)
113 - */
114 - public function validate($url)
115 - {
116 - if (!$query = $this->parseUrlAndGetQuery($url)) {
117 - return false;
118 - }
119 -
120 - if (!isset($query['_data']) || !isset($query['_signature'])) {
121 - return false;
122 - }
123 -
124 - return $this->verifySignature($query['_data'], $query['_signature']);
125 - }
126 -
127 - /**
128 - * Parse query string from the url.
129 - *
130 - * @param string $url
131 - * @return mixed (bool or array)
132 - */
133 - public function parseUrlAndGetQuery($url)
134 - {
135 - $parts = parse_url($url);
136 -
137 - if (!isset($parts['query'])) {
138 - return false;
139 - }
140 -
141 - parse_str($parts['query'], $query);
142 -
143 - return $query;
144 - }
145 -
146 - /**
147 - * Verify the signature.
148 - *
149 - * @param array $data
150 - * @param string $signature
151 - * @return mixed (bool or array)
152 - */
153 - public function verifySignature($data, $signature)
154 - {
155 - $expected = hash_hmac('sha256', $data, $this->encrypter->getKey());
156 -
157 - if (!hash_equals($expected, $signature)) return false;
158 -
159 - parse_str($this->encrypter->decrypt($data), $params);
160 -
161 - if (isset($params['expires_at']) && time() > $params['expires_at']) {
162 - return false;
163 - }
164 -
165 - return empty($params) ? true : $params;
166 - }
167 -
168 - /**
169 208 * Helper method for home_url.
170 209 *
171 210 * @return string
172 211 */
@@ -261,21 +300,210 @@
261 300 /**
262 301 * Generate a URL from a file path.
263 302 *
264 303 * @param string $path
304 + * @param bool $checkFile Whether to check if the file exists
265 305 * @return string
266 306 */
267 - public function fromFile($path)
307 + public function fromFile(string $path, bool $checkFile = false): string
268 308 {
269 - return Util::pathToUrl($path);
309 + return Util::pathToUrl($path, $checkFile);
270 310 }
271 311
272 312 /**
273 - * Returns the string representation of the URL object
313 + * Generate a URL from a named route.
274 314 *
315 + * @param string $name
316 + * @param array $params
317 + * @param array $query
318 + * @return string|null
319 + */
320 + public function route($name, $params = [], $query = [])
321 + {
322 + return $this->generator->route($name, $params, $query);
323 + }
324 +
325 + /**
326 + * Return a clone of the URL with a new path.
327 + *
328 + * @param string $path
329 + * @return $this
330 + */
331 + public function withPath($path)
332 + {
333 + $clone = clone $this;
334 + $clone->path = $path;
335 + return $clone;
336 + }
337 +
338 + /**
339 + * Return a clone of the URL with a new query.
340 + *
341 + * @param array|string $query An array of query params or a query string
342 + * @return $this
343 + */
344 + public function withQuery($query)
345 + {
346 + $clone = clone $this;
347 +
348 + if (is_string($query)) {
349 + parse_str($query, $query);
350 + }
351 +
352 + $clone->query = $query;
353 + return $clone;
354 + }
355 +
356 + /**
357 + * Return a clone of the URL with a new fragment.
358 + *
359 + * @param string $fragment
360 + * @return $this
361 + */
362 + public function withFragment($fragment)
363 + {
364 + $clone = clone $this;
365 + $clone->fragment = $fragment;
366 + return $clone;
367 + }
368 +
369 + /**
370 + * Return a clone of the URL with a new scheme.
371 + *
372 + * @param string $scheme
373 + * @return $this
374 + */
375 + public function withScheme($scheme)
376 + {
377 + $clone = clone $this;
378 + $clone->scheme = $scheme;
379 + return $clone;
380 + }
381 +
382 + /**
383 + * Get the URL scheme (e.g., 'http' or 'https').
384 + *
385 + * @return string
386 + */
387 + public function scheme()
388 + {
389 + return $this->scheme;
390 + }
391 +
392 + /**
393 + * Get the URL host.
394 + *
395 + * @return string|null
396 + */
397 + public function host()
398 + {
399 + return $this->host;
400 + }
401 +
402 + /**
403 + * Get the URL port.
404 + *
405 + * @return int|null
406 + */
407 + public function port()
408 + {
409 + return $this->port;
410 + }
411 +
412 + /**
413 + * Get the URL path.
414 + *
415 + * @return string
416 + */
417 + public function path()
418 + {
419 + return $this->path;
420 + }
421 +
422 + /**
423 + * Get the URL query as an array.
424 + *
425 + * @return array
426 + */
427 + public function query()
428 + {
429 + return $this->query;
430 + }
431 +
432 + /**
433 + * Get the URL fragment.
434 + *
435 + * @return string|null
436 + */
437 + public function fragment()
438 + {
439 + return $this->fragment;
440 + }
441 +
442 + /**
443 + * Get the URL pass.
444 + *
445 + * @return string|null
446 + */
447 + public function pass()
448 + {
449 + return $this->pass;
450 + }
451 +
452 + /**
453 + * Get the URL user.
454 + *
455 + * @return string
456 + */
457 + public function user()
458 + {
459 + return $this->user;
460 + }
461 +
462 + /**
463 + * Returns the string representation of the URL object.
464 + *
275 465 * @return string Current URL.
276 466 */
277 467 public function __toString()
278 468 {
279 - return $this->current();
469 + $url = '';
470 +
471 + if ($this->scheme) {
472 + $url .= "{$this->scheme}://";
473 + }
474 +
475 + if ($this->user) {
476 + $url .= $this->user;
477 + if ($this->pass) {
478 + $url .= ":{$this->pass}";
479 + }
480 + $url .= '@';
481 + }
482 +
483 + if ($this->host) {
484 + $url .= $this->host;
485 + }
486 +
487 + if ($this->port) {
488 + $url .= ":{$this->port}";
489 + }
490 +
491 + if ($this->path !== null) {
492 + $url .= $this->path === '' || str_starts_with($this->path, '/')
493 + ? $this->path
494 + : '/' . $this->path;
495 + }
496 +
497 + if (!empty($this->query)) {
498 + $url .= '?' . http_build_query(
499 + $this->query, '', '&', PHP_QUERY_RFC3986
500 + );
501 + }
502 +
503 + if ($this->fragment) {
504 + $url .= "#{$this->fragment}";
505 + }
506 +
507 + return $url;
280 508 }
281 509 }