diff --git a/wcfsetup/install/files/global.php b/wcfsetup/install/files/global.php index 4dcb1a5ef9e..c654366fcbf 100644 --- a/wcfsetup/install/files/global.php +++ b/wcfsetup/install/files/global.php @@ -1,21 +1,19 @@ + * @license GNU Lesser General Public License */ -// include config require_once(__DIR__ . '/app.config.inc.php'); -// Make the frontend inaccessible until WCFSetup completes. -if (!PACKAGE_ID) { +// Deny access to the frontend until the WCFSetup has completed. +if (defined('PACKAGE_ID') && PACKAGE_ID === 0) { \http_response_code(500); exit; } -// initiate wcf core require_once(WCF_DIR . 'lib/system/WCF.class.php'); new wcf\system\WCF(); diff --git a/wcfsetup/install/files/lib/data/package/Package.class.php b/wcfsetup/install/files/lib/data/package/Package.class.php index 7b41028ccde..0cf2f71b1be 100644 --- a/wcfsetup/install/files/lib/data/package/Package.class.php +++ b/wcfsetup/install/files/lib/data/package/Package.class.php @@ -433,11 +433,8 @@ private static function formatVersionForCompare(string $version): string /** * Writes the config.inc.php for an application. - * - * @param int $packageID - * @return void */ - public static function writeConfigFile($packageID) + public static function writeConfigFile(int $packageID): void { $package = new self($packageID); $packageDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR . $package->packageDir)); @@ -447,9 +444,9 @@ public static function writeConfigFile($packageID) $content = "package} (packageID {$packageID})\n"; $content .= "if (!defined('{$prefix}_DIR')) define('{$prefix}_DIR', __DIR__.'/');\n"; - $content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n"; - if ($packageID != 1) { + if ($packageID !== 1) { + $content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n"; $content .= "\n"; $content .= "// helper constants for applications\n"; $content .= "if (!defined('RELATIVE_{$prefix}_DIR')) define('RELATIVE_{$prefix}_DIR', '');\n"; diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php index 801051eadc7..475b49d9836 100644 --- a/wcfsetup/install/files/lib/system/WCF.class.php +++ b/wcfsetup/install/files/lib/system/WCF.class.php @@ -194,6 +194,7 @@ public function __construct() // start initialization $this->initDB(); $this->loadOptions(); + $this->resolveActiveApplication(); $this->initSession(); $this->initLanguage(); $this->initTPL(); @@ -418,7 +419,7 @@ protected function loadOptions(): void require($filename); // check if option file is complete and writable - if (PACKAGE_ID) { + if (!defined('PACKAGE_ID') || \PACKAGE_ID !== 0) { if (!\is_writable($filename)) { FileUtil::makeWritable($filename); @@ -519,6 +520,26 @@ protected function defineLegacyOptions(): void \define('ATTACHMENT_IMAGE_AUTOSCALE_QUALITY', 80); } + /** + * Resolve the active application and the path when using smart URL rewriting. + * + * @since 6.2 + */ + protected function resolveActiveApplication(): void + { + if (!isset($_GET['__rewrittenPath']) || \defined('PACKAGE_ID')) { + if (!\defined('PACKAGE_ID')) { + \define('PACKAGE_ID', 1); + } + + return; + } + + ApplicationHandler::getInstance()->resolveActiveApplication($_GET['__rewrittenPath']); + + unset($_GET['__rewrittenPath']); + } + /** * Starts the session system. */ diff --git a/wcfsetup/install/files/lib/system/WCFACP.class.php b/wcfsetup/install/files/lib/system/WCFACP.class.php index acd7e9aaea1..a77ff33f294 100644 --- a/wcfsetup/install/files/lib/system/WCFACP.class.php +++ b/wcfsetup/install/files/lib/system/WCFACP.class.php @@ -47,6 +47,7 @@ public function __construct() // start initialization $this->initDB(); $this->loadOptions(); + $this->resolveActiveApplication(); $this->initSession(); $this->initLanguage(); $this->initTPL(); diff --git a/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php b/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php index 8a599987bde..5e341413cb8 100644 --- a/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php +++ b/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php @@ -26,16 +26,19 @@ final class ApplicationHandler extends SingletonFactory { /** - * application cache - * @var mixed[][] + * @var array{ + * abbreviation: array, + * application: array, + * rootApplication: ?int, + * sortedPaths: array + * } */ - protected $cache; + private array $cache; /** - * list of page URLs * @var string[] */ - protected array $pageURLs = []; + private array $pageURLs; /** * Initializes cache. @@ -185,7 +188,7 @@ public function getAbbreviations(): array */ public function isInternalURL(string $url): bool { - if (empty($this->pageURLs)) { + if (!isset($this->pageURLs)) { $internalHostnames = ArrayUtil::trim(\explode("\n", StringUtil::unifyNewlines(\INTERNAL_HOSTNAMES))); $this->pageURLs = \array_unique([ @@ -219,9 +222,7 @@ public function isMultiDomainSetup(): bool * @since 5.2 * @deprecated 5.5 - This function is a noop. The 'active' status is determined live. */ - public function rebuildActiveApplication(): void - { - } + public function rebuildActiveApplication(): void {} /** * @since 6.0 @@ -231,6 +232,52 @@ public function getDomainName(): string return $this->getApplicationByID(1)->domainName; } + /** + * Resolve the active package id based on the rewritten URL. + * + * @since 6.2 + */ + public function resolveActiveApplication(string $path): void + { + $rootApplication = $this->cache['rootApplication']; + \assert($rootApplication !== null); + + $path = FileUtil::removeLeadingSlash($path); + $packageID = \array_find_key( + $this->cache['sortedPaths'], + static fn($prefix) => \str_starts_with($path, $prefix), + ); + + if ($packageID === null) { + \assert($this->cache['rootApplication'] !== null); + $packageID = $this->cache['rootApplication']; + } else { + $prefix = $this->cache['sortedPaths'][$packageID]; + $path = \mb_substr($path, \mb_strlen($prefix)); + } + + + RouteHandler::overridePathInfo($path); + + if (!\defined('PACKAGE_ID')) { + \define('PACKAGE_ID', $packageID); + + if ($packageID !== 1) { + $application = ApplicationHandler::getInstance()->getApplicationByID($packageID); + \assert($application !== null); + + // Include the `app.config.inc.php` of the primary app. + $pathname = FileUtil::addTrailingSlash( + FileUtil::getRealPath( + \WCF_DIR . $application->getPackage()->packageDir + ) + ) . 'app.config.inc.php'; + + require_once $pathname; + } + } + } + /** * Rebuilds cookie domain/path for all applications. */ @@ -263,7 +310,7 @@ public static function insertRealDatabaseTableNames(string $string, bool $skipCa } if ($skipCache) { - $sql = "SELECT package + $sql = "SELECT package FROM wcf" . WCF_N . "_package WHERE isApplication = ?"; $statement = WCF::getDB()->prepareUnmanaged($sql); diff --git a/wcfsetup/install/files/lib/system/cache/builder/ApplicationCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/ApplicationCacheBuilder.class.php index 1333b423db0..eb08a982fa6 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/ApplicationCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/ApplicationCacheBuilder.class.php @@ -9,23 +9,22 @@ /** * Caches applications. * - * @author Alexander Ebert + * @author Alexander Ebert * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @license GNU Lesser General Public License */ -class ApplicationCacheBuilder extends AbstractCacheBuilder +final class ApplicationCacheBuilder extends AbstractCacheBuilder { - /** - * @inheritDoc - */ + #[\Override] public function rebuild(array $parameters) { $data = [ 'abbreviation' => [], 'application' => [], + 'rootApplication' => null, + 'sortedPaths' => [], ]; - // fetch applications $sql = "SELECT * FROM wcf" . WCF_N . "_application"; $statement = WCF::getDB()->prepareUnmanaged($sql); @@ -34,9 +33,16 @@ public function rebuild(array $parameters) foreach ($applications as $application) { $data['application'][$application->packageID] = $application; + $data['sortedPaths'][$application->packageID] = $application->domainPath; + } + + \uasort($data['sortedPaths'], static fn($a, $b) => \mb_strlen($b) - \mb_strlen($a)); + $data['rootApplication'] = $this->getRootApplication($data['sortedPaths']); + + if ($data['rootApplication'] !== null) { + $data['sortedPaths'] = $this->stripCommonPath($data['sortedPaths'], $data['rootApplication']); } - // fetch abbreviations $sql = "SELECT packageID, package FROM wcf" . WCF_N . "_package WHERE isApplication = ?"; @@ -49,4 +55,42 @@ public function rebuild(array $parameters) return $data; } + + /** + * @param array $sortedPaths + * @return array + * @since 6.2 + */ + private function stripCommonPath(array $sortedPaths, int $rootApplication): array + { + $length = \mb_strlen($sortedPaths[$rootApplication]); + + return \array_map( + static fn($path) => \mb_substr($path, $length), + $sortedPaths + ); + } + + /** + * @param array $sortedPaths + * @since 6.2 + */ + private function getRootApplication(array $sortedPaths): ?int + { + // There are no applications during the setup. + if ($sortedPaths === []) { + return null; + } + + $candidate = \array_key_last($sortedPaths); + $shortestPath = $sortedPaths[$candidate]; + + foreach ($sortedPaths as $path) { + if (!\str_starts_with($path, $shortestPath)) { + return null; + } + } + + return $candidate; + } } diff --git a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php index 49fe71265f0..63d0a40e22c 100644 --- a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php @@ -89,6 +89,13 @@ protected function init() */ public function handle(string $application = 'wcf', bool $isACPRequest = false): void { + // Override the application when using smart URL rewriting. + if ($application === 'wcf' && \PACKAGE_ID > 1) { + $app = ApplicationHandler::getInstance()->getApplicationByID(\PACKAGE_ID); + \assert($app !== null); + $application = $app->getAbbreviation(); + } + try { $this->isACPRequest = $isACPRequest; diff --git a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php index 26daced5e1b..f2203c9f301 100644 --- a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php @@ -384,4 +384,14 @@ public static function getPathInfo(): string return self::$pathInfo; } + + /** + * Overrides the path info as part of the smart URL rewriting feature. + * + * @since 6.2 + */ + public static function overridePathInfo(string $pathInfo): void + { + self::$pathInfo = $pathInfo; + } }