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