diff --git a/docs/modules/WPLoader.md b/docs/modules/WPLoader.md index 545bbc9a2..02304e46a 100644 --- a/docs/modules/WPLoader.md +++ b/docs/modules/WPLoader.md @@ -1,5 +1,7 @@ ## WPLoader module +// @todo update this + A module to load WordPress and make its code available in tests. Depending on the value of the `loadOnly` configuration parameter, the module will behave differently: diff --git a/src/Module/WPLoader.php b/src/Module/WPLoader.php index ed7b087ff..9275a8a8d 100644 --- a/src/Module/WPLoader.php +++ b/src/Module/WPLoader.php @@ -195,6 +195,32 @@ public function _didLoadWordPress(): bool return $this->didLoadWordPress; } + /** + * Get the absolute path to the mu-plugins directory. + * + * The value will first look at the `WPMU_PLUGIN_DIR` constant, then the `WP_CONTENT_DIR` configuration parameter, + * and will, finally, look in the default path from the WordPress root directory. + * + * @param string $path + * + * @return string + * @since TBD + */ + public function getMuPluginsFolder(string $path = ''): string + { + /** @var array{WPMU_PLUGIN_DIR?: string, WP_CONTENT_DIR?: string} $config */ + $config = $this->config; + $candidates = array_filter([ + $config['WPMU_PLUGIN_DIR'] ?? null, + isset($config['WP_CONTENT_DIR']) ? rtrim($config['WP_CONTENT_DIR'], '\\/') . '/mu-plugins' : null, + $this->installation->getMuPluginsDir() + ]); + /** @var string $muPluginsDir */ + $muPluginsDir = reset($candidates); + + return rtrim($muPluginsDir, '\\/') . ($path ? ltrim($path, '\\/') : '/'); + } + protected function validateConfig(): void { // Coming from required fields, the values are now defined. @@ -440,10 +466,10 @@ public function _initialize(): void $this->installation = new Installation($wpRootDir); } - if ($db instanceof SqliteDatabase && !is_file($this->installation->getContentDir('db.php'))) { + if ($db instanceof SqliteDatabase && !is_file($this->getContentFolder('db.php'))) { Installation::placeSqliteMuPlugin( - $this->installation->getMuPluginsDir(), - $this->installation->getContentDir() + $this->getMuPluginsFolder(), + $this->getContentFolder() ); } @@ -610,8 +636,9 @@ public function _loadWordPress(?bool $loadOnly = null): void /** * Returns the absolute path to the plugins directory. * - * The value will first look at the `WP_PLUGIN_DIR` constant, then the `pluginsFolder` configuration parameter - * and will, finally, look in the default path from the WordPress root directory. + * The value will first look at the `WP_PLUGIN_DIR` constant, then the `pluginsFolder` configuration parameter, + * then the `WP_CONTENT_DIR` configuration parameter, and will, finally, look in the default path from the + * WordPress root directory. * * @example * ```php @@ -626,7 +653,18 @@ public function _loadWordPress(?bool $loadOnly = null): void */ public function getPluginsFolder(string $path = ''): string { - return $this->installation->getPluginsDir($path); + /** @var array{pluginsFolder?: string, WP_PLUGIN_DIR?: string,WP_CONTENT_DIR?: string} $config */ + $config = $this->config; + $candidates = array_filter([ + $config['WP_PLUGIN_DIR'] ?? null, + $config['pluginsFolder'] ?? null, + isset($config['WP_CONTENT_DIR']) ? rtrim($config['WP_CONTENT_DIR'], '\\/') . '/plugins' : null, + $this->installation->getPluginsDir() + ]); + /** @var string $pluginDir */ + $pluginDir = reset($candidates); + + return rtrim($pluginDir, '\\/') . ($path ? ltrim($path, '\\/') : '/'); } /** @@ -856,6 +894,9 @@ private function loadConfigFiles(): void /** * Returns the absolute path to the WordPress content directory. * + * The value will first look at the `WP_CONTENT_DIR` configuration parameter, and will, finally, look in the + * default path from the WordPress root directory. + * * @example * ```php * $content = $this->getContentFolder(); @@ -869,7 +910,16 @@ private function loadConfigFiles(): void */ public function getContentFolder(string $path = ''): string { - return $this->installation->getContentDir($path); + /** @var array{WP_CONTENT_DIR?: string} $config */ + $config = $this->config; + $candidates = array_filter([ + $config['WP_CONTENT_DIR'] ?? null, + $this->installation->getContentDir() + ]); + /** @var string $contentDir */ + $contentDir = reset($candidates); + + return rtrim($contentDir, '\\/') . ($path ? ltrim($path, '\\/') : '/'); } private function getCodeExecutionFactory(): CodeExecutionFactory @@ -1168,7 +1218,7 @@ private function includeAllPlugins(array $plugins, bool $isMultisite): void $activePlugins = []; } - $pluginsDir = $this->installation->getPluginsDir(); + $pluginsDir = $this->getPluginsFolder(); foreach ($plugins as $plugin) { if (!is_file($pluginsDir . "/$plugin")) { diff --git a/tests/_support/Traits/InstallationMocks.php b/tests/_support/Traits/InstallationMocks.php new file mode 100644 index 000000000..bff868f5a --- /dev/null +++ b/tests/_support/Traits/InstallationMocks.php @@ -0,0 +1,99 @@ + [ + 'version.php' => <<< PHP + <<< PHP + ' ' [ + 'version.php' => <<< PHP + ' ' [ - 'version.php' => <<< PHP - <<< PHP - ' 'makeMockWordPressInstallation(); + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation(); $moduleContainer = new ModuleContainer(new Di(), []); $module = new WPLoader($moduleContainer, [ 'dbUrl' => $dbUrl, @@ -100,7 +48,7 @@ public function testWillLoadWordPressInBeforeSuiteWhenLoadOnlyIsTrue(): void public function testWillLoadWordPressInInitializeWhenLoadOnlyIsFalse(): void { - [$wpRootFolder, $dbUrl] = $this->makeMockWordPressInstallation(); + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation(); $moduleContainer = new ModuleContainer(new Di(), []); $module = new WPLoader($moduleContainer, [ 'dbUrl' => $dbUrl, diff --git a/tests/unit/lucatume/WPBrowser/Module/WPLoaderScaffoldedInstallationCustomLocationsTest.php b/tests/unit/lucatume/WPBrowser/Module/WPLoaderScaffoldedInstallationCustomLocationsTest.php new file mode 100644 index 000000000..ca31958b7 --- /dev/null +++ b/tests/unit/lucatume/WPBrowser/Module/WPLoaderScaffoldedInstallationCustomLocationsTest.php @@ -0,0 +1,161 @@ +makeMockConfiguredInstallation(); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false + ]); + + Fork::executeClosure(function () use ($wpRootFolder, $module) { + // Partial mocking the function that would load WordPress. + uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () { + return true; + }, true); + + $module->_initialize(); + + $this->assertInstanceOf(Configured::class, $module->getInstallation()->getState()); + $this->assertEquals($wpRootFolder . '/', $module->getWpRootFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/', $module->getContentFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/plugins/', $module->getPluginsFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomContentDirFromConfigInConfiguredInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation(); + $contentDir = FS::tmpDir('custom-content-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'WP_CONTENT_DIR' => $contentDir + ]); + + Fork::executeClosure(function() use ($wpRootFolder, $contentDir, $module) { + // Partial mocking the function that would load WordPress. + uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () { + return true; + }, true); + + $module->_initialize(); + + $this->assertInstanceOf(Configured::class, $module->getInstallation()->getState()); + $this->assertEquals($wpRootFolder . '/', $module->getWpRootFolder()); + $this->assertEquals($contentDir . '/', $module->getContentFolder()); + $this->assertEquals($contentDir . '/plugins/', $module->getPluginsFolder()); + $this->assertEquals($contentDir . '/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomPluginsFolderFromConfigInConfiguredInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation(); + $pluginsDir = FS::tmpDir('custom-plugins-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'pluginsFolder' => $pluginsDir + ]); + + Fork::executeClosure(function () use ($wpRootFolder, $pluginsDir, $module) { + // Partial mocking the function that would load WordPress. + uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () { + return true; + }, true); + + $module->_initialize(); + + $this->assertInstanceOf(Configured::class, $module->getInstallation()->getState()); + $this->assertEquals($wpRootFolder . '/', $module->getWpRootFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/', $module->getContentFolder()); + $this->assertEquals($pluginsDir . '/', $module->getPluginsFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomPluginsDirFromConfigInConfiguredInstallation(): void{ + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation(); + $pluginsDir = FS::tmpDir('custom-plugins-dir'); + $pluginsDir2 = FS::tmpDir('custom-plugins-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'WP_PLUGIN_DIR' => $pluginsDir, + 'pluginsFolder' => $pluginsDir2 + ]); + + Fork::executeClosure(function () use ($wpRootFolder, $pluginsDir, $module) { + // Partial mocking the function that would load WordPress. + uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () { + return true; + }, true); + + $module->_initialize(); + + $this->assertInstanceOf(Configured::class, $module->getInstallation()->getState()); + $this->assertEquals($wpRootFolder . '/', $module->getWpRootFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/', $module->getContentFolder()); + $this->assertEquals($pluginsDir . '/', $module->getPluginsFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + // @todo mu-plugins dir in configured + + public function testUsesDefaultContentLocationInScaffoldedInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockScaffoldedInstallation(); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false + ]); + + Fork::executeClosure(function () use ($wpRootFolder, $module) { + // Partial mocking the function that would load WordPress. + uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () { + return true; + }, true); + + $module->_initialize(); + + $this->assertInstanceOf(Scaffolded::class, $module->getInstallation()->getState()); + $this->assertEquals($wpRootFolder . '/', $module->getWpRootFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/', $module->getContentFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/plugins/', $module->getPluginsFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + // @todo custom content in scaffolded + // @todo custom plugins dir in scaffolded + // @todo custom mu-plugins dir in scaffolded +}