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