1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Http;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:
48: class Url implements \JsonSerializable
49: {
50: use Nette\SmartObject;
51:
52:
53: public static $defaultPorts = [
54: 'http' => 80,
55: 'https' => 443,
56: 'ftp' => 21,
57: 'news' => 119,
58: 'nntp' => 119,
59: ];
60:
61:
62: private $scheme = '';
63:
64:
65: private $user = '';
66:
67:
68: private $password = '';
69:
70:
71: private $host = '';
72:
73:
74: private $port;
75:
76:
77: private $path = '';
78:
79:
80: private $query = [];
81:
82:
83: private $fragment = '';
84:
85:
86: 87: 88: 89:
90: public function __construct($url = null)
91: {
92: if (is_string($url)) {
93: $p = @parse_url($url);
94: if ($p === false) {
95: throw new Nette\InvalidArgumentException("Malformed or unsupported URI '$url'.");
96: }
97:
98: $this->scheme = isset($p['scheme']) ? $p['scheme'] : '';
99: $this->port = isset($p['port']) ? $p['port'] : null;
100: $this->host = isset($p['host']) ? rawurldecode($p['host']) : '';
101: $this->user = isset($p['user']) ? rawurldecode($p['user']) : '';
102: $this->password = isset($p['pass']) ? rawurldecode($p['pass']) : '';
103: $this->setPath(isset($p['path']) ? $p['path'] : '');
104: $this->setQuery(isset($p['query']) ? $p['query'] : []);
105: $this->fragment = isset($p['fragment']) ? rawurldecode($p['fragment']) : '';
106:
107: } elseif ($url instanceof self) {
108: foreach ($this as $key => $val) {
109: $this->$key = $url->$key;
110: }
111: }
112: }
113:
114:
115: 116: 117: 118: 119:
120: public function setScheme($value)
121: {
122: $this->scheme = (string) $value;
123: return $this;
124: }
125:
126:
127: 128: 129: 130:
131: public function getScheme()
132: {
133: return $this->scheme;
134: }
135:
136:
137: 138: 139: 140: 141:
142: public function setUser($value)
143: {
144: $this->user = (string) $value;
145: return $this;
146: }
147:
148:
149: 150: 151: 152:
153: public function getUser()
154: {
155: return $this->user;
156: }
157:
158:
159: 160: 161: 162: 163:
164: public function setPassword($value)
165: {
166: $this->password = (string) $value;
167: return $this;
168: }
169:
170:
171: 172: 173: 174:
175: public function getPassword()
176: {
177: return $this->password;
178: }
179:
180:
181: 182: 183: 184: 185:
186: public function setHost($value)
187: {
188: $this->host = (string) $value;
189: $this->setPath($this->path);
190: return $this;
191: }
192:
193:
194: 195: 196: 197:
198: public function getHost()
199: {
200: return $this->host;
201: }
202:
203:
204: 205: 206: 207:
208: public function getDomain($level = 2)
209: {
210: $parts = ip2long($this->host) ? [$this->host] : explode('.', $this->host);
211: $parts = $level >= 0 ? array_slice($parts, -$level) : array_slice($parts, 0, $level);
212: return implode('.', $parts);
213: }
214:
215:
216: 217: 218: 219: 220:
221: public function setPort($value)
222: {
223: $this->port = (int) $value;
224: return $this;
225: }
226:
227:
228: 229: 230: 231:
232: public function getPort()
233: {
234: return $this->port
235: ? $this->port
236: : (isset(self::$defaultPorts[$this->scheme]) ? self::$defaultPorts[$this->scheme] : null);
237: }
238:
239:
240: 241: 242: 243: 244:
245: public function setPath($value)
246: {
247: $this->path = (string) $value;
248: if ($this->host && substr($this->path, 0, 1) !== '/') {
249: $this->path = '/' . $this->path;
250: }
251: return $this;
252: }
253:
254:
255: 256: 257: 258:
259: public function getPath()
260: {
261: return $this->path;
262: }
263:
264:
265: 266: 267: 268: 269:
270: public function setQuery($value)
271: {
272: $this->query = is_array($value) ? $value : self::parseQuery($value);
273: return $this;
274: }
275:
276:
277: 278: 279: 280: 281:
282: public function appendQuery($value)
283: {
284: $this->query = is_array($value)
285: ? $value + $this->query
286: : self::parseQuery($this->getQuery() . '&' . $value);
287: return $this;
288: }
289:
290:
291: 292: 293: 294:
295: public function getQuery()
296: {
297: return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
298: }
299:
300:
301: 302: 303:
304: public function getQueryParameters()
305: {
306: return $this->query;
307: }
308:
309:
310: 311: 312: 313: 314:
315: public function getQueryParameter($name, $default = null)
316: {
317: return isset($this->query[$name]) ? $this->query[$name] : $default;
318: }
319:
320:
321: 322: 323: 324: 325:
326: public function setQueryParameter($name, $value)
327: {
328: $this->query[$name] = $value;
329: return $this;
330: }
331:
332:
333: 334: 335: 336: 337:
338: public function setFragment($value)
339: {
340: $this->fragment = (string) $value;
341: return $this;
342: }
343:
344:
345: 346: 347: 348:
349: public function getFragment()
350: {
351: return $this->fragment;
352: }
353:
354:
355: 356: 357: 358:
359: public function getAbsoluteUrl()
360: {
361: return $this->getHostUrl() . $this->path
362: . (($tmp = $this->getQuery()) ? '?' . $tmp : '')
363: . ($this->fragment === '' ? '' : '#' . $this->fragment);
364: }
365:
366:
367: 368: 369: 370:
371: public function getAuthority()
372: {
373: return $this->host === ''
374: ? ''
375: : ($this->user !== '' && $this->scheme !== 'http' && $this->scheme !== 'https'
376: ? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
377: : '')
378: . $this->host
379: . ($this->port && (!isset(self::$defaultPorts[$this->scheme]) || $this->port !== self::$defaultPorts[$this->scheme])
380: ? ':' . $this->port
381: : '');
382: }
383:
384:
385: 386: 387: 388:
389: public function getHostUrl()
390: {
391: return ($this->scheme ? $this->scheme . ':' : '')
392: . (($authority = $this->getAuthority()) || $this->scheme ? '//' . $authority : '');
393: }
394:
395:
396: 397: 398: 399:
400: public function getBasePath()
401: {
402: $pos = strrpos($this->path, '/');
403: return $pos === false ? '' : substr($this->path, 0, $pos + 1);
404: }
405:
406:
407: 408: 409: 410:
411: public function getBaseUrl()
412: {
413: return $this->getHostUrl() . $this->getBasePath();
414: }
415:
416:
417: 418: 419: 420:
421: public function getRelativeUrl()
422: {
423: return (string) substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
424: }
425:
426:
427: 428: 429: 430: 431:
432: public function isEqual($url)
433: {
434: $url = new self($url);
435: $query = $url->query;
436: ksort($query);
437: $query2 = $this->query;
438: ksort($query2);
439: $http = in_array($this->scheme, ['http', 'https'], true);
440: return $url->scheme === $this->scheme
441: && !strcasecmp($url->host, $this->host)
442: && $url->getPort() === $this->getPort()
443: && ($http || $url->user === $this->user)
444: && ($http || $url->password === $this->password)
445: && self::unescape($url->path, '%/') === self::unescape($this->path, '%/')
446: && $query === $query2
447: && $url->fragment === $this->fragment;
448: }
449:
450:
451: 452: 453: 454:
455: public function canonicalize()
456: {
457: $this->path = preg_replace_callback(
458: '#[^!$&\'()*+,/:;=@%]+#',
459: function ($m) { return rawurlencode($m[0]); },
460: self::unescape($this->path, '%/')
461: );
462: $this->host = strtolower($this->host);
463: return $this;
464: }
465:
466:
467: 468: 469:
470: public function __toString()
471: {
472: return $this->getAbsoluteUrl();
473: }
474:
475:
476: 477: 478:
479: public function jsonSerialize()
480: {
481: return $this->getAbsoluteUrl();
482: }
483:
484:
485: 486: 487: 488: 489: 490:
491: public static function unescape($s, $reserved = '%;/?:@&=+$,')
492: {
493:
494:
495:
496: if ($reserved !== '') {
497: $s = preg_replace_callback(
498: '#%(' . substr(chunk_split(bin2hex($reserved), 2, '|'), 0, -1) . ')#i',
499: function ($m) { return '%25' . strtoupper($m[1]); },
500: $s
501: );
502: }
503: return rawurldecode($s);
504: }
505:
506:
507: 508: 509: 510:
511: public static function parseQuery($s)
512: {
513: parse_str($s, $res);
514: return $res;
515: }
516: }
517: