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 +411 -76 1.232.1.0 View file →
@@ -2,20 +2,154 @@
2 2
3 3 namespace FluentBoards\Framework\Http;
4 4
5 5 use Exception;
6 -use FluentBoards\Framework\Support\DateTime;
6 +use InvalidArgumentException;
7 +use FluentBoards\Framework\Support\Util;
8 +use FluentBoards\Framework\Foundation\App;
9 +use FluentBoards\Framework\Support\UriTemplate;
7 10
8 11 class URL
9 12 {
10 - protected $encrypter = null;
13 + /**
14 + * The full URL string.
15 + *
16 + * @var string
17 + */
18 + protected $url = '';
11 19
12 - public function __construct($encrypter)
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)
13 92 {
14 - $this->encrypter = $encrypter;
93 + $this->prepareRfcProperties(
94 + $this->url = $url ?: $this->current()
95 + );
96 +
97 + $this->generator = $generator ?: new UrlGenerator();
15 98 }
16 99
17 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 + /**
18 152 * Get the current URL
19 153 *
20 154 * @return string
21 155 */
@@ -20,9 +154,9 @@
20 154 * @return string
21 155 */
22 156 public function current()
23 157 {
24 - return get_site_url() . rtrim($_SERVER['REQUEST_URI'], '/');
158 + return rtrim(get_site_url(), '/') . $_SERVER['REQUEST_URI'];
25 159 }
26 160
27 161 /**
28 162 * Parse a url from uncompiled route
@@ -32,18 +166,9 @@
32 166 * @return string
33 167 */
34 168 public function parse(string $url, array $params)
35 169 {
36 - $pattern = '#\{[a-zA-Z0-9-_]+\}#';
37 -
38 - return preg_replace_callback($pattern, function($matches) use ($params) {
39 - foreach ($matches as $match) {
40 - $match = str_replace(['{', '}'], ['', ''], $match);
41 - if (isset($params[$match])) {
42 - return $params[$match];
43 - }
44 - }
45 - }, $url);
170 + return UriTemplate::expand($url, $params);
46 171 }
47 172
48 173 /**
49 174 * Sign the current url.
@@ -59,116 +184,326 @@
59 184 /**
60 185 * Sign a URL
61 186 *
62 187 * @param string $url
188 + * @param array $params
63 189 * @return string
64 190 */
65 191 public function sign($url, $params = [])
66 192 {
67 - $parts = parse_url($url);
193 + return $this->generator->sign($url, $params);
194 + }
68 195
69 - $url = $parts['scheme'] . '://' . $parts['host'] . $parts['path'];
196 + /**
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 + }
70 206
71 - parse_str($parts['query'] ?? '', $query);
72 -
73 - $params = $this->validateExpiryTime($params + $query);
207 + /**
208 + * Helper method for home_url.
209 + *
210 + * @return string
211 + */
212 + public function wp($path = '')
213 + {
214 + $wp = get_option('siteUrl');
74 215
75 - $params = $this->encrypter->encrypt(http_build_query($params));
76 -
77 - $signature = hash_hmac('sha256', $params, $this->encrypter->getKey());
78 -
79 - return $url . '?_data=' . $params . '&_signature=' . $signature;
216 + if ($path) {
217 + $wp .= '/' . trim($path, '/');
218 + }
219 +
220 + return $wp;
80 221 }
81 222
82 223 /**
83 - * Normalize the expiry time.
224 + * Retrieve the wp-content URL.
225 + *
226 + * Note: The wp-content directory is renamable by devs so it's not
227 + * guaranteed to be wp-content, so use this method for safety.
84 228 *
85 - * @param array $params
86 - * @return array
229 + * @param string $path
230 + * @return string
87 231 */
88 - public function validateExpiryTime($params)
232 + public function content($path = '')
89 233 {
90 - if (isset($params['expires_at'])) {
91 - if (is_string($params['expires_at'])) {
92 - $params['expires_at'] = strtotime($params['expires_at']);
93 - } elseif ($params['expires_at'] instanceof DateTime) {
94 - $params['expires_at'] = $params['expires_at']->getTimestamp();
95 - }
234 + return content_url($path);
235 + }
96 236
97 - if (!is_numeric($params['expires_at']) || $params['expires_at'] < time()) {
98 - throw new Exception('The expiry time has passed or invalid.');
99 - }
100 - }
237 + /**
238 + * Retrieve the wp-content/plugins URL.
239 + *
240 + * @param string $path
241 + * @return string
242 + */
243 + public function plugins($path = '')
244 + {
245 + return $this->content('/plugins/' . ltrim($path, '/'));
246 + }
101 247
102 - return $params;
248 + /**
249 + * Retrieve the wp-content/plugins URL.
250 + *
251 + * @param string $path
252 + * @return string
253 + */
254 + public function plugin($path = '')
255 + {
256 + $pluginUrl = rtrim(plugin_dir_url(
257 + App::make('__pluginfile__')
258 + ), '/');
259 +
260 + if ($path) {
261 + $pluginUrl .= '/' . trim($path, '/');
262 + }
263 +
264 + return $pluginUrl;
103 265 }
104 266
105 267 /**
106 - * Validate a URL
268 + * Retrieve the wp-content/themes URL.
107 269 *
108 - * @param string $url
109 - * @return mixed (false or array)
270 + * @param string $path
271 + * @return string
110 272 */
111 - public function validate($url)
273 + public function themes($path = '')
112 274 {
113 - if (!$query = $this->parseUrlAndGetQuery($url)) {
114 - return false;
115 - }
275 + return $this->content('/themes/' . ltrim($path, '/'));
276 + }
116 277
117 - if (!$query['_data'] || !$query['_signature']) {
118 - return false;
119 - }
278 + /**
279 + * Retrieve the wp-content/uploads URL.
280 + *
281 + * @param string $path
282 + * @return string
283 + */
284 + public function uploads($path = '')
285 + {
286 + return $this->content('/uploads/' . ltrim($path, '/'));
287 + }
120 288
121 - return $this->verifySignature($query['_data'], $query['_signature']);
289 + /**
290 + * Retrieve the site/home URL.
291 + *
292 + * @param string $path
293 + * @return string
294 + */
295 + public function home($path = '', $scheme = null)
296 + {
297 + return site_url($path, $scheme);
122 298 }
123 299
124 300 /**
125 - * Parse query string from the url.
301 + * Generate a URL from a file path.
126 302 *
127 - * @param string $url
128 - * @return mixed (bool or array)
303 + * @param string $path
304 + * @param bool $checkFile Whether to check if the file exists
305 + * @return string
129 306 */
130 - public function parseUrlAndGetQuery($url)
307 + public function fromFile(string $path, bool $checkFile = false): string
131 308 {
132 - $parts = parse_url($url);
309 + return Util::pathToUrl($path, $checkFile);
310 + }
133 311
134 - if (!isset($parts['query'])) {
135 - return false;
136 - }
312 + /**
313 + * Generate a URL from a named route.
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 + }
137 324
138 - parse_str($parts['query'], $query);
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 + }
139 337
140 - return $query;
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;
141 354 }
142 355
143 356 /**
144 - * Verify the signature.
145 - *
146 - * @param array $data
147 - * @param string $signature
148 - * @return mixed (false or array)
357 + * Return a clone of the URL with a new fragment.
358 + *
359 + * @param string $fragment
360 + * @return $this
149 361 */
150 - public function verifySignature($data, $signature)
362 + public function withFragment($fragment)
151 363 {
152 - $expected = hash_hmac('sha256', $data, $this->encrypter->getKey());
364 + $clone = clone $this;
365 + $clone->fragment = $fragment;
366 + return $clone;
367 + }
153 368
154 - if (!hash_equals($expected, $signature)) return false;
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 + }
155 381
156 - parse_str($this->encrypter->decrypt(urldecode($data)), $params);
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 + }
157 391
158 - if (isset($params['expires_at']) && time() > $params['expires_at']) {
159 - return false;
160 - }
392 + /**
393 + * Get the URL host.
394 + *
395 + * @return string|null
396 + */
397 + public function host()
398 + {
399 + return $this->host;
400 + }
161 401
162 - return $params;
402 + /**
403 + * Get the URL port.
404 + *
405 + * @return int|null
406 + */
407 + public function port()
408 + {
409 + return $this->port;
163 410 }
164 411
165 412 /**
166 - * Returns the string representation of the URL object
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.
167 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 + *
168 465 * @return string Current URL.
169 466 */
170 467 public function __toString()
171 468 {
172 - 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;
173 508 }
174 509 }