1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: namespace Kotchasan\Http;
12:
13: 14: 15: 16: 17: 18: 19:
20: class Uri extends \Kotchasan\KBase implements \Psr\Http\Message\UriInterface
21: {
22: 23: 24: 25: 26:
27: protected $fragment = '';
28: 29: 30: 31: 32:
33: protected $host = '';
34: 35: 36: 37: 38:
39: protected $path = '';
40: 41: 42: 43: 44:
45: protected $port;
46: 47: 48: 49: 50:
51: protected $query = '';
52: 53: 54: 55: 56:
57: protected $scheme = '';
58: 59: 60: 61: 62:
63: protected $userInfo = '';
64:
65: 66: 67: 68: 69: 70: 71:
72: public function __construct($scheme, $host, $path = '/', $query = '', $port = null, $user = '', $pass = '', $fragment = '')
73: {
74: $this->scheme = $this->filterScheme($scheme);
75: $this->host = $host;
76: $this->path = $path;
77: $this->query = $this->filterQueryFragment($query);
78: $this->port = $this->filterPort($this->scheme, $this->host, $port) ? $port : null;
79: $this->userInfo = $user.($pass === '' ? '' : ':'.$pass);
80: $this->fragment = $this->filterQueryFragment($fragment);
81: }
82:
83: 84: 85: 86: 87:
88: public function __toString()
89: {
90: return self::createUriString(
91: $this->scheme,
92: $this->getAuthority(),
93: $this->path,
94: $this->query,
95: $this->fragment
96: );
97: }
98:
99: 100: 101: 102: 103: 104: 105: 106: 107:
108: public function createBackUri($query_string)
109: {
110: $query_str = array();
111: foreach ($this->parseQueryParams($this->query) as $key => $value) {
112: $key = ltrim($key, '_');
113: if (key_exists($key, $query_string) && $query_string[$key] === null) {
114: continue;
115: } elseif (preg_match('/((^[0-9]+$)|(.*?(username|password|token|time).*?))/', $key)) {
116: continue;
117: }
118: if ($value !== null) {
119: $query_str['_'.$key] = $value;
120: }
121: }
122: foreach ($query_string as $key => $value) {
123: if ($value !== null) {
124: $query_str[$key] = $value;
125: }
126: }
127: return $this->withQuery($this->paramsToQuery($query_str, true));
128: }
129:
130: 131: 132: 133: 134: 135: 136:
137: public static function createFromGlobals()
138: {
139: if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
140: $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'].'://';
141: } elseif ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443)) {
142: $scheme = 'https://';
143: } else {
144: $scheme = 'http://';
145: }
146: if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
147: $host = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_HOST'])));
148: } elseif (empty($_SERVER['HTTP_HOST'])) {
149: $host = $_SERVER['SERVER_NAME'];
150: } else {
151: $host = $_SERVER['HTTP_HOST'];
152: }
153: $pos = strpos($host, ':');
154: if ($pos !== false) {
155: $port = (int) substr($host, $pos + 1);
156: $host = strstr($host, ':', true);
157: } else {
158: $port = isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
159: }
160: $path = empty($_SERVER['REQUEST_URI']) ? '/' : parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
161: $query = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
162: $user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
163: $pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
164: return new static($scheme, $host, $path, $query, $port, $user, $pass);
165: }
166:
167: 168: 169: 170: 171: 172: 173: 174: 175:
176: public static function createFromUri($uri)
177: {
178: $parts = parse_url($uri);
179: if (false === $parts) {
180: throw new \InvalidArgumentException('Invalid Uri');
181: } else {
182: $scheme = isset($parts['scheme']) ? $parts['scheme'] : '';
183: $host = isset($parts['host']) ? $parts['host'] : '';
184: $port = isset($parts['port']) ? $parts['port'] : null;
185: $user = isset($parts['user']) ? $parts['user'] : '';
186: $pass = isset($parts['pass']) ? $parts['pass'] : '';
187: $path = isset($parts['path']) ? $parts['path'] : '';
188: $query = isset($parts['query']) ? $parts['query'] : '';
189: $fragment = isset($parts['fragment']) ? $parts['fragment'] : '';
190: return new static($scheme, $host, $path, $query, $port, $user, $pass, $fragment);
191: }
192: }
193:
194: 195: 196: 197: 198:
199: public function getAuthority()
200: {
201: return ($this->userInfo ? $this->userInfo.'@' : '').$this->host.($this->port !== null ? ':'.$this->port : '');
202: }
203:
204: 205: 206: 207: 208: 209: 210: 211:
212: public function getBack($url, $query_string = array())
213: {
214: return $this->createBack($url, $_GET, $query_string);
215: }
216:
217: 218: 219: 220: 221:
222: public function getFragment()
223: {
224: return $this->fragment;
225: }
226:
227: 228: 229: 230: 231:
232: public function getHost()
233: {
234: return $this->host;
235: }
236:
237: 238: 239: 240: 241:
242: public function getPath()
243: {
244: return $this->path;
245: }
246:
247: 248: 249: 250: 251: 252:
253: public function getPort()
254: {
255: return $this->port;
256: }
257:
258: 259: 260: 261: 262:
263: public function getQuery()
264: {
265: return $this->query;
266: }
267:
268: 269: 270: 271: 272:
273: public function getScheme()
274: {
275: return $this->scheme;
276: }
277:
278: 279: 280: 281: 282:
283: public function getUserInfo()
284: {
285: return $this->userInfo;
286: }
287:
288: 289: 290: 291: 292: 293: 294: 295: 296:
297: public function pagination($totalpage, $page, $maxlink = 9)
298: {
299: if ($totalpage > $maxlink) {
300: $start = $page - floor($maxlink / 2);
301: if ($start < 1) {
302: $start = 1;
303: } elseif ($start + $maxlink > $totalpage) {
304: $start = $totalpage - $maxlink + 1;
305: }
306: } else {
307: $start = 1;
308: }
309: $url = '<a href="'.$this->withParams(array('page' => ':page'), true).'" title="{LNG_go to page} :page">:page</a>';
310: $splitpage = ($start > 2) ? str_replace(':page', 1, $url) : '';
311: for ($i = $start; $i <= $totalpage && $maxlink > 0; ++$i) {
312: $splitpage .= ($i == $page) ? '<strong title="{LNG_Showing page} '.$i.'">'.$i.'</strong>' : str_replace(':page', $i, $url);
313: --$maxlink;
314: }
315: $splitpage .= ($i < $totalpage) ? str_replace(':page', $totalpage, $url) : '';
316: return empty($splitpage) ? '<strong>1</strong>' : $splitpage;
317: }
318:
319: 320: 321: 322: 323: 324: 325: 326:
327: public function paramsToQuery($params, $encode)
328: {
329: $query_str = array();
330: foreach ($params as $key => $value) {
331: if (preg_match('/^[a-zA-Z0-9_\-\[\]]+$/', $key)) {
332: if ($value === null) {
333: $query_str[$key] = $key;
334: } else {
335: $query_str[$key] = $key.'='.$this->filterQueryFragment($value);
336: }
337: }
338: }
339: return implode($encode ? '&' : '&', $query_str);
340: }
341:
342: 343: 344: 345: 346: 347: 348:
349: public function parseQueryParams($query = null)
350: {
351: $query = $query === null ? $this->query : $query;
352: $result = array();
353: if (!empty($query)) {
354: foreach (explode('&', str_replace('&', '&', $query)) as $item) {
355: if (preg_match('/^([a-zA-Z0-9_\-\[\]]+)(=(.*))?$/', $item, $match)) {
356: if (isset($match[3])) {
357: if (!(preg_match('/^[0-9]+$/', $match[1]) && $match[3] === '')) {
358: $result[$match[1]] = $match[3];
359: }
360: } else {
361: $result[$match[1]] = null;
362: }
363: }
364: }
365: }
366: return $result;
367: }
368:
369: 370: 371: 372: 373: 374: 375: 376: 377:
378: public function postBack($url, $query_string = array())
379: {
380: return $this->createBack($url, $_POST, $query_string);
381: }
382:
383: 384: 385: 386: 387: 388: 389: 390: 391: 392:
393: public function withFragment($fragment)
394: {
395: if (!is_string($fragment) && !method_exists($fragment, '__toString')) {
396: throw new \InvalidArgumentException('Uri fragment must be a string');
397: }
398: $fragment = ltrim((string) $fragment, '#');
399: $clone = clone $this;
400: $clone->fragment = $this->filterQueryFragment($fragment);
401: return $clone;
402: }
403:
404: 405: 406: 407: 408: 409: 410: 411:
412: public function withHost($host)
413: {
414: $clone = clone $this;
415: $clone->host = $host;
416: return $clone;
417: }
418:
419: 420: 421: 422: 423: 424: 425: 426:
427: public function withParams($params, $encode = false)
428: {
429: $query_str = array();
430: foreach ($this->parseQueryParams($this->query) as $key => $value) {
431: $query_str[$key] = $value;
432: }
433: foreach ($params as $key => $value) {
434: $query_str[$key] = $value;
435: }
436: return $this->withQuery($this->paramsToQuery($query_str, $encode));
437: }
438:
439: 440: 441: 442: 443: 444: 445: 446:
447: public function withoutParams($names, $encode = false)
448: {
449: $attributes = $this->parseQueryParams($this->query);
450: if (is_array($names)) {
451: foreach ($names as $name) {
452: unset($attributes[$name]);
453: }
454: } else {
455: unset($attributes[$names]);
456: }
457: return $this->withQuery($this->paramsToQuery($attributes, $encode));
458: }
459:
460: 461: 462: 463: 464: 465: 466: 467: 468: 469:
470: public function withPath($path)
471: {
472: $clone = clone $this;
473: $clone->path = $this->filterPath($path);
474: return $clone;
475: }
476:
477: 478: 479: 480: 481: 482: 483: 484: 485: 486:
487: public function withPort($port)
488: {
489: $clone = clone $this;
490: $clone->port = $this->filterPort($this->scheme, $this->host, $port);
491: return $clone;
492: }
493:
494: 495: 496: 497: 498: 499: 500: 501: 502: 503:
504: public function withQuery($query)
505: {
506: if (!is_string($query) && !method_exists($query, '__toString')) {
507: throw new \InvalidArgumentException('Uri query must be a string');
508: }
509: $query = ltrim((string) $query, '?');
510: $clone = clone $this;
511: $clone->query = $this->filterQueryFragment($query);
512: return $clone;
513: }
514:
515: 516: 517: 518: 519: 520: 521: 522:
523: public function withoutQuery($query)
524: {
525: $clone = clone $this;
526: $queries = array();
527: foreach (explode('&', $clone->query) as $item) {
528: $queries[$item] = $item;
529: }
530: foreach ($query as $k => $v) {
531: unset($queries[$k.'='.$v]);
532: }
533: $clone->query = implode('&', $queries);
534: return $clone;
535: }
536:
537: 538: 539: 540: 541: 542: 543: 544: 545: 546:
547: public function withScheme($scheme)
548: {
549: $clone = clone $this;
550: $clone->scheme = $this->filterScheme($scheme);
551: return $clone;
552: }
553:
554: 555: 556: 557: 558: 559: 560: 561: 562:
563: public function withUserInfo($user, $password = null)
564: {
565: $clone = clone $this;
566: $clone->userInfo = $user.($password ? ':'.$password : '');
567: return $clone;
568: }
569:
570: 571: 572: 573: 574: 575: 576: 577: 578: 579:
580: private function createBack($url, $source, $query_string)
581: {
582: foreach ($source as $key => $value) {
583: if ($value !== '' && !preg_match('/.*?(username|password|token|time).*?/', $key) && preg_match('/^_{1,}(.*)$/', $key, $match)) {
584: if (!isset($query_string[$match[1]])) {
585: $query_string[$match[1]] = $value;
586: }
587: }
588: }
589: if (isset($query_string['time'])) {
590: $query_string['time'] = time();
591: }
592: $query_str = array();
593: foreach ($query_string as $key => $value) {
594: if ($value !== null) {
595: $query_str[$key] = $value;
596: }
597: }
598: return $url.(strpos($url, '?') === false ? '?' : '&').$this->paramsToQuery($query_str, false);
599: }
600:
601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612:
613: private static function createUriString($scheme, $authority, $path, $query, $fragment)
614: {
615: $uri = '';
616: if (!empty($scheme)) {
617: $uri .= $scheme.'://';
618: }
619: if (!empty($authority)) {
620: $uri .= $authority;
621: }
622: if ($path != null) {
623: if ($uri && substr($path, 0, 1) !== '/') {
624: $uri .= '/';
625: }
626: $uri .= $path;
627: }
628: if ($query != '') {
629: $uri .= '?'.$query;
630: }
631: if ($fragment != '') {
632: $uri .= '#'.$fragment;
633: }
634: return $uri;
635: }
636:
637: 638: 639: 640: 641: 642: 643:
644: private function filterPath($path)
645: {
646: return preg_replace_callback('/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', function ($match) {
647: return rawurlencode($match[0]);
648: }, $path);
649: }
650:
651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661:
662: private function filterPort($scheme, $host, $port)
663: {
664: if (null !== $port) {
665: $port = (int) $port;
666: if (1 > $port || 0xffff < $port) {
667: throw new \InvalidArgumentException('Port number must be between 1 and 65535');
668: }
669: }
670: return $this->isNonStandardPort($scheme, $host, $port) ? $port : null;
671: }
672:
673: 674: 675: 676: 677: 678: 679:
680: private function filterQueryFragment($str)
681: {
682: return preg_replace_callback('/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', function ($match) {
683: return rawurlencode($match[0]);
684: }, $str);
685: }
686:
687: 688: 689: 690: 691: 692: 693: 694: 695:
696: private function filterScheme($scheme)
697: {
698: $schemes = array('' => '', 'http' => 'http', 'https' => 'https');
699: $scheme = rtrim(strtolower($scheme), ':/');
700: if (isset($schemes[$scheme])) {
701: return $scheme;
702: } else {
703: throw new \InvalidArgumentException('Uri scheme must be http, https or empty string');
704: }
705: }
706:
707: 708: 709: 710: 711: 712: 713: 714: 715: 716:
717: private function isNonStandardPort($scheme, $host, $port)
718: {
719: if (!$scheme && $port) {
720: return true;
721: }
722: if (!$host || !$port) {
723: return false;
724: }
725: return ($scheme != 'http' && $scheme != 'https') || ($port != 80 && $port != 443);
726: }
727: }
728: