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 ?: (isset(self::$defaultPorts[$this->scheme]) ? self::$defaultPorts[$this->scheme] : null);
235: }
236:
237:
238: 239: 240: 241: 242:
243: public function setPath($value)
244: {
245: $this->path = (string) $value;
246: if ($this->host && substr($this->path, 0, 1) !== '/') {
247: $this->path = '/' . $this->path;
248: }
249: return $this;
250: }
251:
252:
253: 254: 255: 256:
257: public function getPath()
258: {
259: return $this->path;
260: }
261:
262:
263: 264: 265: 266: 267:
268: public function setQuery($value)
269: {
270: $this->query = is_array($value) ? $value : self::parseQuery($value);
271: return $this;
272: }
273:
274:
275: 276: 277: 278: 279:
280: public function appendQuery($value)
281: {
282: $this->query = is_array($value)
283: ? $value + $this->query
284: : self::parseQuery($this->getQuery() . '&' . $value);
285: return $this;
286: }
287:
288:
289: 290: 291: 292:
293: public function getQuery()
294: {
295: return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
296: }
297:
298:
299: 300: 301:
302: public function getQueryParameters()
303: {
304: return $this->query;
305: }
306:
307:
308: 309: 310: 311: 312:
313: public function getQueryParameter($name, $default = null)
314: {
315: return isset($this->query[$name]) ? $this->query[$name] : $default;
316: }
317:
318:
319: 320: 321: 322: 323:
324: public function setQueryParameter($name, $value)
325: {
326: $this->query[$name] = $value;
327: return $this;
328: }
329:
330:
331: 332: 333: 334: 335:
336: public function setFragment($value)
337: {
338: $this->fragment = (string) $value;
339: return $this;
340: }
341:
342:
343: 344: 345: 346:
347: public function getFragment()
348: {
349: return $this->fragment;
350: }
351:
352:
353: 354: 355: 356:
357: public function getAbsoluteUrl()
358: {
359: return $this->getHostUrl() . $this->path
360: . (($tmp = $this->getQuery()) ? '?' . $tmp : '')
361: . ($this->fragment === '' ? '' : '#' . $this->fragment);
362: }
363:
364:
365: 366: 367: 368:
369: public function getAuthority()
370: {
371: return $this->host === ''
372: ? ''
373: : ($this->user !== '' && $this->scheme !== 'http' && $this->scheme !== 'https'
374: ? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
375: : '')
376: . $this->host
377: . ($this->port && (!isset(self::$defaultPorts[$this->scheme]) || $this->port !== self::$defaultPorts[$this->scheme])
378: ? ':' . $this->port
379: : '');
380: }
381:
382:
383: 384: 385: 386:
387: public function getHostUrl()
388: {
389: return ($this->scheme ? $this->scheme . ':' : '')
390: . (($authority = $this->getAuthority()) || $this->scheme ? '//' . $authority : '');
391: }
392:
393:
394: 395: 396: 397:
398: public function getBasePath()
399: {
400: $pos = strrpos($this->path, '/');
401: return $pos === false ? '' : substr($this->path, 0, $pos + 1);
402: }
403:
404:
405: 406: 407: 408:
409: public function getBaseUrl()
410: {
411: return $this->getHostUrl() . $this->getBasePath();
412: }
413:
414:
415: 416: 417: 418:
419: public function getRelativeUrl()
420: {
421: return (string) substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
422: }
423:
424:
425: 426: 427: 428: 429:
430: public function isEqual($url)
431: {
432: $url = new self($url);
433: $query = $url->query;
434: ksort($query);
435: $query2 = $this->query;
436: ksort($query2);
437: $http = in_array($this->scheme, ['http', 'https'], true);
438: return $url->scheme === $this->scheme
439: && !strcasecmp($url->host, $this->host)
440: && $url->getPort() === $this->getPort()
441: && ($http || $url->user === $this->user)
442: && ($http || $url->password === $this->password)
443: && self::unescape($url->path, '%/') === self::unescape($this->path, '%/')
444: && $query === $query2
445: && $url->fragment === $this->fragment;
446: }
447:
448:
449: 450: 451: 452:
453: public function canonicalize()
454: {
455: $this->path = preg_replace_callback(
456: '#[^!$&\'()*+,/:;=@%]+#',
457: function ($m) { return rawurlencode($m[0]); },
458: self::unescape($this->path, '%/')
459: );
460: $this->host = strtolower($this->host);
461: return $this;
462: }
463:
464:
465: 466: 467:
468: public function __toString()
469: {
470: return $this->getAbsoluteUrl();
471: }
472:
473:
474: 475: 476:
477: public function jsonSerialize()
478: {
479: return $this->getAbsoluteUrl();
480: }
481:
482:
483: 484: 485: 486: 487: 488:
489: public static function unescape($s, $reserved = '%;/?:@&=+$,')
490: {
491:
492:
493:
494: if ($reserved !== '') {
495: $s = preg_replace_callback(
496: '#%(' . substr(chunk_split(bin2hex($reserved), 2, '|'), 0, -1) . ')#i',
497: function ($m) { return '%25' . strtoupper($m[1]); },
498: $s
499: );
500: }
501: return rawurldecode($s);
502: }
503:
504:
505: 506: 507: 508:
509: public static function parseQuery($s)
510: {
511: parse_str($s, $res);
512: return $res;
513: }
514: }
515: