Uri.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. namespace Grav\Common;
  3. use Grav\Common\Page\Page;
  4. use Grav\Common\Page\Pages;
  5. /**
  6. * The URI object provides information about the current URL
  7. *
  8. * @author RocketTheme
  9. * @license MIT
  10. */
  11. class Uri
  12. {
  13. const HOSTNAME_REGEX = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/';
  14. public $url;
  15. protected $basename;
  16. protected $base;
  17. protected $root;
  18. protected $bits;
  19. protected $extension;
  20. protected $host;
  21. protected $content_path;
  22. protected $path;
  23. protected $paths;
  24. protected $query;
  25. protected $params;
  26. /**
  27. * Constructor.
  28. */
  29. public function __construct()
  30. {
  31. $name = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
  32. // Remove port from HTTP_HOST generated $name
  33. $name = Utils::substrToString($name, ':');
  34. // Validate the hostname
  35. $name = preg_match(Uri::HOSTNAME_REGEX, $name) ? $name : 'unknown';
  36. $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
  37. $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
  38. $root_path = str_replace(' ', '%20', rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/'));
  39. // set the base
  40. if (isset($_SERVER['HTTPS'])) {
  41. $base = (@$_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
  42. } else {
  43. $base = 'http://';
  44. }
  45. // add the sever name
  46. $base .= $name;
  47. // add the port of needed
  48. if ($port != '80' && $port != '443') {
  49. $base .= ":".$port;
  50. }
  51. // check if userdir in the path and workaround PHP bug with PHP_SELF
  52. if (strpos($uri, '/~') !== false && strpos($_SERVER['PHP_SELF'], '/~') === false) {
  53. $root_path = substr($uri, 0, strpos($uri, '/', 1)) . $root_path;
  54. }
  55. // set hostname
  56. $address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '::1';
  57. // check for localhost variations
  58. if ($name == 'localhost' || $address == '::1' || $address == '127.0.0.1') {
  59. $this->host = 'localhost';
  60. } else {
  61. $this->host = $name;
  62. }
  63. $this->base = $base;
  64. $this->root = $base . $root_path;
  65. $this->url = $base . $uri;
  66. }
  67. /**
  68. * Initializes the URI object based on the url set on the object
  69. */
  70. public function init()
  71. {
  72. $grav = Grav::instance();
  73. $config = $grav['config'];
  74. $language = $grav['language'];
  75. // resets
  76. $this->paths = [];
  77. $this->params = [];
  78. $this->query = [];
  79. // get any params and remove them
  80. $uri = str_replace($this->root, '', $this->url);
  81. // remove double slashes
  82. $uri = preg_replace('#/{2,}#', '/', $uri);
  83. // remove the setup.php based base if set:
  84. $setup_base = $grav['pages']->base();
  85. if ($setup_base) {
  86. $uri = str_replace($setup_base, '', $uri);
  87. }
  88. // If configured to, redirect trailing slash URI's with a 301 redirect
  89. if ($config->get('system.pages.redirect_trailing_slash', false) && $uri != '/' && Utils::endsWith($uri, '/')) {
  90. $grav->redirect(rtrim($uri, '/'), 301);
  91. }
  92. // process params
  93. $uri = $this->processParams($uri, $config->get('system.param_sep'));
  94. // set active language
  95. $uri = $language->setActiveFromUri($uri);
  96. // split the URL and params
  97. $bits = parse_url($uri);
  98. // process query string
  99. if (isset($bits['query']) && isset($bits['path'])) {
  100. $this->query = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
  101. $uri = $bits['path'];
  102. }
  103. // remove the extension if there is one set
  104. $parts = pathinfo($uri);
  105. // set the original basename
  106. $this->basename = $parts['basename'];
  107. // set the extension
  108. if (isset($parts['extension'])) {
  109. $this->extension = $parts['extension'];
  110. }
  111. $valid_page_types = implode('|', $config->get('system.pages.types'));
  112. if (preg_match("/\.(".$valid_page_types.")$/", $parts['basename'])) {
  113. $uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS). '/' .$parts['filename'];
  114. }
  115. // set the new url
  116. $this->url = $this->root . $uri;
  117. $this->path = $uri;
  118. $this->content_path = trim(str_replace($this->base, '', $this->path), '/');
  119. if ($this->content_path != '') {
  120. $this->paths = explode('/', $this->content_path);
  121. }
  122. }
  123. /**
  124. * Process any params based in this URL, supports any valid delimiter
  125. *
  126. * @param $uri
  127. * @param string $delimiter
  128. *
  129. * @return string
  130. */
  131. private function processParams($uri, $delimiter = ':')
  132. {
  133. if (strpos($uri, $delimiter) !== false) {
  134. $bits = explode('/', $uri);
  135. $path = array();
  136. foreach ($bits as $bit) {
  137. if (strpos($bit, $delimiter) !== false) {
  138. $param = explode($delimiter, $bit);
  139. if (count($param) == 2) {
  140. $plain_var = filter_var(urldecode($param[1]), FILTER_SANITIZE_STRING);
  141. $this->params[$param[0]] = $plain_var;
  142. }
  143. } else {
  144. $path[] = $bit;
  145. }
  146. }
  147. $uri = '/' . ltrim(implode('/', $path), '/');
  148. }
  149. return $uri;
  150. }
  151. /**
  152. * Return URI path.
  153. *
  154. * @param string $id
  155. * @return string
  156. */
  157. public function paths($id = null)
  158. {
  159. if (isset($id)) {
  160. return $this->paths[$id];
  161. } else {
  162. return $this->paths;
  163. }
  164. }
  165. /**
  166. * Return route to the current URI. By default route doesn't include base path.
  167. *
  168. * @param bool $absolute True to include full path.
  169. * @param bool $domain True to include domain. Works only if first parameter is also true.
  170. * @return string
  171. */
  172. public function route($absolute = false, $domain = false)
  173. {
  174. return urldecode(($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths));
  175. }
  176. /**
  177. * Return full query string or a single query attribute.
  178. *
  179. * @param string $id Optional attribute.
  180. * @return string
  181. */
  182. public function query($id = null, $raw = false)
  183. {
  184. if (isset($id)) {
  185. return isset($this->query[$id]) ? $this->query[$id] : null;
  186. } else {
  187. if ($raw) {
  188. return $this->query;
  189. } else {
  190. return http_build_query($this->query);
  191. }
  192. }
  193. }
  194. /**
  195. * Return all or a single query parameter as a URI compatible string.
  196. *
  197. * @param string $id Optional parameter name.
  198. * @param boolean $array return the array format or not
  199. * @return null|string
  200. */
  201. public function params($id = null, $array = false)
  202. {
  203. $config = Grav::instance()['config'];
  204. $params = null;
  205. if ($id === null) {
  206. if ($array) {
  207. return $this->params;
  208. }
  209. $output = array();
  210. foreach ($this->params as $key => $value) {
  211. $output[] = $key . $config->get('system.param_sep') . $value;
  212. $params = '/'.implode('/', $output);
  213. }
  214. } elseif (isset($this->params[$id])) {
  215. if ($array) {
  216. return $this->params[$id];
  217. }
  218. $params = "/{$id}". $config->get('system.param_sep') . $this->params[$id];
  219. }
  220. return $params;
  221. }
  222. /**
  223. * Get URI parameter.
  224. *
  225. * @param string $id
  226. * @return bool|string
  227. */
  228. public function param($id)
  229. {
  230. if (isset($this->params[$id])) {
  231. return urldecode($this->params[$id]);
  232. } else {
  233. return false;
  234. }
  235. }
  236. /**
  237. * Return URL.
  238. *
  239. * @param bool $include_host Include hostname.
  240. * @return string
  241. */
  242. public function url($include_host = false)
  243. {
  244. if ($include_host) {
  245. return $this->url;
  246. } else {
  247. $url = (str_replace($this->base, '', rtrim($this->url, '/')));
  248. return $url ? $url : '/';
  249. }
  250. }
  251. /**
  252. * Return the Path
  253. *
  254. * @return String The path of the URI
  255. */
  256. public function path()
  257. {
  258. $path = $this->path;
  259. if ($path === '') {
  260. $path = '/';
  261. }
  262. return $path;
  263. }
  264. /**
  265. * Return the Extension of the URI
  266. *
  267. * @param null $default
  268. *
  269. * @return String The extension of the URI
  270. */
  271. public function extension($default = null)
  272. {
  273. if (!$this->extension) {
  274. $this->extension = $default;
  275. }
  276. return $this->extension;
  277. }
  278. /**
  279. * Return the host of the URI
  280. *
  281. * @return String The host of the URI
  282. */
  283. public function host()
  284. {
  285. return $this->host;
  286. }
  287. /**
  288. * Gets the environment name
  289. *
  290. * @return String
  291. */
  292. public function environment()
  293. {
  294. return $this->host();
  295. }
  296. /**
  297. * Return the basename of the URI
  298. *
  299. * @return String The basename of the URI
  300. */
  301. public function basename()
  302. {
  303. return $this->basename;
  304. }
  305. /**
  306. * Return the base of the URI
  307. *
  308. * @return String The base of the URI
  309. */
  310. public function base()
  311. {
  312. return $this->base;
  313. }
  314. /**
  315. * Return root URL to the site.
  316. *
  317. * @param bool $include_host Include hostname.
  318. * @return mixed
  319. */
  320. public function rootUrl($include_host = false)
  321. {
  322. if ($include_host) {
  323. return $this->root;
  324. } else {
  325. $root = str_replace($this->base, '', $this->root);
  326. return $root;
  327. }
  328. }
  329. /**
  330. * Return current page number.
  331. *
  332. * @return int
  333. */
  334. public function currentPage()
  335. {
  336. if (isset($this->params['page'])) {
  337. return $this->params['page'];
  338. } else {
  339. return 1;
  340. }
  341. }
  342. /**
  343. * Return relative path to the referrer defaulting to current or given page.
  344. *
  345. * @param string $default
  346. * @param string $attributes
  347. * @return string
  348. */
  349. public function referrer($default = null, $attributes = null)
  350. {
  351. $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
  352. // Check that referrer came from our site.
  353. $root = $this->rootUrl(true);
  354. if ($referrer) {
  355. // Referrer should always have host set and it should come from the same base address.
  356. if (stripos($referrer, $root) !== 0) {
  357. $referrer = null;
  358. }
  359. }
  360. if (!$referrer) {
  361. $referrer = $default ? $default : $this->route(true, true);
  362. }
  363. if ($attributes) {
  364. $referrer .= $attributes;
  365. }
  366. // Return relative path.
  367. return substr($referrer, strlen($root));
  368. }
  369. /**
  370. * Return the IP address of the current user
  371. *
  372. * @return string ip address
  373. */
  374. public function ip()
  375. {
  376. if (getenv('HTTP_CLIENT_IP'))
  377. $ipaddress = getenv('HTTP_CLIENT_IP');
  378. else if(getenv('HTTP_X_FORWARDED_FOR'))
  379. $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
  380. else if(getenv('HTTP_X_FORWARDED'))
  381. $ipaddress = getenv('HTTP_X_FORWARDED');
  382. else if(getenv('HTTP_FORWARDED_FOR'))
  383. $ipaddress = getenv('HTTP_FORWARDED_FOR');
  384. else if(getenv('HTTP_FORWARDED'))
  385. $ipaddress = getenv('HTTP_FORWARDED');
  386. else if(getenv('REMOTE_ADDR'))
  387. $ipaddress = getenv('REMOTE_ADDR');
  388. else
  389. $ipaddress = 'UNKNOWN';
  390. return $ipaddress;
  391. }
  392. /**
  393. * Is this an external URL? if it starts with `http` then yes, else false
  394. *
  395. * @param string $url the URL in question
  396. * @return boolean is eternal state
  397. */
  398. public function isExternal($url)
  399. {
  400. if (Utils::startsWith($url, 'http')) {
  401. return true;
  402. } else {
  403. return false;
  404. }
  405. }
  406. /**
  407. * The opposite of built-in PHP method parse_url()
  408. *
  409. * @param $parsed_url
  410. * @return string
  411. */
  412. public static function buildUrl($parsed_url)
  413. {
  414. $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
  415. $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
  416. $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
  417. $user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
  418. $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
  419. $pass = ($user || $pass) ? "$pass@" : '';
  420. $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
  421. $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
  422. $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
  423. return "$scheme$user$pass$host$port$path$query$fragment";
  424. }
  425. /**
  426. * Converts links from absolute '/' or relative (../..) to a grav friendly format
  427. *
  428. * @param $page the current page to use as reference
  429. * @param string $markdown_url the URL as it was written in the markdown
  430. *
  431. * @return string the more friendly formatted url
  432. */
  433. public static function convertUrl(Page $page, $markdown_url, $type = 'link')
  434. {
  435. $grav = Grav::instance();
  436. /** @var Grav\Common\Language\Language $language */
  437. $language = $grav['language'];
  438. // Link processing should prepend language
  439. $language_append = '';
  440. if ($type == 'link' && $language->enabled()) {
  441. $language_append = $language->getLanguageURLPrefix();
  442. }
  443. $pages_dir = $grav['locator']->findResource('page://');
  444. $base_url = rtrim($grav['base_url'] . $grav['pages']->base(), '/') . $language_append;
  445. // if absolute and starts with a base_url move on
  446. if (pathinfo($markdown_url, PATHINFO_DIRNAME) == '.' && $page->url() == '/') {
  447. return '/' . $markdown_url;
  448. // no path to convert
  449. } elseif ($base_url != '' && Utils::startsWith($markdown_url, $base_url)) {
  450. return $markdown_url;
  451. // if contains only a fragment
  452. } elseif (Utils::startsWith($markdown_url, '#')) {
  453. return $markdown_url;
  454. } else {
  455. $target = null;
  456. // see if page is relative to this or absolute
  457. if (Utils::startsWith($markdown_url, '/')) {
  458. $normalized_url = Utils::normalizePath($base_url . $markdown_url);
  459. $normalized_path = Utils::normalizePath($pages_dir . $markdown_url);
  460. } else {
  461. $normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url);
  462. $normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url);
  463. }
  464. // special check to see if path checking is required.
  465. $just_path = str_replace($normalized_url, '', $normalized_path);
  466. if ($just_path == $page->path()) {
  467. return $normalized_url;
  468. }
  469. $url_bits = parse_url($normalized_path);
  470. $full_path = ($url_bits['path']);
  471. if (file_exists($full_path)) {
  472. // do nothing
  473. } elseif (file_exists(urldecode($full_path))) {
  474. $full_path = urldecode($full_path);
  475. } else {
  476. return $normalized_url;
  477. }
  478. $path_info = pathinfo($full_path);
  479. $page_path = $path_info['dirname'];
  480. $filename = '';
  481. if ($markdown_url == '..') {
  482. $page_path = $full_path;
  483. } else {
  484. // save the filename if a file is part of the path
  485. if (is_file($full_path)) {
  486. if ($path_info['extension'] != 'md') {
  487. $filename = '/' . $path_info['basename'];
  488. }
  489. } else {
  490. $page_path = $full_path;
  491. }
  492. }
  493. // get page instances and try to find one that fits
  494. $instances = $grav['pages']->instances();
  495. if (isset($instances[$page_path])) {
  496. $target = $instances[$page_path];
  497. $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
  498. return Uri::buildUrl($url_bits);
  499. }
  500. return $normalized_url;
  501. }
  502. }
  503. }