Url.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <?php
  2. namespace PicoFeed\Client;
  3. /**
  4. * URL class.
  5. *
  6. * @author Frederic Guillot
  7. */
  8. class Url
  9. {
  10. /**
  11. * URL.
  12. *
  13. * @var string
  14. */
  15. private $url = '';
  16. /**
  17. * URL components.
  18. *
  19. * @var array
  20. */
  21. private $components = array();
  22. /**
  23. * Constructor.
  24. *
  25. * @param string $url URL
  26. */
  27. public function __construct($url)
  28. {
  29. $this->url = $url;
  30. $this->components = parse_url($url) ?: array();
  31. // Issue with PHP < 5.4.7 and protocol relative url
  32. if (version_compare(PHP_VERSION, '5.4.7', '<') && $this->isProtocolRelative()) {
  33. $pos = strpos($this->components['path'], '/', 2);
  34. if ($pos === false) {
  35. $pos = strlen($this->components['path']);
  36. }
  37. $this->components['host'] = substr($this->components['path'], 2, $pos - 2);
  38. $this->components['path'] = substr($this->components['path'], $pos);
  39. }
  40. }
  41. /**
  42. * Shortcut method to get an absolute url from relative url.
  43. *
  44. * @static
  45. * @param mixed $item_url Unknown url (can be relative or not)
  46. * @param mixed $website_url Website url
  47. * @return string
  48. */
  49. public static function resolve($item_url, $website_url)
  50. {
  51. $link = is_string($item_url) ? new self($item_url) : $item_url;
  52. $website = is_string($website_url) ? new self($website_url) : $website_url;
  53. if ($link->isRelativeUrl()) {
  54. if ($link->isRelativePath()) {
  55. return $link->getAbsoluteUrl($website->getBaseUrl($website->getBasePath()));
  56. }
  57. return $link->getAbsoluteUrl($website->getBaseUrl());
  58. } elseif ($link->isProtocolRelative()) {
  59. $link->setScheme($website->getScheme());
  60. }
  61. return $link->getAbsoluteUrl();
  62. }
  63. /**
  64. * Shortcut method to get a base url.
  65. *
  66. * @static
  67. * @param string $url
  68. * @return string
  69. */
  70. public static function base($url)
  71. {
  72. $link = new self($url);
  73. return $link->getBaseUrl();
  74. }
  75. /**
  76. * Get the base URL.
  77. *
  78. * @param string $suffix Add a suffix to the url
  79. * @return string
  80. */
  81. public function getBaseUrl($suffix = '')
  82. {
  83. return $this->hasHost() ? $this->getScheme('://').$this->getHost().$this->getPort(':').$suffix : '';
  84. }
  85. /**
  86. * Get the absolute URL.
  87. *
  88. * @param string $base_url Use this url as base url
  89. * @return string
  90. */
  91. public function getAbsoluteUrl($base_url = '')
  92. {
  93. if ($base_url) {
  94. $base = new self($base_url);
  95. $url = $base->getAbsoluteUrl().substr($this->getFullPath(), 1);
  96. } else {
  97. $url = $this->hasHost() ? $this->getBaseUrl().$this->getFullPath() : '';
  98. }
  99. return $url;
  100. }
  101. /**
  102. * Return true if the url is relative.
  103. *
  104. * @return bool
  105. */
  106. public function isRelativeUrl()
  107. {
  108. return !$this->hasScheme() && !$this->isProtocolRelative();
  109. }
  110. /**
  111. * Return true if the path is relative.
  112. *
  113. * @return bool
  114. */
  115. public function isRelativePath()
  116. {
  117. $path = $this->getPath();
  118. return empty($path) || $path[0]
  119. !== '/';
  120. }
  121. /**
  122. * Filters the path of a URI.
  123. *
  124. * Imported from Guzzle library: https://github.com/guzzle/psr7/blob/master/src/Uri.php#L568-L582
  125. *
  126. * @param $path
  127. * @param string $charUnreserved
  128. * @param string $charSubDelims
  129. * @return string
  130. */
  131. public function filterPath($path, $charUnreserved = 'a-zA-Z0-9_\-\.~', $charSubDelims = '!\$&\'\(\)\*\+,;=')
  132. {
  133. return preg_replace_callback(
  134. '/(?:[^'.$charUnreserved.$charSubDelims.':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
  135. function (array $matches) { return rawurlencode($matches[0]); },
  136. $path
  137. );
  138. }
  139. /**
  140. * Get the path.
  141. *
  142. * @return string
  143. */
  144. public function getPath()
  145. {
  146. return $this->filterPath(empty($this->components['path']) ? '' : $this->components['path']);
  147. }
  148. /**
  149. * Get the base path.
  150. *
  151. * @return string
  152. */
  153. public function getBasePath()
  154. {
  155. $current_path = $this->getPath();
  156. $path = $this->isRelativePath() ? '/' : '';
  157. $path .= substr($current_path, -1) === '/' ? $current_path : dirname($current_path);
  158. return preg_replace('/\\\\\/|\/\//', '/', $path.'/');
  159. }
  160. /**
  161. * Get the full path (path + querystring + fragment).
  162. *
  163. * @return string
  164. */
  165. public function getFullPath()
  166. {
  167. $path = $this->isRelativePath() ? '/' : '';
  168. $path .= $this->getPath();
  169. $path .= empty($this->components['query']) ? '' : '?'.$this->components['query'];
  170. $path .= empty($this->components['fragment']) ? '' : '#'.$this->components['fragment'];
  171. return $path;
  172. }
  173. /**
  174. * Get the hostname.
  175. *
  176. * @return string
  177. */
  178. public function getHost()
  179. {
  180. return empty($this->components['host']) ? '' : $this->components['host'];
  181. }
  182. /**
  183. * Return true if the url has a hostname.
  184. *
  185. * @return bool
  186. */
  187. public function hasHost()
  188. {
  189. return !empty($this->components['host']);
  190. }
  191. /**
  192. * Get the scheme.
  193. *
  194. * @param string $suffix Suffix to add when there is a scheme
  195. * @return string
  196. */
  197. public function getScheme($suffix = '')
  198. {
  199. return ($this->hasScheme() ? $this->components['scheme'] : 'http').$suffix;
  200. }
  201. /**
  202. * Set the scheme.
  203. *
  204. * @param string $scheme Set a scheme
  205. * @return $this
  206. */
  207. public function setScheme($scheme)
  208. {
  209. $this->components['scheme'] = $scheme;
  210. return $this;
  211. }
  212. /**
  213. * Return true if the url has a scheme.
  214. *
  215. * @return bool
  216. */
  217. public function hasScheme()
  218. {
  219. return !empty($this->components['scheme']);
  220. }
  221. /**
  222. * Get the port.
  223. *
  224. * @param string $prefix Prefix to add when there is a port
  225. * @return string
  226. */
  227. public function getPort($prefix = '')
  228. {
  229. return $this->hasPort() ? $prefix.$this->components['port'] : '';
  230. }
  231. /**
  232. * Return true if the url has a port.
  233. *
  234. * @return bool
  235. */
  236. public function hasPort()
  237. {
  238. return !empty($this->components['port']);
  239. }
  240. /**
  241. * Return true if the url is protocol relative (start with //).
  242. *
  243. * @return bool
  244. */
  245. public function isProtocolRelative()
  246. {
  247. return strpos($this->url, '//') === 0;
  248. }
  249. }