3
declare(strict_types=1);
6
* This file is part of CodeIgniter 4 framework.
8
* (c) CodeIgniter Foundation <admin@codeigniter.com>
10
* For the full copyright and license information, please view
11
* the LICENSE file that was distributed with this source code.
14
namespace CodeIgniter\HTTP;
16
use BadMethodCallException;
17
use CodeIgniter\Exceptions\ConfigException;
18
use CodeIgniter\HTTP\Exceptions\HTTPException;
22
* URI for the application site
24
* @see \CodeIgniter\HTTP\SiteURITest
26
class SiteURI extends URI
29
* The current baseURL.
31
private readonly URI $baseURL;
34
* The path part of baseURL.
36
* The baseURL "http://example.com/" → '/'
37
* The baseURL "http://localhost:8888/ci431/public/" → '/ci431/public/'
39
private string $basePathWithoutIndexPage;
44
private string $indexPage;
47
* List of URI segments in baseURL and indexPage.
49
* If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b",
50
* and the baseURL is "http://localhost:8888/ci431/public/", then:
57
private array $baseSegments;
60
* List of URI segments after indexPage.
62
* The word "URI Segments" originally means only the URI path part relative
65
* If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b",
66
* and the baseURL is "http://localhost:8888/ci431/public/", then:
73
* @deprecated This property will be private.
78
* URI path relative to baseURL.
80
* If the baseURL contains sub folders, this value will be different from
81
* the current URI path.
83
* This value never starts with '/'.
85
private string $routePath;
88
* @param string $relativePath URI path relative to baseURL. May include
89
* queries or fragments.
90
* @param string|null $host Optional current hostname.
91
* @param string|null $scheme Optional scheme. 'http' or 'https'.
92
* @phpstan-param 'http'|'https'|null $scheme
94
public function __construct(
96
string $relativePath = '',
98
?string $scheme = null
100
$this->indexPage = $configApp->indexPage;
102
$this->baseURL = $this->determineBaseURL($configApp, $host, $scheme);
104
$this->setBasePath();
106
// Fix routePath, query, fragment
107
[$routePath, $query, $fragment] = $this->parseRelativePath($relativePath);
109
// Fix indexPage and routePath
110
$indexPageRoutePath = $this->getIndexPageRoutePath($routePath);
112
// Fix the current URI
113
$uri = $this->baseURL . $indexPageRoutePath;
116
$parts = parse_url($uri);
117
if ($parts === false) {
118
throw HTTPException::forUnableToParseURI($uri);
120
$parts['query'] = $query;
121
$parts['fragment'] = $fragment;
122
$this->applyParts($parts);
124
$this->setRoutePath($routePath);
127
private function parseRelativePath(string $relativePath): array
129
$parts = parse_url('http://dummy/' . $relativePath);
130
if ($parts === false) {
131
throw HTTPException::forUnableToParseURI($relativePath);
134
$routePath = $relativePath === '/' ? '/' : ltrim($parts['path'], '/');
136
$query = $parts['query'] ?? '';
137
$fragment = $parts['fragment'] ?? '';
139
return [$routePath, $query, $fragment];
142
private function determineBaseURL(
147
$baseURL = $this->normalizeBaseURL($configApp);
149
$uri = new URI($baseURL);
152
if ($scheme !== null && $scheme !== '') {
153
$uri->setScheme($scheme);
154
} elseif ($configApp->forceGlobalSecureRequests) {
155
$uri->setScheme('https');
159
if ($host !== null) {
160
$uri->setHost($host);
166
private function getIndexPageRoutePath(string $routePath): string
168
// Remove starting slash unless it is `/`.
169
if ($routePath !== '' && $routePath[0] === '/' && $routePath !== '/') {
170
$routePath = ltrim($routePath, '/');
173
// Check for an index page
175
if ($this->indexPage !== '') {
176
$indexPage = $this->indexPage;
178
// Check if we need a separator
179
if ($routePath !== '' && $routePath[0] !== '/' && $routePath[0] !== '?') {
184
$indexPageRoutePath = $indexPage . $routePath;
186
if ($indexPageRoutePath === '/') {
187
$indexPageRoutePath = '';
190
return $indexPageRoutePath;
193
private function normalizeBaseURL(App $configApp): string
195
// It's possible the user forgot a trailing slash on their
196
// baseURL, so let's help them out.
197
$baseURL = rtrim($configApp->baseURL, '/ ') . '/';
200
if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) {
201
throw new ConfigException(
202
'Config\App::$baseURL "' . $baseURL . '" is not a valid URL.'
210
* Sets basePathWithoutIndexPage and baseSegments.
212
private function setBasePath(): void
214
$this->basePathWithoutIndexPage = $this->baseURL->getPath();
216
$this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage);
218
if ($this->indexPage !== '') {
219
$this->baseSegments[] = $this->indexPage;
226
public function setBaseURL(string $baseURL): void
228
throw new BadMethodCallException('Cannot use this method.');
234
public function setURI(?string $uri = null)
236
throw new BadMethodCallException('Cannot use this method.');
240
* Returns the baseURL.
244
public function getBaseURL(): string
246
return (string) $this->baseURL;
250
* Returns the URI path relative to baseURL.
252
* @return string The Route path.
254
public function getRoutePath(): string
256
return $this->routePath;
260
* Formats the URI as a string.
262
public function __toString(): string
264
return static::createURIString(
266
$this->getAuthority(),
274
* Sets the route path (and segments).
278
public function setPath(string $path)
280
$this->setRoutePath($path);
286
* Sets the route path (and segments).
288
private function setRoutePath(string $routePath): void
290
$routePath = $this->filterPath($routePath);
292
$indexPageRoutePath = $this->getIndexPageRoutePath($routePath);
294
$this->path = $this->basePathWithoutIndexPage . $indexPageRoutePath;
296
$this->routePath = ltrim($routePath, '/');
298
$this->segments = $this->convertToSegments($this->routePath);
302
* Converts path to segments
304
private function convertToSegments(string $path): array
306
$tempPath = trim($path, '/');
308
return ($tempPath === '') ? [] : explode('/', $tempPath);
312
* Sets the path portion of the URI based on segments.
316
* @deprecated This method will be private.
318
public function refreshPath()
320
$allSegments = array_merge($this->baseSegments, $this->segments);
321
$this->path = '/' . $this->filterPath(implode('/', $allSegments));
323
if ($this->routePath === '/' && $this->path !== '/') {
327
$this->routePath = $this->filterPath(implode('/', $this->segments));
333
* Saves our parts from a parse_url() call.
335
protected function applyParts(array $parts): void
337
if (! empty($parts['host'])) {
338
$this->host = $parts['host'];
340
if (! empty($parts['user'])) {
341
$this->user = $parts['user'];
343
if (isset($parts['path']) && $parts['path'] !== '') {
344
$this->path = $this->filterPath($parts['path']);
346
if (! empty($parts['query'])) {
347
$this->setQuery($parts['query']);
349
if (! empty($parts['fragment'])) {
350
$this->fragment = $parts['fragment'];
354
if (isset($parts['scheme'])) {
355
$this->setScheme(rtrim($parts['scheme'], ':/'));
357
$this->setScheme('http');
361
if (isset($parts['port']) && $parts['port'] !== null) {
362
// Valid port numbers are enforced by earlier parse_url() or setPort()
363
$this->port = $parts['port'];
366
if (isset($parts['pass'])) {
367
$this->password = $parts['pass'];
372
* For base_url() helper.
374
* @param array|string $relativePath URI string or array of URI segments.
375
* @param string|null $scheme URI scheme. E.g., http, ftp. If empty
376
* string '' is set, a protocol-relative
379
public function baseUrl($relativePath = '', ?string $scheme = null): string
381
$relativePath = $this->stringifyRelativePath($relativePath);
383
$config = clone config(App::class);
384
$config->indexPage = '';
386
$host = $this->getHost();
388
$uri = new self($config, $relativePath, $host, $scheme);
390
// Support protocol-relative links
391
if ($scheme === '') {
392
return substr((string) $uri, strlen($uri->getScheme()) + 1);
395
return (string) $uri;
399
* @param array|string $relativePath URI string or array of URI segments
401
private function stringifyRelativePath($relativePath): string
403
if (is_array($relativePath)) {
404
$relativePath = implode('/', $relativePath);
407
return $relativePath;
411
* For site_url() helper.
413
* @param array|string $relativePath URI string or array of URI segments.
414
* @param string|null $scheme URI scheme. E.g., http, ftp. If empty
415
* string '' is set, a protocol-relative
417
* @param App|null $config Alternate configuration to use.
419
public function siteUrl($relativePath = '', ?string $scheme = null, ?App $config = null): string
421
$relativePath = $this->stringifyRelativePath($relativePath);
423
// Check current host.
424
$host = $config === null ? $this->getHost() : null;
426
$config ??= config(App::class);
428
$uri = new self($config, $relativePath, $host, $scheme);
430
// Support protocol-relative links
431
if ($scheme === '') {
432
return substr((string) $uri, strlen($uri->getScheme()) + 1);
435
return (string) $uri;