Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add SiteURI class #7252

Merged
merged 31 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7ac527d
feat: add SiteURI class
kenjis Feb 14, 2023
7eec7bb
chore: add system/HTTP/SiteURI.php
kenjis Feb 14, 2023
5593f8a
docs: add @deprecated
kenjis Feb 14, 2023
8a66de5
test: fix incorrect test
kenjis Feb 14, 2023
406128f
refactor: fix typo in method name
kenjis Feb 14, 2023
7d91c0e
docs: update PHPDocs
kenjis Feb 14, 2023
b728948
docs: add @deprecated
kenjis Feb 15, 2023
9af4994
feat: add validation for baseURL
kenjis Feb 15, 2023
5fa5305
feat: add getBaseURL() implementation
kenjis Feb 15, 2023
c8d5eef
feat: add param $relativePath to constructor
kenjis Feb 16, 2023
b30035b
feat: add param $host to constructor
kenjis Feb 16, 2023
9d82338
feat: add param $scheme to constructor
kenjis Feb 17, 2023
b231d1c
style: break long line
kenjis Feb 17, 2023
4932572
fix: remove unneeded methods
kenjis Feb 17, 2023
c3628d1
fix: change default values to null
kenjis Feb 17, 2023
57afaf9
fix: handling fragment
kenjis Feb 17, 2023
246eea2
fix: host in baseURL may be wrong
kenjis Feb 18, 2023
08bbc18
fix: disable setBaseURL() method
kenjis Feb 18, 2023
ab6da61
test: add assertions
kenjis Feb 20, 2023
82a567d
refactor: SiteURI::__construct()
kenjis Feb 20, 2023
ccfd79d
test: add test cases for path starting with slash
kenjis Feb 20, 2023
e733417
fix: change behavior
kenjis Feb 21, 2023
33fc028
test: use dataProviders
kenjis Feb 21, 2023
ba35e57
docs: update comments
kenjis Feb 22, 2023
fe754f3
fix: change behavior
kenjis Feb 23, 2023
3f4a9fd
fix: remove $allowedHostnames check
kenjis Feb 23, 2023
ecf96eb
test: add test cases
kenjis Feb 23, 2023
5193414
refactor: remove unneeded override
kenjis Feb 24, 2023
b24c01c
docs: fix typo in comments
kenjis Jul 10, 2023
3e349e1
docs: make URI::setSilent() deprecated
kenjis Jul 10, 2023
e529291
feat: add URI::withScheme() and deprecate URI::setScheme()
kenjis Jul 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test-phpcpd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ jobs:
--exclude system/Database/OCI8/Builder.php
--exclude system/Database/Postgre/Builder.php
--exclude system/Debug/Exceptions.php
--exclude system/HTTP/SiteURI.php
-- app/ public/ system/
366 changes: 366 additions & 0 deletions system/HTTP/SiteURI.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\HTTP;

use BadMethodCallException;
use CodeIgniter\Exceptions\ConfigException;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use Config\App;

/**
* URI for the application site
*/
class SiteURI extends URI
{
/**
* The current baseURL.
*/
private URI $baseURL;

/**
* The path part of baseURL.
*
* The baseURL "http://example.com/" → '/'
* The baseURL "http://localhost:8888/ci431/public/" → '/ci431/public/'
*/
private string $basePathWithoutIndexPage;

/**
* The Index File.
*/
private string $indexPage;

/**
* List of URI segments in baseURL and indexPage.
*
* If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b",
* and the baseUR is "http://localhost:8888/ci431/public/", then:
MGatner marked this conversation as resolved.
Show resolved Hide resolved
* $baseSegments = [
* 0 => 'ci431',
* 1 => 'public',
* 2 => 'index.php',
* ];
*/
private array $baseSegments;

/**
* List of URI segments after indexPage.
*
* The word "URI Segments" originally means only the URI path part relative
* to the baseURL.
*
* If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b",
* and the baseUR is "http://localhost:8888/ci431/public/", then:
MGatner marked this conversation as resolved.
Show resolved Hide resolved
* $segments = [
* 0 => 'test',
* ];
*
* @var array
*
* @deprecated This property will be private.
MGatner marked this conversation as resolved.
Show resolved Hide resolved
*/
protected $segments;

/**
* URI path relative to baseURL.
*
* If the baseURL contains sub folders, this value will be different from
* the current URI path.
*
* This value never starts with '/'.
*/
private string $routePath;

/**
* @param string $relativePath URI path relative to baseURL. May include
* queries or fragments.
* @param string|null $host Optional current hostname.
* @param string|null $scheme Optional scheme. 'http' or 'https'.
* @phpstan-param 'http'|'https'|null $scheme
*/
public function __construct(
App $configApp,
string $relativePath = '',
?string $host = null,
?string $scheme = null
) {
$this->indexPage = $configApp->indexPage;

$this->baseURL = $this->determineBaseURL($configApp, $host, $scheme);

$this->setBasePath();

// Fix routePath, query, fragment
[$routePath, $query, $fragment] = $this->parseRelativePath($relativePath);

// Fix indexPage and routePath
$indexPageRoutePath = $this->getIndexPageRoutePath($routePath);

// Fix the current URI
$uri = $this->baseURL . $indexPageRoutePath;

// applyParts
$parts = parse_url($uri);
if ($parts === false) {
throw HTTPException::forUnableToParseURI($uri);
}
$parts['query'] = $query;
$parts['fragment'] = $fragment;
$this->applyParts($parts);

$this->setRoutePath($routePath);
}

private function parseRelativePath(string $relativePath): array
{
$parts = parse_url('http://dummy/' . $relativePath);
if ($parts === false) {
throw HTTPException::forUnableToParseURI($relativePath);
}

$routePath = $relativePath === '/' ? '/' : ltrim($parts['path'], '/');

$query = $parts['query'] ?? '';
$fragment = $parts['fragment'] ?? '';

return [$routePath, $query, $fragment];
}

private function determineBaseURL(
App $configApp,
?string $host,
?string $scheme
): URI {
$baseURL = $this->normalizeBaseURL($configApp);

$uri = new URI($baseURL);

// Update scheme
if ($scheme !== null) {
$uri->setScheme($scheme);
} elseif ($configApp->forceGlobalSecureRequests) {
$uri->setScheme('https');
}

// Update host
if ($host !== null) {
$uri->setHost($host);
}

return $uri;
}

private function getIndexPageRoutePath(string $routePath): string
{
// Remove starting slash unless it is `/`.
if ($routePath !== '' && $routePath[0] === '/' && $routePath !== '/') {
$routePath = ltrim($routePath, '/');
}

// Check for an index page
$indexPage = '';
if ($this->indexPage !== '') {
$indexPage = $this->indexPage;

// Check if we need a separator
if ($routePath !== '' && $routePath[0] !== '/' && $routePath[0] !== '?') {
$indexPage .= '/';
}
}

$indexPageRoutePath = $indexPage . $routePath;

if ($indexPageRoutePath === '/') {
$indexPageRoutePath = '';
}

return $indexPageRoutePath;
}

private function normalizeBaseURL(App $configApp): string
{
// It's possible the user forgot a trailing slash on their
// baseURL, so let's help them out.
$baseURL = rtrim($configApp->baseURL, '/ ') . '/';

// Validate baseURL
if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) {
throw new ConfigException(
'Config\App::$baseURL is invalid.'
);
}

return $baseURL;
}

/**
* Sets basePathWithoutIndexPage and baseSegments.
*/
private function setBasePath(): void
{
$this->basePathWithoutIndexPage = $this->baseURL->getPath();

$this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage);

if ($this->indexPage) {
$this->baseSegments[] = $this->indexPage;
}
}

/**
* @deprecated
*/
public function setBaseURL(string $baseURL): void
{
throw new BadMethodCallException('Cannot use this method.');
}

/**
* @deprecated
*/
public function setURI(?string $uri = null)
{
throw new BadMethodCallException('Cannot use this method.');
}

/**
* Returns the baseURL.
*
* @interal
*/
public function getBaseURL(): string
{
return (string) $this->baseURL;
}

/**
* Returns the URI path relative to baseURL.
*
* @return string The Route path.
*/
public function getRoutePath(): string
{
return $this->routePath;
}

/**
* Formats the URI as a string.
*/
public function __toString(): string
{
return static::createURIString(
$this->getScheme(),
$this->getAuthority(),
$this->getPath(),
$this->getQuery(),
$this->getFragment()
);
}

/**
* Sets the route path (and segments).
*
* @return $this
*/
public function setPath(string $path)
{
$this->setRoutePath($path);

return $this;
}

/**
* Sets the route path (and segments).
*/
private function setRoutePath(string $routePath): void
{
$routePath = $this->filterPath($routePath);

$indexPageRoutePath = $this->getIndexPageRoutePath($routePath);

$this->path = $this->basePathWithoutIndexPage . $indexPageRoutePath;

$this->routePath = ltrim($routePath, '/');

$this->segments = $this->convertToSegments($this->routePath);
}

/**
* Converts path to segments
*/
private function convertToSegments(string $path): array
{
$tempPath = trim($path, '/');

return ($tempPath === '') ? [] : explode('/', $tempPath);
}

/**
* Sets the path portion of the URI based on segments.
*
* @return $this
*
* @deprecated This method will be private.
*/
public function refreshPath()
{
$allSegments = array_merge($this->baseSegments, $this->segments);
$this->path = '/' . $this->filterPath(implode('/', $allSegments));

if ($this->routePath === '/' && $this->path !== '/') {
$this->path .= '/';
}

$this->routePath = $this->filterPath(implode('/', $this->segments));

return $this;
}

/**
* Saves our parts from a parse_url() call.
*/
protected function applyParts(array $parts)
{
if (! empty($parts['host'])) {
$this->host = $parts['host'];
}
if (! empty($parts['user'])) {
$this->user = $parts['user'];
}
if (isset($parts['path']) && $parts['path'] !== '') {
$this->path = $this->filterPath($parts['path']);
}
if (! empty($parts['query'])) {
$this->setQuery($parts['query']);
}
if (! empty($parts['fragment'])) {
$this->fragment = $parts['fragment'];
}

// Scheme
if (isset($parts['scheme'])) {
$this->setScheme(rtrim($parts['scheme'], ':/'));
} else {
$this->setScheme('http');
}

// Port
if (isset($parts['port']) && $parts['port'] !== null) {
// Valid port numbers are enforced by earlier parse_url() or setPort()
$this->port = $parts['port'];
}

if (isset($parts['pass'])) {
$this->password = $parts['pass'];
}
}
}
Loading