Skip to content

Commit b6d4ac1

Browse files
authored
Smart URL Rewriting v2 (#6527)
* Improve the typing of the ApplicationHandler * Add support for smart URL rewriting * Ignore the path parameter if a `PACKAGE_ID` is already set * Include the `app.config.inc.php` of the primary app * Skip the check for the root app during setup
1 parent cf32bb2 commit b6d4ac1

File tree

8 files changed

+156
-31
lines changed

8 files changed

+156
-31
lines changed

wcfsetup/install/files/global.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
<?php
22

33
/**
4-
* @author Marcel Werk
4+
* @author Marcel Werk
55
* @copyright 2001-2019 WoltLab GmbH
6-
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
77
*/
88

9-
// include config
109
require_once(__DIR__ . '/app.config.inc.php');
1110

12-
// Make the frontend inaccessible until WCFSetup completes.
13-
if (!PACKAGE_ID) {
11+
// Deny access to the frontend until the WCFSetup has completed.
12+
if (defined('PACKAGE_ID') && PACKAGE_ID === 0) {
1413
\http_response_code(500);
1514

1615
exit;
1716
}
1817

19-
// initiate wcf core
2018
require_once(WCF_DIR . 'lib/system/WCF.class.php');
2119
new wcf\system\WCF();

wcfsetup/install/files/lib/data/package/Package.class.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,8 @@ private static function formatVersionForCompare(string $version): string
433433

434434
/**
435435
* Writes the config.inc.php for an application.
436-
*
437-
* @param int $packageID
438-
* @return void
439436
*/
440-
public static function writeConfigFile($packageID)
437+
public static function writeConfigFile(int $packageID): void
441438
{
442439
$package = new self($packageID);
443440
$packageDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR . $package->packageDir));
@@ -447,9 +444,9 @@ public static function writeConfigFile($packageID)
447444
$content = "<?php\n";
448445
$content .= "// {$package->package} (packageID {$packageID})\n";
449446
$content .= "if (!defined('{$prefix}_DIR')) define('{$prefix}_DIR', __DIR__.'/');\n";
450-
$content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n";
451447

452-
if ($packageID != 1) {
448+
if ($packageID !== 1) {
449+
$content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n";
453450
$content .= "\n";
454451
$content .= "// helper constants for applications\n";
455452
$content .= "if (!defined('RELATIVE_{$prefix}_DIR')) define('RELATIVE_{$prefix}_DIR', '');\n";

wcfsetup/install/files/lib/system/WCF.class.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public function __construct()
194194
// start initialization
195195
$this->initDB();
196196
$this->loadOptions();
197+
$this->resolveActiveApplication();
197198
$this->initSession();
198199
$this->initLanguage();
199200
$this->initTPL();
@@ -418,7 +419,7 @@ protected function loadOptions(): void
418419
require($filename);
419420

420421
// check if option file is complete and writable
421-
if (PACKAGE_ID) {
422+
if (!defined('PACKAGE_ID') || \PACKAGE_ID !== 0) {
422423
if (!\is_writable($filename)) {
423424
FileUtil::makeWritable($filename);
424425

@@ -519,6 +520,26 @@ protected function defineLegacyOptions(): void
519520
\define('ATTACHMENT_IMAGE_AUTOSCALE_QUALITY', 80);
520521
}
521522

523+
/**
524+
* Resolve the active application and the path when using smart URL rewriting.
525+
*
526+
* @since 6.2
527+
*/
528+
protected function resolveActiveApplication(): void
529+
{
530+
if (!isset($_GET['__rewrittenPath']) || \defined('PACKAGE_ID')) {
531+
if (!\defined('PACKAGE_ID')) {
532+
\define('PACKAGE_ID', 1);
533+
}
534+
535+
return;
536+
}
537+
538+
ApplicationHandler::getInstance()->resolveActiveApplication($_GET['__rewrittenPath']);
539+
540+
unset($_GET['__rewrittenPath']);
541+
}
542+
522543
/**
523544
* Starts the session system.
524545
*/

wcfsetup/install/files/lib/system/WCFACP.class.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public function __construct()
4747
// start initialization
4848
$this->initDB();
4949
$this->loadOptions();
50+
$this->resolveActiveApplication();
5051
$this->initSession();
5152
$this->initLanguage();
5253
$this->initTPL();

wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,19 @@
2626
final class ApplicationHandler extends SingletonFactory
2727
{
2828
/**
29-
* application cache
30-
* @var mixed[][]
29+
* @var array{
30+
* abbreviation: array<string, int>,
31+
* application: array<int, Application>,
32+
* rootApplication: ?int,
33+
* sortedPaths: array<int, string>
34+
* }
3135
*/
32-
protected $cache;
36+
private array $cache;
3337

3438
/**
35-
* list of page URLs
3639
* @var string[]
3740
*/
38-
protected array $pageURLs = [];
41+
private array $pageURLs;
3942

4043
/**
4144
* Initializes cache.
@@ -185,7 +188,7 @@ public function getAbbreviations(): array
185188
*/
186189
public function isInternalURL(string $url): bool
187190
{
188-
if (empty($this->pageURLs)) {
191+
if (!isset($this->pageURLs)) {
189192
$internalHostnames = ArrayUtil::trim(\explode("\n", StringUtil::unifyNewlines(\INTERNAL_HOSTNAMES)));
190193

191194
$this->pageURLs = \array_unique([
@@ -219,9 +222,7 @@ public function isMultiDomainSetup(): bool
219222
* @since 5.2
220223
* @deprecated 5.5 - This function is a noop. The 'active' status is determined live.
221224
*/
222-
public function rebuildActiveApplication(): void
223-
{
224-
}
225+
public function rebuildActiveApplication(): void {}
225226

226227
/**
227228
* @since 6.0
@@ -231,6 +232,52 @@ public function getDomainName(): string
231232
return $this->getApplicationByID(1)->domainName;
232233
}
233234

235+
/**
236+
* Resolve the active package id based on the rewritten URL.
237+
*
238+
* @since 6.2
239+
*/
240+
public function resolveActiveApplication(string $path): void
241+
{
242+
$rootApplication = $this->cache['rootApplication'];
243+
\assert($rootApplication !== null);
244+
245+
$path = FileUtil::removeLeadingSlash($path);
246+
$packageID = \array_find_key(
247+
$this->cache['sortedPaths'],
248+
static fn($prefix) => \str_starts_with($path, $prefix),
249+
);
250+
251+
if ($packageID === null) {
252+
\assert($this->cache['rootApplication'] !== null);
253+
$packageID = $this->cache['rootApplication'];
254+
} else {
255+
$prefix = $this->cache['sortedPaths'][$packageID];
256+
$path = \mb_substr($path, \mb_strlen($prefix));
257+
}
258+
259+
260+
RouteHandler::overridePathInfo($path);
261+
262+
if (!\defined('PACKAGE_ID')) {
263+
\define('PACKAGE_ID', $packageID);
264+
265+
if ($packageID !== 1) {
266+
$application = ApplicationHandler::getInstance()->getApplicationByID($packageID);
267+
\assert($application !== null);
268+
269+
// Include the `app.config.inc.php` of the primary app.
270+
$pathname = FileUtil::addTrailingSlash(
271+
FileUtil::getRealPath(
272+
\WCF_DIR . $application->getPackage()->packageDir
273+
)
274+
) . 'app.config.inc.php';
275+
276+
require_once $pathname;
277+
}
278+
}
279+
}
280+
234281
/**
235282
* Rebuilds cookie domain/path for all applications.
236283
*/
@@ -263,7 +310,7 @@ public static function insertRealDatabaseTableNames(string $string, bool $skipCa
263310
}
264311

265312
if ($skipCache) {
266-
$sql = "SELECT package
313+
$sql = "SELECT package
267314
FROM wcf" . WCF_N . "_package
268315
WHERE isApplication = ?";
269316
$statement = WCF::getDB()->prepareUnmanaged($sql);

wcfsetup/install/files/lib/system/cache/builder/ApplicationCacheBuilder.class.php

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,22 @@
99
/**
1010
* Caches applications.
1111
*
12-
* @author Alexander Ebert
12+
* @author Alexander Ebert
1313
* @copyright 2001-2019 WoltLab GmbH
14-
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1515
*/
16-
class ApplicationCacheBuilder extends AbstractCacheBuilder
16+
final class ApplicationCacheBuilder extends AbstractCacheBuilder
1717
{
18-
/**
19-
* @inheritDoc
20-
*/
18+
#[\Override]
2119
public function rebuild(array $parameters)
2220
{
2321
$data = [
2422
'abbreviation' => [],
2523
'application' => [],
24+
'rootApplication' => null,
25+
'sortedPaths' => [],
2626
];
2727

28-
// fetch applications
2928
$sql = "SELECT *
3029
FROM wcf" . WCF_N . "_application";
3130
$statement = WCF::getDB()->prepareUnmanaged($sql);
@@ -34,9 +33,16 @@ public function rebuild(array $parameters)
3433

3534
foreach ($applications as $application) {
3635
$data['application'][$application->packageID] = $application;
36+
$data['sortedPaths'][$application->packageID] = $application->domainPath;
37+
}
38+
39+
\uasort($data['sortedPaths'], static fn($a, $b) => \mb_strlen($b) - \mb_strlen($a));
40+
$data['rootApplication'] = $this->getRootApplication($data['sortedPaths']);
41+
42+
if ($data['rootApplication'] !== null) {
43+
$data['sortedPaths'] = $this->stripCommonPath($data['sortedPaths'], $data['rootApplication']);
3744
}
3845

39-
// fetch abbreviations
4046
$sql = "SELECT packageID, package
4147
FROM wcf" . WCF_N . "_package
4248
WHERE isApplication = ?";
@@ -49,4 +55,42 @@ public function rebuild(array $parameters)
4955

5056
return $data;
5157
}
58+
59+
/**
60+
* @param array<int, string> $sortedPaths
61+
* @return array<int, string>
62+
* @since 6.2
63+
*/
64+
private function stripCommonPath(array $sortedPaths, int $rootApplication): array
65+
{
66+
$length = \mb_strlen($sortedPaths[$rootApplication]);
67+
68+
return \array_map(
69+
static fn($path) => \mb_substr($path, $length),
70+
$sortedPaths
71+
);
72+
}
73+
74+
/**
75+
* @param array<int, string> $sortedPaths
76+
* @since 6.2
77+
*/
78+
private function getRootApplication(array $sortedPaths): ?int
79+
{
80+
// There are no applications during the setup.
81+
if ($sortedPaths === []) {
82+
return null;
83+
}
84+
85+
$candidate = \array_key_last($sortedPaths);
86+
$shortestPath = $sortedPaths[$candidate];
87+
88+
foreach ($sortedPaths as $path) {
89+
if (!\str_starts_with($path, $shortestPath)) {
90+
return null;
91+
}
92+
}
93+
94+
return $candidate;
95+
}
5296
}

wcfsetup/install/files/lib/system/request/RequestHandler.class.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ protected function init()
8989
*/
9090
public function handle(string $application = 'wcf', bool $isACPRequest = false): void
9191
{
92+
// Override the application when using smart URL rewriting.
93+
if ($application === 'wcf' && \PACKAGE_ID > 1) {
94+
$app = ApplicationHandler::getInstance()->getApplicationByID(\PACKAGE_ID);
95+
\assert($app !== null);
96+
$application = $app->getAbbreviation();
97+
}
98+
9299
try {
93100
$this->isACPRequest = $isACPRequest;
94101

wcfsetup/install/files/lib/system/request/RouteHandler.class.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,14 @@ public static function getPathInfo(): string
384384

385385
return self::$pathInfo;
386386
}
387+
388+
/**
389+
* Overrides the path info as part of the smart URL rewriting feature.
390+
*
391+
* @since 6.2
392+
*/
393+
public static function overridePathInfo(string $pathInfo): void
394+
{
395+
self::$pathInfo = $pathInfo;
396+
}
387397
}

0 commit comments

Comments
 (0)