Skip to content

Commit bf79774

Browse files
committed
Prototype for smart URL rewriting
1 parent d549404 commit bf79774

File tree

8 files changed

+163
-18
lines changed

8 files changed

+163
-18
lines changed

wcfsetup/install/files/global.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
require_once(__DIR__ . '/app.config.inc.php');
1111

1212
// Make the frontend inaccessible until WCFSetup completes.
13+
/*
14+
15+
TODO: This is currently not possible, find a solution!
16+
1317
if (!PACKAGE_ID) {
1418
\http_response_code(500);
1519
1620
exit;
1721
}
22+
*/
1823

1924
// initiate wcf core
2025
require_once(WCF_DIR . 'lib/system/WCF.class.php');

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,9 @@ public static function writeConfigFile($packageID)
447447
$content = "<?php\n";
448448
$content .= "// {$package->package} (packageID {$packageID})\n";
449449
$content .= "if (!defined('{$prefix}_DIR')) define('{$prefix}_DIR', __DIR__.'/');\n";
450-
$content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n";
451450

452451
if ($packageID != 1) {
452+
$content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n";
453453
$content .= "\n";
454454
$content .= "// helper constants for applications\n";
455455
$content .= "if (!defined('RELATIVE_{$prefix}_DIR')) define('RELATIVE_{$prefix}_DIR', '');\n";

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

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use wcf\system\registry\RegistryHandler;
2525
use wcf\system\request\Request;
2626
use wcf\system\request\RequestHandler;
27+
use wcf\system\request\RouteHandler;
2728
use wcf\system\session\SessionFactory;
2829
use wcf\system\session\SessionHandler;
2930
use wcf\system\style\StyleHandler;
@@ -194,6 +195,7 @@ public function __construct()
194195
// start initialization
195196
$this->initDB();
196197
$this->loadOptions();
198+
$this->resolveActiveApplication();
197199
$this->initSession();
198200
$this->initLanguage();
199201
$this->initTPL();
@@ -418,7 +420,7 @@ protected function loadOptions(): void
418420
require($filename);
419421

420422
// check if option file is complete and writable
421-
if (PACKAGE_ID) {
423+
if (!\defined('\\PACKAGE_ID')) {
422424
if (!\is_writable($filename)) {
423425
FileUtil::makeWritable($filename);
424426

@@ -450,6 +452,66 @@ protected function loadOptions(): void
450452
}
451453
}
452454

455+
protected function resolveActiveApplication(): void
456+
{
457+
if (\defined('PACKAGE_ID')) {
458+
return;
459+
}
460+
461+
$applications = ApplicationHandler::getInstance()->getApplications();
462+
if (!\URL_OMIT_INDEX_PHP || \count($applications) === 1) {
463+
\define('PACKAGE_ID', 1);
464+
return;
465+
}
466+
467+
// We do not support smart rewrites for setups where apps are installed
468+
// in different directory where the only shared ancestor is not an app.
469+
$rootApp = ApplicationHandler::getInstance()->getRootApplication();
470+
if ($rootApp === null) {
471+
\define('PACKAGE_ID', 1);
472+
return;
473+
}
474+
475+
$sortedPaths = ApplicationHandler::getInstance()->getSortedPaths();
476+
477+
// When the core is the root app we can simply check the path info for
478+
// any apps appearing at the start of it.
479+
$coreIsAtRoot = ($rootApp === ApplicationHandler::getInstance()->getWCF());
480+
481+
/** @var ?int */
482+
$candidate = null;
483+
$pathInfo = RouteHandler::getPathInfo();
484+
if ($coreIsAtRoot) {
485+
foreach ($sortedPaths as $packageID => $pathname) {
486+
if (\str_starts_with($pathInfo, \mb_substr($pathname, \mb_strlen($rootApp->domainPath)))) {
487+
$candidate = $packageID;
488+
break;
489+
}
490+
}
491+
492+
\assert($candidate !== null);
493+
494+
$app = ApplicationHandler::getInstance()->getApplicationByID($candidate);
495+
$prefix = \mb_substr($app->domainPath, \mb_strlen($rootApp->domainPath));
496+
RouteHandler::ltrimPathInfo($prefix);
497+
} else {
498+
\wcfDebug(RouteHandler::getPath(), $pathInfo);
499+
}
500+
501+
if ($candidate === null) {
502+
\define('PACKAGE_ID', 1);
503+
} else {
504+
$application = ApplicationHandler::getInstance()->getApplicationByID($candidate);
505+
\assert($application !== null);
506+
507+
\define('PACKAGE_ID', $candidate);
508+
509+
// Include the `app.config.inc.php` of the primary app.
510+
$pathname = FileUtil::addTrailingSlash(FileUtil::getRealPath(\WCF_DIR . $application->getPackage()->packageDir)) . 'app.config.inc.php';
511+
require_once $pathname;
512+
}
513+
}
514+
453515
/**
454516
* Defines constants for obsolete options, which were removed.
455517
*

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: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@
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+
* sortedPaths: array<int, string>,
33+
* rootApplication: ?int,
34+
* }
3135
*/
32-
protected $cache;
36+
private array $cache;
3337

3438
/**
3539
* list of page URLs
3640
* @var string[]
3741
*/
38-
protected array $pageURLs = [];
42+
private array $pageURLs;
3943

4044
/**
4145
* Initializes cache.
@@ -185,7 +189,7 @@ public function getAbbreviations(): array
185189
*/
186190
public function isInternalURL(string $url): bool
187191
{
188-
if (empty($this->pageURLs)) {
192+
if (!isset($this->pageURLs)) {
189193
$internalHostnames = ArrayUtil::trim(\explode("\n", StringUtil::unifyNewlines(\INTERNAL_HOSTNAMES)));
190194

191195
$this->pageURLs = \array_unique([
@@ -219,9 +223,7 @@ public function isMultiDomainSetup(): bool
219223
* @since 5.2
220224
* @deprecated 5.5 - This function is a noop. The 'active' status is determined live.
221225
*/
222-
public function rebuildActiveApplication(): void
223-
{
224-
}
226+
public function rebuildActiveApplication(): void {}
225227

226228
/**
227229
* @since 6.0
@@ -231,6 +233,37 @@ public function getDomainName(): string
231233
return $this->getApplicationByID(1)->domainName;
232234
}
233235

236+
/**
237+
* Returns a list of the domain paths of all apps sorted by their length
238+
* with the longest value appearing first. The key of each path is the
239+
* package id of the corresponding app.
240+
*
241+
* @return array<int, string>
242+
* @since 6.2
243+
*/
244+
public function getSortedPaths(): array
245+
{
246+
return $this->cache['sortedPaths'];
247+
}
248+
249+
/**
250+
* Returns the app that is the root of all other apps. This is the case when
251+
* all other apps installed in a direct or indirect subdirectory.
252+
*
253+
* @since 6.2
254+
*/
255+
public function getRootApplication(): ?Application
256+
{
257+
if ($this->cache['rootApplication'] === null) {
258+
return null;
259+
}
260+
261+
$rootApp = $this->getApplicationByID($this->cache['rootApplication']);
262+
\assert($rootApp !== null);
263+
264+
return $rootApp;
265+
}
266+
234267
/**
235268
* Rebuilds cookie domain/path for all applications.
236269
*/
@@ -263,7 +296,7 @@ public static function insertRealDatabaseTableNames(string $string, bool $skipCa
263296
}
264297

265298
if ($skipCache) {
266-
$sql = "SELECT package
299+
$sql = "SELECT package
267300
FROM wcf" . WCF_N . "_package
268301
WHERE isApplication = ?";
269302
$statement = WCF::getDB()->prepareUnmanaged($sql);

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@
99
/**
1010
* Caches applications.
1111
*
12-
* @author Alexander Ebert
13-
* @copyright 2001-2019 WoltLab GmbH
14-
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12+
* @author Alexander Ebert
13+
* @copyright 2001-2025 WoltLab GmbH
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+
'sortedPaths' => [],
25+
'rootApplication' => null,
2626
];
2727

2828
// fetch applications
@@ -34,8 +34,12 @@ public function rebuild(array $parameters)
3434

3535
foreach ($applications as $application) {
3636
$data['application'][$application->packageID] = $application;
37+
$data['sortedPaths'][$application->packageID] = $application->domainPath;
3738
}
3839

40+
\uasort($data['sortedPaths'], static fn($a, $b) => \mb_strlen($b) - \mb_strlen($a));
41+
$data['rootApplication'] = $this->getRootApplication($data['sortedPaths']);
42+
3943
// fetch abbreviations
4044
$sql = "SELECT packageID, package
4145
FROM wcf" . WCF_N . "_package
@@ -49,4 +53,22 @@ public function rebuild(array $parameters)
4953

5054
return $data;
5155
}
56+
57+
/**
58+
* @param array<int, string> $sortedPaths
59+
* @since 6.2
60+
*/
61+
private function getRootApplication(array $sortedPaths): ?int
62+
{
63+
$candidate = \array_key_last($sortedPaths);
64+
$shortestPath = $sortedPaths[$candidate];
65+
66+
foreach ($sortedPaths as $path) {
67+
if (!\str_starts_with($path, $shortestPath)) {
68+
return null;
69+
}
70+
}
71+
72+
return $candidate;
73+
}
5274
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ protected function init()
8989
*/
9090
public function handle(string $application = 'wcf', bool $isACPRequest = false): void
9191
{
92+
// Override the application when this request was the result of a smart
93+
// rewrite.
94+
if ($application === 'wcf' && \PACKAGE_ID > 1) {
95+
$app = ApplicationHandler::getInstance()->getApplicationByID(\PACKAGE_ID);
96+
\assert($app !== null);
97+
98+
$application = $app->getAbbreviation();
99+
}
100+
92101
try {
93102
$this->isACPRequest = $isACPRequest;
94103

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

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

385385
return self::$pathInfo;
386386
}
387+
388+
/**
389+
* TODO: This is merely a helper to get this working for the time being.
390+
*
391+
* @since 6.2
392+
*/
393+
public static function ltrimPathInfo(string $prefix): void
394+
{
395+
\assert(isset(self::$pathInfo));
396+
\assert(\str_starts_with(self::$pathInfo, $prefix));
397+
398+
self::$pathInfo = \mb_substr(self::$pathInfo, \mb_strlen($prefix));
399+
}
387400
}

0 commit comments

Comments
 (0)