body = $body; $this->config = $config; parent::__construct($config, $uri); $this->populateHeaders(); $this->uri = $uri; $this->detectURI($config->uriProtocol, $config->baseURL); $this->validLocales = $config->supportedLocales; $this->detectLocale($config); } //-------------------------------------------------------------------- /** * Handles setting up the locale, perhaps auto-detecting through * content negotiation. * * @param $config */ public function detectLocale($config) { $this->locale = $this->defaultLocale = $config->defaultLocale; if (! $config->negotiateLocale) { return; } $this->setLocale($this->negotiate('language', $config->supportedLocales)); } //-------------------------------------------------------------------- /** * Returns the default locale as set in Config\App.php * * @return string */ public function getDefaultLocale(): string { return $this->defaultLocale; } //-------------------------------------------------------------------- /** * Gets the current locale, with a fallback to the default * locale if none is set. * * @return string */ public function getLocale(): string { return $this->locale ?? $this->defaultLocale; } //-------------------------------------------------------------------- /** * Sets the locale string for this request. * * @param string $locale * * @return $this */ public function setLocale(string $locale) { // If it's not a valid locale, set it // to the default locale for the site. if (! in_array($locale, $this->validLocales)) { $locale = $this->defaultLocale; } $this->locale = $locale; // If the intl extension is loaded, make sure // that we set the locale for it... if not, though, // don't worry about it. try { if (class_exists('\Locale', false)) { \Locale::setDefault($locale); } } catch (\Exception $e) {} return $this; } //-------------------------------------------------------------------- /** * Determines if this request was made from the command line (CLI). * * @return bool */ public function isCLI(): bool { return (PHP_SAPI === 'cli' || defined('STDIN')); } //-------------------------------------------------------------------- /** * Test to see if a request contains the HTTP_X_REQUESTED_WITH header. * * @return bool */ public function isAJAX(): bool { return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); } //-------------------------------------------------------------------- /** * Attempts to detect if the current connection is secure through * a few different methods. * * @return bool */ public function isSecure(): bool { if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') { return true; } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { return true; } elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') { return true; } return false; } //-------------------------------------------------------------------- /** * Fetch an item from the $_REQUEST object. This is the simplest way * to grab data from the request object and can be used in lieu of the * other get* methods in most cases. * * @param null $index * @param null $filter * * @return mixed */ public function getVar($index = null, $filter = null) { return $this->fetchGlobal(INPUT_REQUEST, $index, $filter); } //-------------------------------------------------------------------- /** * A convenience method that grabs the raw input stream and decodes * the JSON into an array. * * If $assoc == true, then all objects in the response will be converted * to associative arrays. * * @param bool $assoc Whether to return objects as associative arrays * @param int $depth How many levels deep to decode * @param int $options Bitmask of options * * @see http://php.net/manual/en/function.json-decode.php * * @return mixed */ public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0) { return json_decode($this->body, $assoc, $depth, $options); } //-------------------------------------------------------------------- /** * Fetch an item from GET data. * * @param null $index Index for item to fetch from $_GET. * @param null $filter A filter name to apply. * * @return mixed */ public function getGet($index = null, $filter = null) { return $this->fetchGlobal(INPUT_GET, $index, $filter); } //-------------------------------------------------------------------- /** * Fetch an item from POST. * * @param null $index Index for item to fetch from $_POST. * @param null $filter A filter name to apply * * @return mixed */ public function getPost($index = null, $filter = null) { return $this->fetchGlobal(INPUT_POST, $index, $filter); } //-------------------------------------------------------------------- /** * Fetch an item from POST data with fallback to GET. * * @param null $index Index for item to fetch from $_POST or $_GET * @param null $filter A filter name to apply * * @return mixed */ public function getPostGet($index = null, $filter = null) { // Use $_POST directly here, since filter_has_var only // checks the initial POST data, not anything that might // have been added since. return isset($_POST[$index]) ? $this->getPost($index, $filter) : $this->getGet($index, $filter); } //-------------------------------------------------------------------- /** * Fetch an item from GET data with fallback to POST. * * @param null $index Index for item to be fetched from $_GET or $_POST * @param null $filter A filter name to apply * * @return mixed */ public function getGetPost($index = null, $filter = null) { // Use $_GET directly here, since filter_has_var only // checks the initial GET data, not anything that might // have been added since. return isset($_GET[$index]) ? $this->getGet($index, $filter) : $this->getPost($index, $filter); } //-------------------------------------------------------------------- /** * Fetch an item from the COOKIE array. * * @param null $index Index for item to be fetched from $_COOKIE * @param null $filter A filter name to be applied * * @return mixed */ public function getCookie($index = null, $filter = null) { return $this->fetchGlobal(INPUT_COOKIE, $index, $filter); } //-------------------------------------------------------------------- /** * Fetch the user agent string * * @param null $filter */ public function getUserAgent($filter = null) { return $this->fetchGlobal(INPUT_SERVER, 'HTTP_USER_AGENT', $filter); } //-------------------------------------------------------------------- /** * Returns an array of all files that have been uploaded with this * request. Each file is represented by an UploadedFile instance. * * @return array */ public function getFiles(): array { if (is_null($this->files)) { // @todo modify to use Services, at the very least. $this->files = new FileCollection(); } return $this->files->all(); } //-------------------------------------------------------------------- /** * Retrieves a single file by the name of the input field used * to upload it. * * @param string $fileID * * @return UploadedFile|null */ public function getFile(string $fileID) { if (is_null($this->files)) { // @todo modify to use Services, at the very least. $this->files = new FileCollection(); } return $this->files->getFile($fileID); } //-------------------------------------------------------------------- /** * Sets up our URI object based on the information we have. This is * either provided by the user in the baseURL Config setting, or * determined from the environment as needed. * * @param $protocol * @param $baseURL */ protected function detectURI($protocol, $baseURL) { $this->uri->setPath($this->detectPath($protocol)); // Based on our baseURL provided by the developer (if set) // set our current domain name, scheme if ( ! empty($baseURL)) { // We cannot add the path here, otherwise it's possible // that the routing will not work correctly if we are // within a sub-folder scheme. So it's modified in // the $this->uri->setScheme(parse_url($baseURL, PHP_URL_SCHEME)); $this->uri->setHost(parse_url($baseURL, PHP_URL_HOST)); $this->uri->setPort(parse_url($baseURL, PHP_URL_PORT)); $this->uri->resolveRelativeURI(parse_url($baseURL, PHP_URL_PATH)); } else { $this->isSecure() ? $this->uri->setScheme('https') : $this->uri->setScheme('http'); // While both SERVER_NAME and HTTP_HOST are open to security issues, // if we have to choose, we will go with the server-controlled version first. ! empty($_SERVER['SERVER_NAME']) ? (isset($_SERVER['SERVER_NAME']) ? $this->uri->setHost($_SERVER['SERVER_NAME']) : null) : (isset($_SERVER['HTTP_HOST']) ? $this->uri->setHost($_SERVER['HTTP_HOST']) : null); if ( ! empty($_SERVER['SERVER_PORT'])) { $this->uri->setPort($_SERVER['SERVER_PORT']); } } } //-------------------------------------------------------------------- /** * Based on the URIProtocol Config setting, will attempt to * detect the path portion of the current URI. * * @param $protocol * * @return string|string */ public function detectPath($protocol) { if (empty($protocol)) { $protocol = 'REQUEST_URI'; } switch ($protocol) { case 'REQUEST_URI': $path = $this->parseRequestURI(); break; case 'QUERY_STRING': $path = $this->parseQueryString(); break; case 'PATH_INFO': default: $path = isset($_SERVER[$protocol]) ? $_SERVER[$protocol] : $this->parseRequestURI(); break; } return $path; } //-------------------------------------------------------------------- /** * Provides a convenient way to work with the Negotiate class * for content negotiation. * * @param string $type * @param array $supported * @param bool $strictMatch * * @return mixed */ public function negotiate(string $type, array $supported, bool $strictMatch = false) { if (is_null($this->negotiate)) { $this->negotiate = Services::negotiator($this, true); } switch (strtolower($type)) { case 'media': return $this->negotiate->media($supported, $strictMatch); break; case 'charset': return $this->negotiate->charset($supported); break; case 'encoding': return $this->negotiate->encoding($supported); break; case 'language': return $this->negotiate->language($supported); break; } throw new \InvalidArgumentException($type.' is not a valid negotiation type.'); } //-------------------------------------------------------------------- /** * Will parse the REQUEST_URI and automatically detect the URI from it, * fixing the query string if necessary. * * @return string The URI it found. */ protected function parseRequestURI(): string { if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME'])) { return ''; } // parse_url() returns false if no host is present, but the path or query string // contains a colon followed by a number $parts = parse_url('http://dummy'.$_SERVER['REQUEST_URI']); $query = isset($parts['query']) ? $parts['query'] : ''; $uri = isset($parts['path']) ? $parts['path'] : ''; if (isset($_SERVER['SCRIPT_NAME'][0])) { if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) { $uri = (string)substr($uri, strlen($_SERVER['SCRIPT_NAME'])); } elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) { $uri = (string)substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); } } // This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct // URI is found, and also fixes the QUERY_STRING getServer var and $_GET array. if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0) { $query = explode('?', $query, 2); $uri = $query[0]; $_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : ''; } else { $_SERVER['QUERY_STRING'] = $query; } parse_str($_SERVER['QUERY_STRING'], $_GET); if ($uri === '/' || $uri === '') { return '/'; } return $this->removeRelativeDirectory($uri); } //-------------------------------------------------------------------- /** * Parse QUERY_STRING * * Will parse QUERY_STRING and automatically detect the URI from it. * * @return string */ protected function parseQueryString(): string { $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); if (trim($uri, '/') === '') { return ''; } elseif (strncmp($uri, '/', 1) === 0) { $uri = explode('?', $uri, 2); $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : ''; $uri = $uri[0]; } parse_str($_SERVER['QUERY_STRING'], $_GET); return $this->removeRelativeDirectory($uri); } //-------------------------------------------------------------------- /** * Remove relative directory (../) and multi slashes (///) * * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri() * * @param string $uri * * @return string */ protected function removeRelativeDirectory($uri) { $uris = []; $tok = strtok($uri, '/'); while ($tok !== false) { if (( ! empty($tok) || $tok === '0') && $tok !== '..') { $uris[] = $tok; } $tok = strtok('/'); } return implode('/', $uris); } // -------------------------------------------------------------------- }