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