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