"Fossies" - the Fresh Open Source Software Archive

Member "grav/system/src/Grav/Common/Uri.php" (1 Sep 2020, 43715 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Uri.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: v1.6.24_vs_v1.6.25.

    1 <?php
    2 
    3 /**
    4  * @package    Grav\Common
    5  *
    6  * @copyright  Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
    7  * @license    MIT License; see LICENSE file for details.
    8  */
    9 
   10 namespace Grav\Common;
   11 
   12 use Grav\Common\Config\Config;
   13 use Grav\Common\Language\Language;
   14 use Grav\Common\Page\Interfaces\PageInterface;
   15 use Grav\Common\Page\Pages;
   16 use Grav\Framework\Route\RouteFactory;
   17 use Grav\Framework\Uri\UriFactory;
   18 use Grav\Framework\Uri\UriPartsFilter;
   19 use RocketTheme\Toolbox\Event\Event;
   20 
   21 class Uri
   22 {
   23     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])$/';
   24 
   25     /** @var \Grav\Framework\Uri\Uri */
   26     protected static $currentUri;
   27 
   28     /** @var \Grav\Framework\Route\Route */
   29     protected static $currentRoute;
   30 
   31     public $url;
   32 
   33     // Uri parts.
   34     protected $scheme;
   35     protected $user;
   36     protected $password;
   37     protected $host;
   38     protected $port;
   39     protected $path;
   40     protected $query;
   41     protected $fragment;
   42 
   43     // Internal stuff.
   44     protected $base;
   45     protected $basename;
   46     protected $content_path;
   47     protected $extension;
   48     protected $env;
   49     protected $paths;
   50     protected $queries;
   51     protected $params;
   52     protected $root;
   53     protected $root_path;
   54     protected $uri;
   55     protected $content_type;
   56     protected $post;
   57 
   58     /**
   59      * Uri constructor.
   60      * @param string|array $env
   61      */
   62     public function __construct($env = null)
   63     {
   64         if (is_string($env)) {
   65             $this->createFromString($env);
   66         } else {
   67             $this->createFromEnvironment(\is_array($env) ? $env : $_SERVER);
   68         }
   69     }
   70 
   71     /**
   72      * Initialize the URI class with a url passed via parameter.
   73      * Used for testing purposes.
   74      *
   75      * @param string $url the URL to use in the class
   76      *
   77      * @return $this
   78      */
   79     public function initializeWithUrl($url = '')
   80     {
   81         if ($url) {
   82             $this->createFromString($url);
   83         }
   84 
   85         return $this;
   86     }
   87 
   88     /**
   89      * Initialize the URI class by providing url and root_path arguments
   90      *
   91      * @param string $url
   92      * @param string $root_path
   93      *
   94      * @return $this
   95      */
   96     public function initializeWithUrlAndRootPath($url, $root_path)
   97     {
   98         $this->initializeWithUrl($url);
   99         $this->root_path = $root_path;
  100 
  101         return $this;
  102     }
  103 
  104     /**
  105      * Validate a hostname
  106      *
  107      * @param string $hostname The hostname
  108      *
  109      * @return boolean
  110      */
  111     public function validateHostname($hostname)
  112     {
  113         return (bool)preg_match(static::HOSTNAME_REGEX, $hostname);
  114     }
  115 
  116     /**
  117      * Initializes the URI object based on the url set on the object
  118      */
  119     public function init()
  120     {
  121         $grav = Grav::instance();
  122 
  123         /** @var Config $config */
  124         $config = $grav['config'];
  125 
  126         /** @var Language $language */
  127         $language = $grav['language'];
  128 
  129         // add the port to the base for non-standard ports
  130         if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
  131             $this->base .= ':' . (string)$this->port;
  132         }
  133 
  134         // Handle custom base
  135         $custom_base = rtrim($grav['config']->get('system.custom_base_url'), '/');
  136 
  137         if ($custom_base) {
  138             $custom_parts = parse_url($custom_base);
  139             $orig_root_path = $this->root_path;
  140             $this->root_path = isset($custom_parts['path']) ? rtrim($custom_parts['path'], '/') : '';
  141             if (isset($custom_parts['scheme'])) {
  142                 $this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
  143                 $this->root = $custom_base;
  144             } else {
  145                 $this->root = $this->base . $this->root_path;
  146             }
  147             $this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri);
  148         } else {
  149             $this->root = $this->base . $this->root_path;
  150         }
  151 
  152         $this->url = $this->base . $this->uri;
  153 
  154         $uri = Utils::replaceFirstOccurrence(static::filterPath($this->root), '', $this->url);
  155 
  156         // remove the setup.php based base if set:
  157         $setup_base = $grav['pages']->base();
  158         if ($setup_base) {
  159             $uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
  160         }
  161 
  162         // process params
  163         $uri = $this->processParams($uri, $config->get('system.param_sep'));
  164 
  165         // set active language
  166         $uri = $language->setActiveFromUri($uri);
  167 
  168         // split the URL and params
  169         $bits = parse_url($uri);
  170 
  171         //process fragment
  172         if (isset($bits['fragment'])) {
  173             $this->fragment = $bits['fragment'];
  174         }
  175 
  176         // Get the path. If there's no path, make sure pathinfo() still returns dirname variable
  177         $path = $bits['path'] ?? '/';
  178 
  179         // remove the extension if there is one set
  180         $parts = pathinfo($path);
  181 
  182         // set the original basename
  183         $this->basename = $parts['basename'];
  184 
  185         // set the extension
  186         if (isset($parts['extension'])) {
  187             $this->extension = $parts['extension'];
  188         }
  189 
  190         // Strip the file extension for valid page types
  191         if ($this->isValidExtension($this->extension)) {
  192             $path = Utils::replaceLastOccurrence(".{$this->extension}", '', $path);
  193         }
  194 
  195         // set the new url
  196         $this->url = $this->root . $path;
  197         $this->path = static::cleanPath($path);
  198         $this->content_path = trim(Utils::replaceFirstOccurrence($this->base, '', $this->path), '/');
  199         if ($this->content_path !== '') {
  200             $this->paths = explode('/', $this->content_path);
  201         }
  202 
  203         // Set some Grav stuff
  204         $grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true);
  205         $grav['base_url_relative'] = $this->rootUrl(false);
  206         $grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
  207 
  208         RouteFactory::setRoot($this->root_path);
  209         RouteFactory::setLanguage($language->getLanguageURLPrefix());
  210     }
  211 
  212     /**
  213      * Return URI path.
  214      *
  215      * @param string $id
  216      *
  217      * @return string|string[]
  218      */
  219     public function paths($id = null)
  220     {
  221         if ($id !== null) {
  222             return $this->paths[$id];
  223         }
  224 
  225         return $this->paths;
  226     }
  227 
  228     /**
  229      * Return route to the current URI. By default route doesn't include base path.
  230      *
  231      * @param bool $absolute True to include full path.
  232      * @param bool $domain True to include domain. Works only if first parameter is also true.
  233      *
  234      * @return string
  235      */
  236     public function route($absolute = false, $domain = false)
  237     {
  238         return ($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths);
  239     }
  240 
  241     /**
  242      * Return full query string or a single query attribute.
  243      *
  244      * @param string $id Optional attribute. Get a single query attribute if set
  245      * @param bool $raw If true and $id is not set, return the full query array. Otherwise return the query string
  246      *
  247      * @return string|array Returns an array if $id = null and $raw = true
  248      */
  249     public function query($id = null, $raw = false)
  250     {
  251         if ($id !== null) {
  252             return $this->queries[$id] ?? null;
  253         }
  254 
  255         if ($raw) {
  256             return $this->queries;
  257         }
  258 
  259         if (!$this->queries) {
  260             return '';
  261         }
  262 
  263         return http_build_query($this->queries);
  264     }
  265 
  266     /**
  267      * Return all or a single query parameter as a URI compatible string.
  268      *
  269      * @param string $id Optional parameter name.
  270      * @param boolean $array return the array format or not
  271      *
  272      * @return null|string|array
  273      */
  274     public function params($id = null, $array = false)
  275     {
  276         $config = Grav::instance()['config'];
  277         $sep = $config->get('system.param_sep');
  278 
  279         $params = null;
  280         if ($id === null) {
  281             if ($array) {
  282                 return $this->params;
  283             }
  284             $output = [];
  285             foreach ($this->params as $key => $value) {
  286                 $output[] = "{$key}{$sep}{$value}";
  287                 $params = '/' . implode('/', $output);
  288             }
  289         } elseif (isset($this->params[$id])) {
  290             if ($array) {
  291                 return $this->params[$id];
  292             }
  293             $params = "/{$id}{$sep}{$this->params[$id]}";
  294         }
  295 
  296         return $params;
  297     }
  298 
  299     /**
  300      * Get URI parameter.
  301      *
  302      * @param string $id
  303      *
  304      * @return bool|string
  305      */
  306     public function param($id)
  307     {
  308         if (isset($this->params[$id])) {
  309             return html_entity_decode(rawurldecode($this->params[$id]), ENT_COMPAT | ENT_HTML401, 'UTF-8');
  310         }
  311 
  312         return false;
  313     }
  314 
  315     /**
  316      * Gets the Fragment portion of a URI (eg #target)
  317      *
  318      * @param string $fragment
  319      *
  320      * @return string|null
  321      */
  322     public function fragment($fragment = null)
  323     {
  324         if ($fragment !== null) {
  325             $this->fragment = $fragment;
  326         }
  327         return $this->fragment;
  328     }
  329 
  330     /**
  331      * Return URL.
  332      *
  333      * @param bool $include_host Include hostname.
  334      *
  335      * @return string
  336      */
  337     public function url($include_host = false)
  338     {
  339         if ($include_host) {
  340             return $this->url;
  341         }
  342 
  343         $url = Utils::replaceFirstOccurrence($this->base, '', rtrim($this->url, '/'));
  344 
  345         return $url ?: '/';
  346     }
  347 
  348     /**
  349      * Return the Path
  350      *
  351      * @return String The path of the URI
  352      */
  353     public function path()
  354     {
  355         return $this->path;
  356     }
  357 
  358     /**
  359      * Return the Extension of the URI
  360      *
  361      * @param string|null $default
  362      *
  363      * @return string The extension of the URI
  364      */
  365     public function extension($default = null)
  366     {
  367         if (!$this->extension) {
  368             $this->extension = $default;
  369         }
  370 
  371         return $this->extension;
  372     }
  373 
  374     public function method()
  375     {
  376         $method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
  377 
  378         if ($method === 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) {
  379             $method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']);
  380         }
  381 
  382         return $method;
  383     }
  384 
  385     /**
  386      * Return the scheme of the URI
  387      *
  388      * @param bool $raw
  389      * @return string The scheme of the URI
  390      */
  391     public function scheme($raw = false)
  392     {
  393         if (!$raw) {
  394             $scheme = '';
  395             if ($this->scheme) {
  396                 $scheme = $this->scheme . '://';
  397             } elseif ($this->host) {
  398                 $scheme = '//';
  399             }
  400 
  401             return $scheme;
  402         }
  403 
  404         return $this->scheme;
  405     }
  406 
  407 
  408     /**
  409      * Return the host of the URI
  410      *
  411      * @return string|null The host of the URI
  412      */
  413     public function host()
  414     {
  415         return $this->host;
  416     }
  417 
  418     /**
  419      * Return the port number if it can be figured out
  420      *
  421      * @param bool $raw
  422      * @return int|null
  423      */
  424     public function port($raw = false)
  425     {
  426         $port = $this->port;
  427         // If not in raw mode and port is not set, figure it out from scheme.
  428         if (!$raw && $port === null) {
  429             if ($this->scheme === 'http') {
  430                 $this->port = 80;
  431             } elseif ($this->scheme === 'https') {
  432                 $this->port = 443;
  433             }
  434         }
  435 
  436         return $this->port;
  437     }
  438 
  439     /**
  440      * Return user
  441      *
  442      * @return string|null
  443      */
  444     public function user()
  445     {
  446         return $this->user;
  447     }
  448 
  449     /**
  450      * Return password
  451      *
  452      * @return string|null
  453      */
  454     public function password()
  455     {
  456         return $this->password;
  457     }
  458 
  459     /**
  460      * Gets the environment name
  461      *
  462      * @return String
  463      */
  464     public function environment()
  465     {
  466         return $this->env;
  467     }
  468 
  469 
  470     /**
  471      * Return the basename of the URI
  472      *
  473      * @return String The basename of the URI
  474      */
  475     public function basename()
  476     {
  477         return $this->basename;
  478     }
  479 
  480     /**
  481      * Return the full uri
  482      *
  483      * @param bool $include_root
  484      * @return mixed
  485      */
  486     public function uri($include_root = true)
  487     {
  488         if ($include_root) {
  489             return $this->uri;
  490         }
  491 
  492         return Utils::replaceFirstOccurrence($this->root_path, '', $this->uri);
  493     }
  494 
  495     /**
  496      * Return the base of the URI
  497      *
  498      * @return String The base of the URI
  499      */
  500     public function base()
  501     {
  502         return $this->base;
  503     }
  504 
  505     /**
  506      * Return the base relative URL including the language prefix
  507      * or the base relative url if multi-language is not enabled
  508      *
  509      * @return String The base of the URI
  510      */
  511     public function baseIncludingLanguage()
  512     {
  513         $grav = Grav::instance();
  514 
  515         /** @var Pages $pages */
  516         $pages = $grav['pages'];
  517 
  518         return $pages->baseUrl(null, false);
  519     }
  520 
  521     /**
  522      * Return root URL to the site.
  523      *
  524      * @param bool $include_host Include hostname.
  525      *
  526      * @return mixed
  527      */
  528     public function rootUrl($include_host = false)
  529     {
  530         if ($include_host) {
  531             return $this->root;
  532         }
  533 
  534         return Utils::replaceFirstOccurrence($this->base, '', $this->root);
  535     }
  536 
  537     /**
  538      * Return current page number.
  539      *
  540      * @return int
  541      */
  542     public function currentPage()
  543     {
  544         $page = (int)($this->params['page'] ?? 1);
  545 
  546         return max(1, $page);
  547     }
  548 
  549     /**
  550      * Return relative path to the referrer defaulting to current or given page.
  551      *
  552      * @param string $default
  553      * @param string $attributes
  554      *
  555      * @return string
  556      */
  557     public function referrer($default = null, $attributes = null)
  558     {
  559         $referrer = $_SERVER['HTTP_REFERER'] ?? null;
  560 
  561         // Check that referrer came from our site.
  562         $root = $this->rootUrl(true);
  563         if ($referrer) {
  564             // Referrer should always have host set and it should come from the same base address.
  565             if (stripos($referrer, $root) !== 0) {
  566                 $referrer = null;
  567             }
  568         }
  569 
  570         if (!$referrer) {
  571             $referrer = $default ?: $this->route(true, true);
  572         }
  573 
  574         if ($attributes) {
  575             $referrer .= $attributes;
  576         }
  577 
  578         // Return relative path.
  579         return substr($referrer, strlen($root));
  580     }
  581 
  582     public function __toString()
  583     {
  584         return static::buildUrl($this->toArray());
  585     }
  586 
  587     public function toOriginalString()
  588     {
  589         return static::buildUrl($this->toArray(true));
  590     }
  591 
  592     public function toArray($full = false)
  593     {
  594         if ($full === true) {
  595             $root_path = $this->root_path ?? '';
  596             $extension = isset($this->extension) && $this->isValidExtension($this->extension) ? '.' . $this->extension : '';
  597             $path = $root_path . $this->path . $extension;
  598         } else {
  599             $path = $this->path;
  600         }
  601 
  602         return [
  603             'scheme'    => $this->scheme,
  604             'host'      => $this->host,
  605             'port'      => $this->port,
  606             'user'      => $this->user,
  607             'pass'      => $this->password,
  608             'path'      => $path,
  609             'params'    => $this->params,
  610             'query'     => $this->query,
  611             'fragment'  => $this->fragment
  612         ];
  613     }
  614 
  615     /**
  616      * Calculate the parameter regex based on the param_sep setting
  617      *
  618      * @return string
  619      */
  620     public static function paramsRegex()
  621     {
  622         return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
  623     }
  624 
  625     /**
  626      * Return the IP address of the current user
  627      *
  628      * @return string ip address
  629      */
  630     public static function ip()
  631     {
  632         if (getenv('HTTP_CLIENT_IP')) {
  633             $ip = getenv('HTTP_CLIENT_IP');
  634         } elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
  635             $ip = getenv('HTTP_X_FORWARDED_FOR');
  636         } elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
  637             $ip = getenv('HTTP_X_FORWARDED');
  638         } elseif (getenv('HTTP_FORWARDED_FOR')) {
  639             $ip = getenv('HTTP_FORWARDED_FOR');
  640         } elseif (getenv('HTTP_FORWARDED')) {
  641             $ip = getenv('HTTP_FORWARDED');
  642         } elseif (getenv('REMOTE_ADDR')){
  643             $ip = getenv('REMOTE_ADDR');
  644         } else {
  645             $ip = 'UNKNOWN';
  646         }
  647 
  648         return $ip;
  649     }
  650 
  651     /**
  652      * Returns current Uri.
  653      *
  654      * @return \Grav\Framework\Uri\Uri
  655      */
  656     public static function getCurrentUri()
  657     {
  658         if (!static::$currentUri) {
  659             static::$currentUri = UriFactory::createFromEnvironment($_SERVER);
  660         }
  661 
  662         return static::$currentUri;
  663     }
  664 
  665     /**
  666      * Returns current route.
  667      *
  668      * @return \Grav\Framework\Route\Route
  669      */
  670     public static function getCurrentRoute()
  671     {
  672         if (!static::$currentRoute) {
  673             $uri = Grav::instance()['uri'];
  674             static::$currentRoute = RouteFactory::createFromParts($uri->toArray());
  675         }
  676 
  677         return static::$currentRoute;
  678     }
  679 
  680     /**
  681      * Is this an external URL? if it starts with `http` then yes, else false
  682      *
  683      * @param  string $url the URL in question
  684      *
  685      * @return boolean      is eternal state
  686      */
  687     public static function isExternal($url)
  688     {
  689         return (0 === strpos($url, 'http://') || 0 === strpos($url, 'https://') || 0 === strpos($url, '//'));
  690     }
  691 
  692     /**
  693      * The opposite of built-in PHP method parse_url()
  694      *
  695      * @param array $parsed_url
  696      *
  697      * @return string
  698      */
  699     public static function buildUrl($parsed_url)
  700     {
  701         $scheme    = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . ':' : '';
  702         $authority = isset($parsed_url['host']) ? '//' : '';
  703         $host      = $parsed_url['host'] ?? '';
  704         $port      = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
  705         $user      = $parsed_url['user'] ?? '';
  706         $pass      = isset($parsed_url['pass']) ? ':' . $parsed_url['pass']  : '';
  707         $pass      = ($user || $pass) ? "{$pass}@" : '';
  708         $path      = $parsed_url['path'] ?? '';
  709         $path      = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path;
  710         $query     = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
  711         $fragment  = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
  712 
  713         return "{$scheme}{$authority}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
  714     }
  715 
  716     /**
  717      * @param array $params
  718      * @return string
  719      */
  720     public static function buildParams(array $params)
  721     {
  722         if (!$params) {
  723             return '';
  724         }
  725 
  726         $grav = Grav::instance();
  727         $sep = $grav['config']->get('system.param_sep');
  728 
  729         $output = [];
  730         foreach ($params as $key => $value) {
  731             $output[] = "{$key}{$sep}{$value}";
  732         }
  733 
  734         return '/' . implode('/', $output);
  735     }
  736 
  737     /**
  738      * Converts links from absolute '/' or relative (../..) to a Grav friendly format
  739      *
  740      * @param PageInterface $page the current page to use as reference
  741      * @param string|array $url the URL as it was written in the markdown
  742      * @param string $type the type of URL, image | link
  743      * @param bool $absolute if null, will use system default, if true will use absolute links internally
  744      * @param bool $route_only only return the route, not full URL path
  745      * @return string|array the more friendly formatted url
  746      */
  747     public static function convertUrl(PageInterface $page, $url, $type = 'link', $absolute = false, $route_only = false)
  748     {
  749         $grav = Grav::instance();
  750 
  751         $uri = $grav['uri'];
  752 
  753         // Link processing should prepend language
  754         $language = $grav['language'];
  755         $language_append = '';
  756         if ($type === 'link' && $language->enabled()) {
  757             $language_append = $language->getLanguageURLPrefix();
  758         }
  759 
  760         // Handle Excerpt style $url array
  761         $url_path = is_array($url) ? $url['path'] : $url;
  762 
  763         $external          = false;
  764         $base              = $grav['base_url_relative'];
  765         $base_url          = rtrim($base . $grav['pages']->base(), '/') . $language_append;
  766         $pages_dir         = $grav['locator']->findResource('page://');
  767 
  768         // if absolute and starts with a base_url move on
  769         if (isset($url['scheme']) && Utils::startsWith($url['scheme'], 'http')) {
  770             $external = true;
  771         } elseif ($url_path === '' && isset($url['fragment'])) {
  772             $external = true;
  773         } elseif ($url_path === '/' || ($base_url !== '' && Utils::startsWith($url_path, $base_url))) {
  774             $url_path = $base_url . $url_path;
  775         } else {
  776 
  777             // see if page is relative to this or absolute
  778             if (Utils::startsWith($url_path, '/')) {
  779                 $normalized_url = Utils::normalizePath($base_url . $url_path);
  780                 $normalized_path = Utils::normalizePath($pages_dir . $url_path);
  781             } else {
  782                 $page_route = ($page->home() && !empty($url_path)) ? $page->rawRoute() : $page->route();
  783                 $normalized_url = $base_url . Utils::normalizePath(rtrim($page_route, '/') . '/' . $url_path);
  784                 $normalized_path = Utils::normalizePath($page->path() . '/' . $url_path);
  785             }
  786 
  787             // special check to see if path checking is required.
  788             $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
  789             if ($normalized_url === '/' || $just_path === $page->path()) {
  790                 $url_path = $normalized_url;
  791             } else {
  792                 $url_bits = static::parseUrl($normalized_path);
  793                 $full_path = $url_bits['path'];
  794                 $raw_full_path = rawurldecode($full_path);
  795 
  796                 if (file_exists($raw_full_path)) {
  797                     $full_path = $raw_full_path;
  798                 } elseif (!file_exists($full_path)) {
  799                     $full_path = false;
  800                 }
  801 
  802                 if ($full_path) {
  803                     $path_info = pathinfo($full_path);
  804                     $page_path = $path_info['dirname'];
  805                     $filename = '';
  806 
  807                     if ($url_path === '..') {
  808                         $page_path = $full_path;
  809                     } else {
  810                         // save the filename if a file is part of the path
  811                         if (is_file($full_path)) {
  812                             if ($path_info['extension'] !== 'md') {
  813                                 $filename = '/' . $path_info['basename'];
  814                             }
  815                         } else {
  816                             $page_path = $full_path;
  817                         }
  818                     }
  819 
  820                     // get page instances and try to find one that fits
  821                     $instances = $grav['pages']->instances();
  822                     if (isset($instances[$page_path])) {
  823                         /** @var PageInterface $target */
  824                         $target = $instances[$page_path];
  825                         $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
  826 
  827                         $url_path = Uri::buildUrl($url_bits);
  828                     } else {
  829                         $url_path = $normalized_url;
  830                     }
  831                 } else {
  832                     $url_path = $normalized_url;
  833                 }
  834             }
  835         }
  836 
  837         // handle absolute URLs
  838         if (\is_array($url) && !$external && ($absolute === true || $grav['config']->get('system.absolute_urls', false))) {
  839 
  840             $url['scheme'] = $uri->scheme(true);
  841             $url['host'] = $uri->host();
  842             $url['port'] = $uri->port(true);
  843 
  844             // check if page exists for this route, and if so, check if it has SSL enabled
  845             $pages = $grav['pages'];
  846             $routes = $pages->routes();
  847 
  848             // if this is an image, get the proper path
  849             $url_bits = pathinfo($url_path);
  850             if (isset($url_bits['extension'])) {
  851                 $target_path = $url_bits['dirname'];
  852             } else {
  853                 $target_path = $url_path;
  854             }
  855 
  856             // strip base from this path
  857             $target_path = Utils::replaceFirstOccurrence($uri->rootUrl(), '', $target_path);
  858 
  859             // set to / if root
  860             if (empty($target_path)) {
  861                 $target_path = '/';
  862             }
  863 
  864             // look to see if this page exists and has ssl enabled
  865             if (isset($routes[$target_path])) {
  866                 $target_page = $pages->get($routes[$target_path]);
  867                 if ($target_page) {
  868                     $ssl_enabled = $target_page->ssl();
  869                     if ($ssl_enabled !== null) {
  870                         if ($ssl_enabled) {
  871                             $url['scheme'] = 'https';
  872                         } else {
  873                             $url['scheme'] = 'http';
  874                         }
  875                     }
  876                 }
  877             }
  878         }
  879 
  880         // Handle route only
  881         if ($route_only) {
  882             $url_path = Utils::replaceFirstOccurrence(static::filterPath($base_url), '', $url_path);
  883         }
  884 
  885         // transform back to string/array as needed
  886         if (is_array($url)) {
  887             $url['path'] = $url_path;
  888         } else {
  889             $url = $url_path;
  890         }
  891 
  892         return $url;
  893     }
  894 
  895     public static function parseUrl($url)
  896     {
  897         $grav = Grav::instance();
  898 
  899         $encodedUrl = preg_replace_callback(
  900             '%[^:/@?&=#]+%usD',
  901             function ($matches) { return rawurlencode($matches[0]); },
  902             $url
  903         );
  904 
  905         $parts = parse_url($encodedUrl);
  906 
  907         if (false === $parts) {
  908             return false;
  909         }
  910 
  911         foreach($parts as $name => $value) {
  912             $parts[$name] = rawurldecode($value);
  913         }
  914 
  915         if (!isset($parts['path'])) {
  916             $parts['path'] = '';
  917         }
  918 
  919         list($stripped_path, $params) = static::extractParams($parts['path'], $grav['config']->get('system.param_sep'));
  920 
  921         if (!empty($params)) {
  922             $parts['path'] = $stripped_path;
  923             $parts['params'] = $params;
  924         }
  925 
  926         return $parts;
  927     }
  928 
  929     public static function extractParams($uri, $delimiter)
  930     {
  931         $params = [];
  932 
  933         if (strpos($uri, $delimiter) !== false) {
  934             preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
  935 
  936             foreach ($matches as $match) {
  937                 $param = explode($delimiter, $match[1]);
  938                 if (\count($param) === 2) {
  939                     $plain_var = filter_var(rawurldecode($param[1]), FILTER_SANITIZE_STRING);
  940                     $params[$param[0]] = $plain_var;
  941                     $uri = str_replace($match[0], '', $uri);
  942                 }
  943             }
  944         }
  945 
  946         return [$uri, $params];
  947     }
  948 
  949     /**
  950      * Converts links from absolute '/' or relative (../..) to a Grav friendly format
  951      *
  952      * @param PageInterface   $page         the current page to use as reference
  953      * @param string $markdown_url the URL as it was written in the markdown
  954      * @param string $type         the type of URL, image | link
  955      * @param null   $relative     if null, will use system default, if true will use relative links internally
  956      *
  957      * @return string the more friendly formatted url
  958      */
  959     public static function convertUrlOld(PageInterface $page, $markdown_url, $type = 'link', $relative = null)
  960     {
  961         $grav = Grav::instance();
  962 
  963         $language = $grav['language'];
  964 
  965         // Link processing should prepend language
  966         $language_append = '';
  967         if ($type === 'link' && $language->enabled()) {
  968             $language_append = $language->getLanguageURLPrefix();
  969         }
  970         $pages_dir = $grav['locator']->findResource('page://');
  971         if ($relative === null) {
  972             $base = $grav['base_url'];
  973         } else {
  974             $base = $relative ? $grav['base_url_relative'] : $grav['base_url_absolute'];
  975         }
  976 
  977         $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append;
  978 
  979         // if absolute and starts with a base_url move on
  980         if (pathinfo($markdown_url, PATHINFO_DIRNAME) === '.' && $page->url() === '/') {
  981             return '/' . $markdown_url;
  982         }
  983         // no path to convert
  984         if ($base_url !== '' && Utils::startsWith($markdown_url, $base_url)) {
  985             return $markdown_url;
  986         }
  987         // if contains only a fragment
  988         if (Utils::startsWith($markdown_url, '#')) {
  989             return $markdown_url;
  990         }
  991 
  992         $target = null;
  993         // see if page is relative to this or absolute
  994         if (Utils::startsWith($markdown_url, '/')) {
  995             $normalized_url = Utils::normalizePath($base_url . $markdown_url);
  996             $normalized_path = Utils::normalizePath($pages_dir . $markdown_url);
  997         } else {
  998             $normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url);
  999             $normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url);
 1000         }
 1001 
 1002         // special check to see if path checking is required.
 1003         $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
 1004         if ($just_path === $page->path()) {
 1005             return $normalized_url;
 1006         }
 1007 
 1008         $url_bits = parse_url($normalized_path);
 1009         $full_path = $url_bits['path'];
 1010 
 1011         if (file_exists($full_path)) {
 1012             // do nothing
 1013         } elseif (file_exists(rawurldecode($full_path))) {
 1014             $full_path = rawurldecode($full_path);
 1015         } else {
 1016             return $normalized_url;
 1017         }
 1018 
 1019         $path_info = pathinfo($full_path);
 1020         $page_path = $path_info['dirname'];
 1021         $filename = '';
 1022 
 1023         if ($markdown_url === '..') {
 1024             $page_path = $full_path;
 1025         } else {
 1026             // save the filename if a file is part of the path
 1027             if (is_file($full_path)) {
 1028                 if ($path_info['extension'] !== 'md') {
 1029                     $filename = '/' . $path_info['basename'];
 1030                 }
 1031             } else {
 1032                 $page_path = $full_path;
 1033             }
 1034         }
 1035 
 1036         // get page instances and try to find one that fits
 1037         $instances = $grav['pages']->instances();
 1038         if (isset($instances[$page_path])) {
 1039             /** @var PageInterface $target */
 1040             $target = $instances[$page_path];
 1041             $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
 1042 
 1043             return static::buildUrl($url_bits);
 1044         }
 1045 
 1046         return $normalized_url;
 1047     }
 1048 
 1049     /**
 1050      * Adds the nonce to a URL for a specific action
 1051      *
 1052      * @param string $url            the url
 1053      * @param string $action         the action
 1054      * @param string $nonceParamName the param name to use
 1055      *
 1056      * @return string the url with the nonce
 1057      */
 1058     public static function addNonce($url, $action, $nonceParamName = 'nonce')
 1059     {
 1060         $fake = $url && strpos($url, '/') === 0;
 1061 
 1062         if ($fake) {
 1063             $url = 'http://domain.com' . $url;
 1064         }
 1065         $uri = new static($url);
 1066         $parts = $uri->toArray();
 1067         $nonce = Utils::getNonce($action);
 1068         $parts['params'] = ($parts['params'] ?? []) + [$nonceParamName => $nonce];
 1069 
 1070         if ($fake) {
 1071             unset($parts['scheme'], $parts['host']);
 1072         }
 1073 
 1074         return static::buildUrl($parts);
 1075     }
 1076 
 1077     /**
 1078      * Is the passed in URL a valid URL?
 1079      *
 1080      * @param string $url
 1081      * @return bool
 1082      */
 1083     public static function isValidUrl($url)
 1084     {
 1085         $regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
 1086         if (preg_match($regex, $url)) {
 1087             return true;
 1088         }
 1089 
 1090         return false;
 1091     }
 1092 
 1093     /**
 1094      * Removes extra double slashes and fixes back-slashes
 1095      *
 1096      * @param string $path
 1097      * @return mixed|string
 1098      */
 1099     public static function cleanPath($path)
 1100     {
 1101         $regex = '/(\/)\/+/';
 1102         $path = str_replace(['\\', '/ /'], '/', $path);
 1103         $path = preg_replace($regex,'$1',$path);
 1104 
 1105         return $path;
 1106     }
 1107 
 1108     /**
 1109      * Filters the user info string.
 1110      *
 1111      * @param string $info The raw user or password.
 1112      * @return string The percent-encoded user or password string.
 1113      */
 1114     public static function filterUserInfo($info)
 1115     {
 1116         return $info !== null ? UriPartsFilter::filterUserInfo($info) : '';
 1117     }
 1118 
 1119     /**
 1120      * Filter Uri path.
 1121      *
 1122      * This method percent-encodes all reserved
 1123      * characters in the provided path string. This method
 1124      * will NOT double-encode characters that are already
 1125      * percent-encoded.
 1126      *
 1127      * @param  string $path The raw uri path.
 1128      * @return string       The RFC 3986 percent-encoded uri path.
 1129      * @link   http://www.faqs.org/rfcs/rfc3986.html
 1130      */
 1131     public static function filterPath($path)
 1132     {
 1133         return $path !== null ? UriPartsFilter::filterPath($path) : '';
 1134     }
 1135 
 1136     /**
 1137      * Filters the query string or fragment of a URI.
 1138      *
 1139      * @param string $query The raw uri query string.
 1140      * @return string The percent-encoded query string.
 1141      */
 1142     public static function filterQuery($query)
 1143     {
 1144         return $query !== null ? UriPartsFilter::filterQueryOrFragment($query) : '';
 1145     }
 1146 
 1147     /**
 1148      * @param array $env
 1149      */
 1150     protected function createFromEnvironment(array $env)
 1151     {
 1152         // Build scheme.
 1153         if (isset($env['HTTP_X_FORWARDED_PROTO']) && Grav::instance()['config']->get('system.http_x_forwarded.protocol')) {
 1154             $this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
 1155         } elseif (isset($env['X-FORWARDED-PROTO'])) {
 1156             $this->scheme = $env['X-FORWARDED-PROTO'];
 1157         } elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
 1158             $this->scheme = $env['HTTP_CLOUDFRONT_FORWARDED_PROTO'];
 1159         } elseif (isset($env['REQUEST_SCHEME']) && empty($env['HTTPS'])) {
 1160            $this->scheme = $env['REQUEST_SCHEME'];
 1161         } else {
 1162             $https = $env['HTTPS'] ?? '';
 1163             $this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
 1164         }
 1165 
 1166         // Build user and password.
 1167         $this->user = $env['PHP_AUTH_USER'] ?? null;
 1168         $this->password = $env['PHP_AUTH_PW'] ?? null;
 1169 
 1170         // Build host.
 1171         if (isset($env['HTTP_X_FORWARDED_HOST']) && Grav::instance()['config']->get('system.http_x_forwarded.host')) {
 1172             $hostname = $env['HTTP_X_FORWARDED_HOST'];
 1173         } else if (isset($env['HTTP_HOST'])) {
 1174             $hostname = $env['HTTP_HOST'];
 1175         } elseif (isset($env['SERVER_NAME'])) {
 1176             $hostname = $env['SERVER_NAME'];
 1177         } else {
 1178             $hostname = 'localhost';
 1179         }
 1180         // Remove port from HTTP_HOST generated $hostname
 1181         $hostname = Utils::substrToString($hostname, ':');
 1182         // Validate the hostname
 1183         $this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
 1184 
 1185         // Build port.
 1186         if (isset($env['HTTP_X_FORWARDED_PORT']) && Grav::instance()['config']->get('system.http_x_forwarded.port')) {
 1187            $this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
 1188         } elseif (isset($env['X-FORWARDED-PORT'])) {
 1189            $this->port = (int)$env['X-FORWARDED-PORT'];
 1190         } elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
 1191            // Since AWS Cloudfront does not provide a forwarded port header,
 1192            // we have to build the port using the scheme.
 1193            $this->port = $this->port();
 1194         } elseif (isset($env['SERVER_PORT'])) {
 1195            $this->port = (int)$env['SERVER_PORT'];
 1196         } else {
 1197            $this->port = null;
 1198         }
 1199 
 1200         if ($this->hasStandardPort()) {
 1201             $this->port = null;
 1202         }
 1203 
 1204         // Build path.
 1205         $request_uri = $env['REQUEST_URI'] ?? '';
 1206         $this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
 1207 
 1208         // Build query string.
 1209         $this->query =  $env['QUERY_STRING'] ?? '';
 1210         if ($this->query === '') {
 1211             $this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
 1212         }
 1213 
 1214         // Support ngnix routes.
 1215         if (strpos($this->query, '_url=') === 0) {
 1216             parse_str($this->query, $query);
 1217             unset($query['_url']);
 1218             $this->query = http_build_query($query);
 1219         }
 1220 
 1221         // Build fragment.
 1222         $this->fragment = null;
 1223 
 1224         // Filter userinfo, path and query string.
 1225         $this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
 1226         $this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
 1227         $this->path = empty($this->path) ? '/' : static::filterPath($this->path);
 1228         $this->query = static::filterQuery($this->query);
 1229 
 1230         $this->reset();
 1231     }
 1232 
 1233     /**
 1234      * Does this Uri use a standard port?
 1235      *
 1236      * @return bool
 1237      */
 1238     protected function hasStandardPort()
 1239     {
 1240         return ($this->port === 80 || $this->port === 443);
 1241     }
 1242 
 1243     /**
 1244      * @param string $url
 1245      */
 1246     protected function createFromString($url)
 1247     {
 1248         // Set Uri parts.
 1249         $parts = parse_url($url);
 1250         if ($parts === false) {
 1251             throw new \RuntimeException('Malformed URL: ' . $url);
 1252         }
 1253         $this->scheme = $parts['scheme'] ?? null;
 1254         $this->user = $parts['user'] ?? null;
 1255         $this->password = $parts['pass'] ?? null;
 1256         $this->host = $parts['host'] ?? null;
 1257         $this->port = isset($parts['port']) ? (int)$parts['port'] : null;
 1258         $this->path = $parts['path'] ?? '';
 1259         $this->query = $parts['query'] ?? '';
 1260         $this->fragment = $parts['fragment'] ?? null;
 1261 
 1262         // Validate the hostname
 1263         if ($this->host) {
 1264             $this->host = $this->validateHostname($this->host) ? $this->host : 'unknown';
 1265         }
 1266         // Filter userinfo, path, query string and fragment.
 1267         $this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
 1268         $this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
 1269         $this->path = empty($this->path) ? '/' : static::filterPath($this->path);
 1270         $this->query = static::filterQuery($this->query);
 1271         $this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null;
 1272 
 1273         $this->reset();
 1274     }
 1275 
 1276     protected function reset()
 1277     {
 1278         // resets
 1279         parse_str($this->query, $this->queries);
 1280         $this->extension    = null;
 1281         $this->basename     = null;
 1282         $this->paths        = [];
 1283         $this->params       = [];
 1284         $this->env          = $this->buildEnvironment();
 1285         $this->uri          = $this->path . (!empty($this->query) ? '?' . $this->query : '');
 1286 
 1287         $this->base         = $this->buildBaseUrl();
 1288         $this->root_path    = $this->buildRootPath();
 1289         $this->root         = $this->base . $this->root_path;
 1290         $this->url          = $this->base . $this->uri;
 1291     }
 1292 
 1293     /**
 1294      * Get post from either $_POST or JSON response object
 1295      * By default returns all data, or can return a single item
 1296      *
 1297      * @param string $element
 1298      * @param string $filter_type
 1299      * @return array|mixed|null
 1300      */
 1301     public function post($element = null, $filter_type = null)
 1302     {
 1303         if (!$this->post) {
 1304             $content_type = $this->getContentType();
 1305             if ($content_type === 'application/json') {
 1306                 $json = file_get_contents('php://input');
 1307                 $this->post = json_decode($json, true);
 1308             } elseif (!empty($_POST)) {
 1309                 $this->post = (array)$_POST;
 1310             }
 1311 
 1312             $event = new Event(['post' => &$this->post]);
 1313             Grav::instance()->fireEvent('onHttpPostFilter', $event);
 1314         }
 1315 
 1316         if ($this->post && null !== $element) {
 1317             $item = Utils::getDotNotation($this->post, $element);
 1318             if ($filter_type) {
 1319                 $item = filter_var($item, $filter_type);
 1320             }
 1321             return $item;
 1322         }
 1323 
 1324         return $this->post;
 1325     }
 1326 
 1327     /**
 1328      * Get content type from request
 1329      *
 1330      * @param bool $short
 1331      * @return null|string
 1332      */
 1333     public function getContentType($short = true)
 1334     {
 1335         if (isset($_SERVER['CONTENT_TYPE'])) {
 1336             $content_type = $_SERVER['CONTENT_TYPE'];
 1337             if ($short) {
 1338                 return Utils::substrToString($content_type,';');
 1339             }
 1340             return $content_type;
 1341         }
 1342         return null;
 1343     }
 1344 
 1345     /**
 1346      * Check if this is a valid Grav extension
 1347      *
 1348      * @param $extension
 1349      * @return bool
 1350      */
 1351     public function isValidExtension($extension)
 1352     {
 1353         $valid_page_types = implode('|', Utils::getSupportPageTypes());
 1354 
 1355         // Strip the file extension for valid page types
 1356         if (preg_match('/(' . $valid_page_types . ')/', $extension)) {
 1357             return true;
 1358         }
 1359         return false;
 1360     }
 1361 
 1362     /**
 1363      * Allow overriding of any element (be careful!)
 1364      *
 1365      * @param $data
 1366      * @return Uri
 1367      */
 1368     public function setUriProperties($data)
 1369     {
 1370         foreach (get_object_vars($this) as $property => $default) {
 1371             if (!array_key_exists($property, $data)) continue;
 1372             $this->{$property} = $data[$property]; // assign value to object
 1373         }
 1374         return $this;
 1375     }
 1376 
 1377     /**
 1378      * Get the base URI with port if needed
 1379      *
 1380      * @return string
 1381      */
 1382     private function buildBaseUrl()
 1383     {
 1384         return $this->scheme() . $this->host;
 1385     }
 1386 
 1387     /**
 1388      * Get the Grav Root Path
 1389      *
 1390      * @return string
 1391      */
 1392     private function buildRootPath()
 1393     {
 1394         // In Windows script path uses backslash, convert it:
 1395         $scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
 1396         $rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
 1397 
 1398         return $rootPath;
 1399     }
 1400 
 1401     private function buildEnvironment()
 1402     {
 1403         // check for localhost variations
 1404         if ($this->host === '127.0.0.1' || $this->host === '::1') {
 1405             return 'localhost';
 1406         }
 1407 
 1408         return $this->host ?: 'unknown';
 1409     }
 1410 
 1411     /**
 1412      * Process any params based in this URL, supports any valid delimiter
 1413      *
 1414      * @param string $uri
 1415      * @param string $delimiter
 1416      *
 1417      * @return string
 1418      */
 1419     private function processParams($uri, $delimiter = ':')
 1420     {
 1421         if (strpos($uri, $delimiter) !== false) {
 1422             preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
 1423 
 1424             foreach ($matches as $match) {
 1425                 $param = explode($delimiter, $match[1]);
 1426                 if (count($param) === 2) {
 1427                     $plain_var = filter_var($param[1], FILTER_SANITIZE_STRING);
 1428                     $this->params[$param[0]] = $plain_var;
 1429                     $uri = str_replace($match[0], '', $uri);
 1430                 }
 1431             }
 1432         }
 1433         return $uri;
 1434     }
 1435 }