diff --git a/docs/modules/WPLoader.md b/docs/modules/WPLoader.md index 99309a6c8..021ceb56b 100644 --- a/docs/modules/WPLoader.md +++ b/docs/modules/WPLoader.md @@ -56,8 +56,9 @@ When used in this mode, the module supports the following configuration paramete during normal activation and are known to work correctly when activated silently. Plugin paths can be specified following the same format of the `plugins` parameter. * `bootstrapActions` - a list of actions or callables to call **after** WordPress is loaded and before the tests run. -* `theme` - the theme to activate and load in the WordPress installation. The theme must be specified in slug format - like `twentytwentythree`. +* `theme` - the theme to activate and load in the WordPress installation. The theme can be specified in slug format, + e.g., `twentytwentythree`, to load it from the WordPress installation themes directory. Alternatively, the theme can + be specified as an absolute or relative path to a theme folder, e.g., `/home/themes/my-theme` or `vendor/acme/vendor-theme`. To use both a parent and ha child theme from arbitrary absolute or relative paths, define the `theme` parameter as an array of theme paths, e.g., `['/home/themes/parent-theme', '.']`. * `AUTH_KEY` - the `AUTH_KEY` constant value to use when loading WordPress. If the `wpRootFolder` path points at a configured installation, containing the `wp-config.php` file, then the value of the constant in the configuration file will be used, else it will be randomly generated. @@ -128,11 +129,15 @@ modules: adminEmail: admin@wordpress.test title: 'Integration Tests' plugins: - - hello.php # This plugin will be loaded from the WordPress installation plugins directory. - - /home/plugins/woocommerce/woocommerce.php # This plugin will be loaded from an arbitrary absolute path. - - vendor/acme/project/plugin.php # This plugin will be loaded from an arbitrary relative path inside the project root folder. - - my-plugin.php # This plugin will be loaded from the project root folder. - theme: twentytwentythree + # This plugin will be loaded from the WordPress installation plugins directory. + - hello.php + # This plugin will be loaded from an arbitrary absolute path. + - /home/plugins/woocommerce/woocommerce.php + # This plugin will be loaded from an arbitrary relative path inside the project root folder. + - vendor/acme/project/plugin.php + # This plugin will be loaded from the project root folder. + - my-plugin.php + theme: twentytwentythree # Load the theme from the WordPress installation themes directory. ``` The following configuration uses [dynamic configuration parameters][3] to set the module configuration: @@ -151,11 +156,12 @@ modules: adminEmail: '%WP_ADMIN_EMAIL%' title: '%WP_TITLE%' plugins: - - hello.php # This plugin will be loaded from the WordPress installation plugins directory. - - /home/plugins/woocommerce/woocommerce.php # This plugin will be loaded from an arbitrary absolute path. - - my-plugin.php # This plugin will be loaded from the project root folder. - - vendor/acme/project/plugin.php # This plugin will be loaded from an arbitrary relative path inside the project root folder. - theme: twentytwentythree + - hello.php + - /home/plugins/woocommerce/woocommerce.php + - my-plugin.php + - vendor/acme/project/plugin.php + # Parent theme from the WordPress installation themes directory, child theme from absolute path. + theme: [twentytwentythree, /home/themes/my-theme] ``` The following example configuration uses a SQLite database and loads a database fixture before the tests run: @@ -181,7 +187,11 @@ modules: - hello.php - woocommerce/woocommerce.php - my-plugin/my-plugin.php - theme: twentytwentythree + theme: + # Parent theme from relative path. + - vendor/acme/parent-theme + # Child theme from the current working directory. + - . ``` The follow example configuration prevents the backup of globals and static attributes in all the tests of the suite that diff --git a/includes/core-phpunit/wp-tests-config.php b/includes/core-phpunit/wp-tests-config.php index 0e64ab9b4..e944a1143 100644 --- a/includes/core-phpunit/wp-tests-config.php +++ b/includes/core-phpunit/wp-tests-config.php @@ -54,10 +54,12 @@ } $abspath = rtrim($wpLoaderConfig['wpRootFolder'], '\\/') . '/'; +$themes = (array)$wpLoaderConfig['theme']; +$stylesheet = end($themes); foreach ([ 'ABSPATH' => $abspath, - 'WP_DEFAULT_THEME' => $wpLoaderConfig['theme'], + 'WP_DEFAULT_THEME' => $stylesheet, 'WP_TESTS_MULTISITE' => $wpLoaderConfig['multisite'], 'WP_DEBUG' => true, 'DB_NAME' => $wpLoaderConfig['dbName'], @@ -91,7 +93,7 @@ define($const, $value); } } -unset($const); +unset($const, $themes, $stylesheet); $table_prefix = $wpLoaderConfig['tablePrefix']; diff --git a/src/Module/WPLoader.php b/src/Module/WPLoader.php index 650b8d0d4..66c16f3be 100644 --- a/src/Module/WPLoader.php +++ b/src/Module/WPLoader.php @@ -23,6 +23,7 @@ use lucatume\WPBrowser\Utils\CorePHPUnit; use lucatume\WPBrowser\Utils\Db as DbUtils; use lucatume\WPBrowser\Utils\Filesystem as FS; +use lucatume\WPBrowser\Utils\Property; use lucatume\WPBrowser\Utils\Random; use lucatume\WPBrowser\WordPress\CodeExecution\CodeExecutionFactory; use lucatume\WPBrowser\WordPress\Database\DatabaseInterface; @@ -105,7 +106,7 @@ class WPLoader extends Module * plugins: string[], * silentlyActivatePlugins: string[], * bootstrapActions: string|string[], - * theme: string, + * theme: string|string[], * AUTH_KEY: string, * SECURE_AUTH_KEY: string, * LOGGED_IN_KEY: string, @@ -228,11 +229,13 @@ protected function validateConfig(): void $this->config['theme'] = $this->config['WP_TESTS_MULTISITE'] ?? $this->config['theme'] ?? ''; - if (!is_string($this->config['theme'])) { + if (!( + is_string($this->config['theme']) + || (is_array($this->config['theme']) && Arr::hasShape($this->config['theme'], ['string', 'string']))) + ) { throw new ModuleConfigException( __CLASS__, - "The `theme` configuration parameter must be a string.\n" . - "For child themes, use the child theme slug." + "The `theme` configuration parameter must be either a string, or an array of two strings." ); } @@ -349,7 +352,7 @@ public function _initialize(): void * plugins: string[], * silentlyActivatePlugins: string[], * bootstrapActions: string|string[], - * theme: string, + * theme: string|string[], * AUTH_KEY: string, * SECURE_AUTH_KEY: string, * LOGGED_IN_KEY: string, @@ -589,7 +592,7 @@ public function getPluginsFolder(string $path = ''): string } /** - * Returns the absolute path to the themes directory. + * Returns the absolute path to the themes' directory. * * @example * ```php @@ -656,6 +659,11 @@ private function installAndBootstrapInstallation(): void $silentPlugins = $this->config['silentlyActivatePlugins']; $this->includeAllPlugins(array_merge($plugins, $silentPlugins), $isMultisite); + if (!empty($this->config['theme'])) { + /** @var string|array{string,string} $theme */ + $theme = $this->config['theme']; + $this->switchThemeFromFile($theme); + } $this->includeCorePHPUniteSuiteBootstrapFile(); Dispatcher::dispatch(self::EVENT_AFTER_INSTALL, $this); @@ -701,15 +709,15 @@ static function (string $plugin, bool $silent) use ($closuresFactory, $multisite ) ); - /** @var string $stylesheet */ - $stylesheet = $this->config['theme']; - if ($stylesheet) { - $jobs['stylesheet::' . $stylesheet] = $closuresFactory->toSwitchTheme($stylesheet, $multisite); + $themes = (array)$this->config['theme']; + foreach ($themes as $theme) { + $jobs['theme::' . basename($theme)] = $closuresFactory->toSwitchTheme($theme, $multisite); } $pluginsList = implode(', ', $plugins); - if ($stylesheet) { - codecept_debug('Activating plugins: ' . $pluginsList . ' and switching theme: ' . $stylesheet); + if ($themes) { + codecept_debug('Activating plugins: ' . $pluginsList + . ' and switching theme(s): ' . implode(', ', array_map('basename', $themes))); } else { codecept_debug('Activating plugins: ' . $pluginsList); } @@ -731,7 +739,7 @@ static function (string $plugin, bool $silent) use ($closuresFactory, $multisite : $result->getStdoutBuffer(); $message = $type === 'plugin' ? "Failed to activate plugin $name. $reason" - : "Failed to switch theme $name. $reason"; + : "Failed to switch to theme $name. $reason"; throw new ModuleException(__CLASS__, $message); } } @@ -1056,8 +1064,9 @@ private function muActivatePluginsTheme(array $plugins): array $database = $this->db; if ($this->config['theme']) { + $themes = (array)$this->config['theme']; // Refresh the theme related options. - update_site_option('allowedthemes', [$this->config['theme'] => true]); + update_site_option('allowedthemes', array_combine($themes, array_fill(0, count($themes), true))); if ($database === null) { throw new ModuleException( __CLASS__, @@ -1152,4 +1161,57 @@ private function includeAllPlugins(array $plugins, bool $isMultisite): void } }, -100000); } + + /** + * @param string|array{string,string} $theme + */ + private function switchThemeFromFile(string|array $theme):void + { + [$template, $stylesheet] = is_array($theme) ? $theme : [$theme, $theme]; + $templateRealpath = realpath($template); + $stylesheetRealpath = realpath($stylesheet); + $include = 0; + + if ($templateRealpath) { + $include |= 1; + } + + if ($stylesheetRealpath) { + $include |= 2; + } + + if ($include === 0) { + return; + } + + /** @var string $templateRealpath */ + /** @var string $stylesheetRealpath */ + + PreloadFilters::addFilter('after_setup_theme', static function () use ( + $include, + $templateRealpath, + $stylesheetRealpath + ) { + global $wp_stylesheet_path, $wp_template_path, $wp_theme_directories; + ($include & 1) && $wp_template_path = $templateRealpath; + ($include & 2) && $wp_stylesheet_path = $stylesheetRealpath; + ($include & 1) && ($wp_theme_directories[] = dirname($templateRealpath)); + ($include & 2) && ($wp_theme_directories[] = dirname($stylesheetRealpath)); + $wp_theme_directories = array_values(array_unique($wp_theme_directories)); + // Stylesheet first, template second. + (($include & 2) && ($stylesheetRealpath !== $templateRealpath)) + && include $stylesheetRealpath . '/functions.php'; + ($include & 1) && include $templateRealpath . '/functions.php'; + }, -100000); + + $templateName = basename($templateRealpath); + $templateRoot = dirname($templateRealpath); + $stylesheetName = basename($stylesheetRealpath); + $stylesheetRoot = dirname($stylesheetRealpath); + + PreloadFilters::addFilter('pre_option_template', static fn() => $templateName); + PreloadFilters::addFilter('pre_option_template_root', static fn() => $templateRoot); + PreloadFilters::addFilter('pre_option_stylesheet', static fn() => $stylesheetName); + PreloadFilters::addFilter('pre_option_stylesheet_root', static fn() => $stylesheetRoot); + } } diff --git a/src/WordPress/CodeExecution/ThemeSwitchAction.php b/src/WordPress/CodeExecution/ThemeSwitchAction.php index bdeae28ad..b52f82a6f 100644 --- a/src/WordPress/CodeExecution/ThemeSwitchAction.php +++ b/src/WordPress/CodeExecution/ThemeSwitchAction.php @@ -34,8 +34,21 @@ private function switchTheme(string $stylesheet, bool $multisite): void { // The `switch_theme` function will not complain about a missing theme: check it now. $theme = wp_get_theme($stylesheet); - if (!($theme instanceof WP_Theme && $theme->exists())) { - throw new InstallationException("Theme $stylesheet does not exist."); + + if (!($theme instanceof WP_Theme && $theme->exists() && !$theme->errors())) { + $themeRealPath = realpath($stylesheet); + + if ($themeRealPath && is_dir($themeRealPath) && is_file($themeRealPath . '/style.css')) { + $this->loadThemeFromFile($themeRealPath, $multisite); + return; + } + + $message = "Errors with theme $stylesheet."; + if ($theme->errors()) { + $message = implode(', ', $theme->errors()->get_error_messages()); + } + + throw new InstallationException($message); } if ($multisite) { @@ -53,4 +66,13 @@ public function getClosure(): Closure return $request->execute(); }; } + + private function loadThemeFromFile(string $themeRealPath, bool $multisite): void + { + include_once $themeRealPath . '/functions.php'; + $basename = basename($themeRealPath); + update_option('template', $basename); + update_option('stylesheet', $basename); + do_action('after_setup_theme'); + } } diff --git a/tests/_data/themes/dummy/style.css b/tests/_data/themes/dummy/style.css index b1cb6ff28..47c310536 100755 --- a/tests/_data/themes/dummy/style.css +++ b/tests/_data/themes/dummy/style.css @@ -1,6 +1,5 @@ /* Theme Name: Dummy Description: Dummy theme. -Template: dummy Version: 0.1.0 */ diff --git a/tests/_support/_generated/WploaderTesterActions.php b/tests/_support/_generated/WploaderTesterActions.php index 78c4ba56a..4b032a83d 100644 --- a/tests/_support/_generated/WploaderTesterActions.php +++ b/tests/_support/_generated/WploaderTesterActions.php @@ -1,4 +1,4 @@ -mockModuleContainer = new ModuleContainer(new Di(), $moduleContainerConfig); + return new WPLoader($this->mockModuleContainer, ($moduleConfig ?? $this->config)); + } + + /** + * It should allow loading a plugin from an arbitrary path + * + * @test + */ + public function should_allow_loading_a_plugin_from_an_arbitrary_path(): void + { + $myPluginCode = <<< PHP + $myPluginCode, + + 'var' => [ + 'wordpress' => [] + ], + 'vendor' => [ + 'acme' => [ + + ] + ] + ]); + $wpRootDir = $projectDir . '/var/wordpress'; + $installation = Installation::scaffold($wpRootDir); + $dbName = Random::dbName(); + $dbHost = Env::get('WORDPRESS_DB_HOST'); + $dbUser = Env::get('WORDPRESS_DB_USER'); + $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); + $installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_'); + // Copy WooCommerce from the main installation to a temporary directory. + $tmpDir = sys_get_temp_dir(); + $mainWPInstallationRootDir = Env::get('WORDPRESS_ROOT_DIR'); + if (!FS::recurseCopy( + $mainWPInstallationRootDir . '/wp-content/plugins/woocommerce', + $tmpDir . '/external-woocommerce' + )) { + throw new \RuntimeException('Could not copy plugin woocommerce'); + } + $externalAbsolutePathPluginDir = $tmpDir . '/external-woocommerce'; + $this->assertFileExists($externalAbsolutePathPluginDir . '/woocommerce.php'); + if (!FS::recurseCopy( + codecept_data_dir('plugins/some-external-plugin'), + $projectDir . '/vendor/acme/some-external-plugin' + )) { + throw new \RuntimeException('Could not copy plugin some-external-plugin'); + } + + $hash = md5(microtime()); + $externalExplodingPlugin = sys_get_temp_dir() . '/' . $hash . '/exploding-plugin'; + if (!(mkdir($externalExplodingPlugin, 0777, true) && is_dir($externalExplodingPlugin))) { + throw new \RuntimeException('Could not create exploding plugin directory'); + } + if (!copy(codecept_data_dir('plugins/exploding-plugin/main.php'), $externalExplodingPlugin . '/main.php')) { + throw new \RuntimeException('Could not copy exploding plugin file'); + } + $testPluginFileContents = <<< PHP +config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $installationDb->getDbUrl(), + 'tablePrefix' => 'test_', + 'plugins' => [ + 'test.php', // From the WordPress installation plugins directory. + $externalAbsolutePathPluginDir . '/woocommerce.php', // Absolute path. + 'vendor/acme/some-external-plugin/some-plugin.php', // Relative path to the project root folder. + 'my-plugin.php' // Relative path to the project root folder, development plugin file. + ], + 'silentlyActivatePlugins' => [ + $externalExplodingPlugin . '/main.php' // Absolute path. + ] + ]; + + $wpLoader = $this->module(); + $projectDirname = basename($projectDir); + + $this->assertInIsolation( + static function () use ($wpLoader, $projectDir) { + chdir($projectDir); + $projectDirname = basename($projectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals([ + 'test.php', + 'external-woocommerce/woocommerce.php', + 'some-external-plugin/some-plugin.php', + "$projectDirname/my-plugin.php", + 'exploding-plugin/main.php' + ], get_option('active_plugins')); + + // Test plugin from the WordPress installation plugins directory. + Assert::assertEquals('1', get_option('test_plugin_activated')); + Assert::assertTrue(function_exists('test_plugin_main')); + + // WooCommerce from the absolute path. + Assert::assertTrue(function_exists('wc_get_product')); + Assert::assertTrue(class_exists('WC_Product')); + $product = new \WC_Product(); + $product->set_name('Test Product'); + $product->set_price(10); + $product->set_status('publish'); + $product->save(); + Assert::assertInstanceOf(\WC_Product::class, $product); + Assert::assertInstanceOf(\WC_Product::class, wc_get_product($product->get_id())); + + // Some external plugin from the relative path. + Assert::assertTrue(function_exists('some_plugin_main')); + Assert::assertEquals('1', get_option('some_plugin_activated')); + + // My plugin from the relative path. + Assert::assertTrue(function_exists('my_plugin_main')); + Assert::assertEquals('1', get_option('my_plugin_activated')); + + // Exploding plugin from the absolute path. + Assert::assertTrue(function_exists('exploding_plugin_main')); + Assert::assertEquals('', get_option('exploding_plugin_activated')); + } + ); + } + + /** + * It should allow loading a plugin from an arbitrary path in multisite + * + * @test + */ + public function should_allow_loading_a_plugin_from_an_arbitrary_path_in_multisite(): void + { + $myPluginCode = <<< PHP + $myPluginCode, + + 'var' => [ + 'wordpress' => [] + ], + 'vendor' => [ + 'acme' => [ + + ] + ] + ]); + $wpRootDir = $projectDir . '/var/wordpress'; + $installation = Installation::scaffold($wpRootDir); + $dbName = Random::dbName(); + $dbHost = Env::get('WORDPRESS_DB_HOST'); + $dbUser = Env::get('WORDPRESS_DB_USER'); + $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); + $installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_'); + // Copy WooCommerce from the main installation to a temporary directory. + $tmpDir = sys_get_temp_dir(); + $mainWPInstallationRootDir = Env::get('WORDPRESS_ROOT_DIR'); + if (!FS::recurseCopy( + $mainWPInstallationRootDir . '/wp-content/plugins/woocommerce', + $tmpDir . '/external-woocommerce' + )) { + throw new \RuntimeException('Could not copy plugin woocommerce'); + } + $externalAbsolutePathPluginDir = $tmpDir . '/external-woocommerce'; + $this->assertFileExists($externalAbsolutePathPluginDir . '/woocommerce.php'); + if (!FS::recurseCopy( + codecept_data_dir('plugins/some-external-plugin'), + $projectDir . '/vendor/acme/some-external-plugin' + )) { + throw new \RuntimeException('Could not copy plugin some-external-plugin'); + } + + $hash = md5(microtime()); + $externalExplodingPlugin = sys_get_temp_dir() . '/' . $hash . '/exploding-plugin'; + if (!(mkdir($externalExplodingPlugin, 0777, true) && is_dir($externalExplodingPlugin))) { + throw new \RuntimeException('Could not create exploding plugin directory'); + } + if (!copy(codecept_data_dir('plugins/exploding-plugin/main.php'), $externalExplodingPlugin . '/main.php')) { + throw new \RuntimeException('Could not copy exploding plugin file'); + } + $testPluginFileContents = <<< PHP +config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $installationDb->getDbUrl(), + 'tablePrefix' => 'test_', + 'plugins' => [ + 'test.php', // From the WordPress installation plugins directory. + $externalAbsolutePathPluginDir . '/woocommerce.php', // Absolute path. + 'vendor/acme/some-external-plugin/some-plugin.php', // Relative path to the project root folder. + 'my-plugin.php' // Relative path to the project root folder, development plugin file. + ], + 'silentlyActivatePlugins' => [ + $externalExplodingPlugin . '/main.php' // Absolute path. + ] + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $projectDir) { + chdir($projectDir); + $projectDirname = basename($projectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals([ + 'test.php', + 'external-woocommerce/woocommerce.php', + 'some-external-plugin/some-plugin.php', + "$projectDirname/my-plugin.php", + 'exploding-plugin/main.php' + ], array_keys(get_site_option('active_sitewide_plugins'))); + + // Test plugin from the WordPress installation plugins directory. + Assert::assertEquals('1', get_option('test_plugin_activated')); + Assert::assertTrue(function_exists('test_plugin_main')); + + // WooCommerce from the absolute path. + Assert::assertTrue(function_exists('wc_get_product')); + Assert::assertTrue(class_exists('WC_Product')); + $product = new \WC_Product(); + $product->set_name('Test Product'); + $product->set_price(10); + $product->set_status('publish'); + $product->save(); + Assert::assertInstanceOf(\WC_Product::class, $product); + Assert::assertInstanceOf(\WC_Product::class, wc_get_product($product->get_id())); + + // Some external plugin from the relative path. + Assert::assertTrue(function_exists('some_plugin_main')); + Assert::assertEquals('1', get_option('some_plugin_activated')); + + // My plugin from the relative path. + Assert::assertTrue(function_exists('my_plugin_main')); + Assert::assertEquals('1', get_option('my_plugin_activated')); + + // Exploding plugin from the absolute path. + Assert::assertTrue(function_exists('exploding_plugin_main')); + Assert::assertEquals('', get_option('exploding_plugin_activated')); + } + ); + } +} diff --git a/tests/unit/lucatume/WPBrowser/Module/WPLoaderArbitraryThemeLocationTest.php b/tests/unit/lucatume/WPBrowser/Module/WPLoaderArbitraryThemeLocationTest.php new file mode 100644 index 000000000..bf51da28b --- /dev/null +++ b/tests/unit/lucatume/WPBrowser/Module/WPLoaderArbitraryThemeLocationTest.php @@ -0,0 +1,752 @@ + <<< CSS + /* + Theme Name: My Theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => << '', + 'var' => [ + 'wordpress' => [] + ], + 'vendor' => [ + 'acme' => [ + 'some-theme' => [ + 'style.css' => <<< CSS + /* + Theme Name: Acme Some Theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => <<< PHP + '', + ] + ] + ] + ]); + $wpRootDir = $themeProjectDir . '/var/wordpress'; + Installation::scaffold($wpRootDir, '6.1.1'); + $dbName = Random::dbName(); + $dbHost = Env::get('WORDPRESS_DB_HOST'); + $dbUser = Env::get('WORDPRESS_DB_USER'); + $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); + $db = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'test_'); + $this->config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => $themeProjectDir, + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + $themeName = basename($themeProjectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals($themeName, get_option('stylesheet')); + Assert::assertEquals($themeName, get_option('template')); + Assert::assertTrue(function_exists('my_theme_setup')); + Assert::assertEquals('1', get_option('my_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir, $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir, $wp_template_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Theme', $theme->get('Name')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure the module to use a relative path. + $this->config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => '.', + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + $projectDirname = basename($themeProjectDir); + + $wpLoader->_initialize(); + Assert::assertEquals($projectDirname, get_option('stylesheet')); + Assert::assertEquals($projectDirname, get_option('template')); + Assert::assertTrue(function_exists('my_theme_setup')); + Assert::assertEquals('1', get_option('my_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir, $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir, $wp_template_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Theme', $theme->get('Name')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure to use a theme from an absolute path. + $this->config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => $themeProjectDir . '/vendor/acme/some-theme', + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_some_theme_setup')); + Assert::assertEquals('1', get_option('acme_some_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_template_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('Acme Some Theme', $theme->get('Name')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure to use a theme from a relative path. + $this->config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => 'vendor/acme/some-theme', + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_some_theme_setup')); + Assert::assertEquals('1', get_option('acme_some_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_template_path); + } + ); + } + + private function module(array $moduleContainerConfig = [], ?array $moduleConfig = null): WPLoader + { + $this->mockModuleContainer = new ModuleContainer(new Di(), $moduleContainerConfig); + return new WPLoader($this->mockModuleContainer, ($moduleConfig ?? $this->config)); + } + + /** + * It should allow loading theme from arbitrary location in multisite + * + * @test + */ + public function should_allow_loading_theme_from_arbitrary_location_in_multisite(): void + { + $themeProjectDir = FS::tmpDir('wploader_', [ + 'style.css' => <<< CSS + /* + Theme Name: My Theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => << '', + 'var' => [ + 'wordpress' => [] + ], + 'vendor' => [ + 'acme' => [ + 'some-theme' => [ + 'style.css' => <<< CSS + /* + Theme Name: Acme Some Theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => <<< PHP + '', + ] + ] + ] + ]); + $wpRootDir = $themeProjectDir . '/var/wordpress'; + Installation::scaffold($wpRootDir, '6.1.1'); + $dbName = Random::dbName(); + $dbHost = Env::get('WORDPRESS_DB_HOST'); + $dbUser = Env::get('WORDPRESS_DB_USER'); + $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); + $db = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'test_'); + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => $themeProjectDir, + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + $themeName = basename($themeProjectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals($themeName, get_option('stylesheet')); + Assert::assertEquals($themeName, get_option('template')); + Assert::assertTrue(function_exists('my_theme_setup')); + Assert::assertEquals('1', get_option('my_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir, $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir, $wp_template_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Theme', $theme->get('Name')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure the module to use a relative path. + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => '.', + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + $projectDirname = basename($themeProjectDir); + + $wpLoader->_initialize(); + Assert::assertEquals($projectDirname, get_option('stylesheet')); + Assert::assertEquals($projectDirname, get_option('template')); + Assert::assertTrue(function_exists('my_theme_setup')); + Assert::assertEquals('1', get_option('my_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir, $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir, $wp_template_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Theme', $theme->get('Name')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure to use a theme from an absolute path. + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => $themeProjectDir . '/vendor/acme/some-theme', + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_some_theme_setup')); + Assert::assertEquals('1', get_option('acme_some_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_template_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('Acme Some Theme', $theme->get('Name')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure to use a theme from a relative path. + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => 'vendor/acme/some-theme', + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $themeProjectDir) { + chdir($themeProjectDir); + + $wpLoader->_initialize(); + + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertEquals('some-theme', get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_some_theme_setup')); + Assert::assertEquals('1', get_option('acme_some_theme_setup_ran')); + global $wp_stylesheet_path, $wp_template_path; + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_stylesheet_path); + Assert::assertEquals($themeProjectDir . '/vendor/acme/some-theme', $wp_template_path); + } + ); + } + +public function invalidThemeConfigurationDataProvider(): array + { + return [ + 'int' => [1], + 'float' => [1.1], + 'bool' => [true], + 'object' => [new \stdClass()], + 'array' => [[]], + 'associative array' => [['foo' => 'bar']], + ]; + } + + /** + * It should throw if theme parameter configured with not an array of two strings + * + * @test + * @dataProvider invalidThemeConfigurationDataProvider + */ + public function should_throw_if_theme_parameter_configured_with_not_an_array_of_two_strings($theme): void + { + $this->expectException(ModuleConfigException::class); + + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => __DIR__, + 'dbUrl' => 'mysql://root:root@mysql:3306/wordpress', + 'tablePrefix' => 'test_', + 'theme' => $theme + ]; + + $this->module(); + } + + /** + * It should allow loading parent and child theme from arbitrary paths + * + * @test + */ + public function should_allow_loading_parent_and_child_theme_from_arbitrary_paths(): void + { + $childThemeDir = FS::tmpDir('wploader_', [ + 'style.css' => <<< CSS + /* + Theme Name: My Child Theme + Template: parent-theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => << '', + 'var' => [ + 'wordpress' => [] + ], + 'vendor' => [ + 'acme' => [ + 'parent-theme' => [ + 'style.css' => <<< CSS + /* + Theme Name: Acme Parent Theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => <<< PHP + '', + ] + ] + ] + ]); + $wpRootDir = $childThemeDir . '/var/wordpress'; + $parentThemeDir = $childThemeDir . '/vendor/acme/parent-theme'; + Installation::scaffold($wpRootDir, '6.1.1'); + $dbName = Random::dbName(); + $dbHost = Env::get('WORDPRESS_DB_HOST'); + $dbUser = Env::get('WORDPRESS_DB_USER'); + $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); + $db = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'test_'); + $this->config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => ['vendor/acme/parent-theme', $childThemeDir] + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $parentThemeDir, $childThemeDir) { + chdir($childThemeDir); + $parentThemeName = basename($parentThemeDir); + $childThemeName = basename($childThemeDir); + + $wpLoader->_initialize(); + + Assert::assertEquals($parentThemeName, get_option('template')); + Assert::assertEquals($childThemeName, get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_parent_theme_setup')); + Assert::assertTrue(function_exists('my_child_theme_setup')); + Assert::assertEquals('1', get_option('acme_parent_theme_setup_ran')); + Assert::assertEquals('1', get_option('my_child_theme_setup_ran')); + global $wp_template_path, $wp_stylesheet_path; + Assert::assertEquals($parentThemeDir, $wp_template_path); + Assert::assertEquals($childThemeDir, $wp_stylesheet_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Child Theme', $theme->get('Name')); + Assert::assertEquals('parent-theme', $theme->get('Template')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure and load the child theme from the current directory. + $this->config = [ + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => ['vendor/acme/parent-theme', '.'] + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $parentThemeDir, $childThemeDir) { + chdir($childThemeDir); + $parentThemeName = basename($parentThemeDir); + $childThemeName = basename($childThemeDir); + + $wpLoader->_initialize(); + + Assert::assertEquals($parentThemeName, get_option('template')); + Assert::assertEquals($childThemeName, get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_parent_theme_setup')); + Assert::assertTrue(function_exists('my_child_theme_setup')); + Assert::assertEquals('1', get_option('acme_parent_theme_setup_ran')); + Assert::assertEquals('1', get_option('my_child_theme_setup_ran')); + global $wp_template_path, $wp_stylesheet_path; + Assert::assertEquals($parentThemeDir, $wp_template_path); + Assert::assertEquals($childThemeDir, $wp_stylesheet_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Child Theme', $theme->get('Name')); + Assert::assertEquals('parent-theme', $theme->get('Template')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + } + + /** + * It should allow loading parent and child theme from arbitrary paths in multisite + * + * @test + */ + public function should_allow_loading_parent_and_child_theme_from_arbitrary_paths_in_multisite(): void + { + $childThemeDir = FS::tmpDir('wploader_', [ + 'style.css' => <<< CSS + /* + Theme Name: My Child Theme + Template: parent-theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => << '', + 'var' => [ + 'wordpress' => [] + ], + 'vendor' => [ + 'acme' => [ + 'parent-theme' => [ + 'style.css' => <<< CSS + /* + Theme Name: Acme Parent Theme + Author: My Author + Description: My Description + Version: 1.0.0 + Tested up to: 5.9 + Requires PHP: 7.0 + License: GPLv2 or later + License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + CSS, + 'functions.php' => <<< PHP + '', + ] + ] + ] + ]); + $wpRootDir = $childThemeDir . '/var/wordpress'; + $parentThemeDir = $childThemeDir . '/vendor/acme/parent-theme'; + Installation::scaffold($wpRootDir, '6.1.1'); + $dbName = Random::dbName(); + $dbHost = Env::get('WORDPRESS_DB_HOST'); + $dbUser = Env::get('WORDPRESS_DB_USER'); + $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); + $db = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'test_'); + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => ['vendor/acme/parent-theme', $childThemeDir] + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $parentThemeDir, $childThemeDir) { + chdir($childThemeDir); + $parentThemeName = basename($parentThemeDir); + $childThemeName = basename($childThemeDir); + + $wpLoader->_initialize(); + + Assert::assertEquals($parentThemeName, get_option('template')); + Assert::assertEquals($childThemeName, get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_parent_theme_setup')); + Assert::assertTrue(function_exists('my_child_theme_setup')); + Assert::assertEquals('1', get_option('acme_parent_theme_setup_ran')); + Assert::assertEquals('1', get_option('my_child_theme_setup_ran')); + global $wp_template_path, $wp_stylesheet_path; + Assert::assertEquals($parentThemeDir, $wp_template_path); + Assert::assertEquals($childThemeDir, $wp_stylesheet_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Child Theme', $theme->get('Name')); + Assert::assertEquals('parent-theme', $theme->get('Template')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + + // Reconfigure and load the child theme from the current directory. + $this->config = [ + 'multisite' => true, + 'wpRootFolder' => $wpRootDir, + 'dbUrl' => $db->getDbUrl(), + 'tablePrefix' => 'test_', + 'theme' => ['vendor/acme/parent-theme', '.'] + ]; + + $wpLoader = $this->module(); + + $this->assertInIsolation( + static function () use ($wpLoader, $parentThemeDir, $childThemeDir) { + chdir($childThemeDir); + $parentThemeName = basename($parentThemeDir); + $childThemeName = basename($childThemeDir); + + $wpLoader->_initialize(); + + Assert::assertEquals($parentThemeName, get_option('template')); + Assert::assertEquals($childThemeName, get_option('stylesheet')); + Assert::assertTrue(function_exists('acme_parent_theme_setup')); + Assert::assertTrue(function_exists('my_child_theme_setup')); + Assert::assertEquals('1', get_option('acme_parent_theme_setup_ran')); + Assert::assertEquals('1', get_option('my_child_theme_setup_ran')); + global $wp_template_path, $wp_stylesheet_path; + Assert::assertEquals($parentThemeDir, $wp_template_path); + Assert::assertEquals($childThemeDir, $wp_stylesheet_path); + $theme = wp_get_theme(); + Assert::assertInstanceOf(\WP_Theme::class, $theme); + Assert::assertEquals('My Child Theme', $theme->get('Name')); + Assert::assertEquals('parent-theme', $theme->get('Template')); + Assert::assertEmpty($theme->errors()); + Assert::assertTrue($theme->exists()); + } + ); + } +} diff --git a/tests/unit/lucatume/WPBrowser/Module/WPLoaderTest.php b/tests/unit/lucatume/WPBrowser/Module/WPLoaderTest.php index cb5e9f4c0..defad60f4 100644 --- a/tests/unit/lucatume/WPBrowser/Module/WPLoaderTest.php +++ b/tests/unit/lucatume/WPBrowser/Module/WPLoaderTest.php @@ -2739,287 +2739,4 @@ static function () use ($wpLoader) { } ); } - - /** - * It should allow loading a plugin from an arbitrary path - * - * @test - */ - public function should_allow_loading_a_plugin_from_an_arbitrary_path(): void - { - $myPluginCode = <<< PHP - $myPluginCode, - - 'var' => [ - 'wordpress' => [] - ], - 'vendor' => [ - 'acme' => [ - - ] - ] - ]); - $wpRootDir = $projectDir. '/var/wordpress'; - $installation = Installation::scaffold($wpRootDir); - $dbName = Random::dbName(); - $dbHost = Env::get('WORDPRESS_DB_HOST'); - $dbUser = Env::get('WORDPRESS_DB_USER'); - $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); - $installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_'); - // Copy WooCommerce from the main installation to a temporary directory. - $tmpDir = sys_get_temp_dir(); - $mainWPInstallationRootDir = Env::get('WORDPRESS_ROOT_DIR'); - if (!FS::recurseCopy( - $mainWPInstallationRootDir . '/wp-content/plugins/woocommerce', - $tmpDir . '/external-woocommerce' - )) { - throw new \RuntimeException('Could not copy plugin woocommerce'); - } - $externalAbsolutePathPluginDir = $tmpDir . '/external-woocommerce'; - $this->assertFileExists($externalAbsolutePathPluginDir . '/woocommerce.php'); - if(!FS::recurseCopy( - codecept_data_dir('plugins/some-external-plugin'), - $projectDir . '/vendor/acme/some-external-plugin' - )){ - throw new \RuntimeException('Could not copy plugin some-external-plugin'); - } - - $hash = md5(microtime()); - $externalExplodingPlugin = sys_get_temp_dir() . '/' . $hash . '/exploding-plugin'; - if(!(mkdir($externalExplodingPlugin, 0777, true) && is_dir($externalExplodingPlugin))){ - throw new \RuntimeException('Could not create exploding plugin directory'); - } - if(!copy(codecept_data_dir('plugins/exploding-plugin/main.php'),$externalExplodingPlugin . '/main.php' )){ - throw new \RuntimeException('Could not copy exploding plugin file'); - } - $testPluginFileContents = <<< PHP -config = [ - 'wpRootFolder' => $wpRootDir, - 'dbUrl' => $installationDb->getDbUrl(), - 'tablePrefix' => 'test_', - 'plugins' => [ - 'test.php', // From the WordPress installation plugins directory. - $externalAbsolutePathPluginDir . '/woocommerce.php', // Absolute path. - 'vendor/acme/some-external-plugin/some-plugin.php', // Relative path to the project root folder. - 'my-plugin.php' // Relative path to the project root folder, development plugin file. - ], - 'silentlyActivatePlugins' => [ - $externalExplodingPlugin . '/main.php' // Absolute path. - ] - ]; - - $wpLoader = $this->module(); - $projectDirname = basename($projectDir); - - $this->assertInIsolation( - static function () use ($wpLoader, $projectDir) { - chdir($projectDir); - $projectDirname = basename($projectDir); - - $wpLoader->_initialize(); - - Assert::assertEquals([ - 'test.php', - 'external-woocommerce/woocommerce.php', - 'some-external-plugin/some-plugin.php', - "$projectDirname/my-plugin.php", - 'exploding-plugin/main.php' - ], get_option('active_plugins')); - - // Test plugin from the WordPress installation plugins directory. - Assert::assertEquals('1', get_option('test_plugin_activated')); - Assert::assertTrue(function_exists('test_plugin_main')); - - // WooCommerce from the absolute path. - Assert::assertTrue(function_exists('wc_get_product')); - Assert::assertTrue(class_exists('WC_Product')); - $product = new \WC_Product(); - $product->set_name('Test Product'); - $product->set_price(10); - $product->set_status('publish'); - $product->save(); - Assert::assertInstanceOf(\WC_Product::class, $product); - Assert::assertInstanceOf(\WC_Product::class, wc_get_product($product->get_id())); - - // Some external plugin from the relative path. - Assert::assertTrue(function_exists('some_plugin_main')); - Assert::assertEquals('1', get_option('some_plugin_activated')); - - // My plugin from the relative path. - Assert::assertTrue(function_exists('my_plugin_main')); - Assert::assertEquals('1', get_option('my_plugin_activated')); - - // Exploding plugin from the absolute path. - Assert::assertTrue(function_exists('exploding_plugin_main')); - Assert::assertEquals('', get_option('exploding_plugin_activated')); - } - ); - } - - /** - * It should allow loading a plugin from an arbitrary path in multisite - * - * @test - */ - public function should_allow_loading_a_plugin_from_an_arbitrary_path_in_multisite(): void - { - $myPluginCode = <<< PHP - $myPluginCode, - - 'var' => [ - 'wordpress' => [] - ], - 'vendor' => [ - 'acme' => [ - - ] - ] - ]); - $wpRootDir = $projectDir. '/var/wordpress'; - $installation = Installation::scaffold($wpRootDir); - $dbName = Random::dbName(); - $dbHost = Env::get('WORDPRESS_DB_HOST'); - $dbUser = Env::get('WORDPRESS_DB_USER'); - $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); - $installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_'); - // Copy WooCommerce from the main installation to a temporary directory. - $tmpDir = sys_get_temp_dir(); - $mainWPInstallationRootDir = Env::get('WORDPRESS_ROOT_DIR'); - if (!FS::recurseCopy( - $mainWPInstallationRootDir . '/wp-content/plugins/woocommerce', - $tmpDir . '/external-woocommerce' - )) { - throw new \RuntimeException('Could not copy plugin woocommerce'); - } - $externalAbsolutePathPluginDir = $tmpDir . '/external-woocommerce'; - $this->assertFileExists($externalAbsolutePathPluginDir . '/woocommerce.php'); - if(!FS::recurseCopy( - codecept_data_dir('plugins/some-external-plugin'), - $projectDir . '/vendor/acme/some-external-plugin' - )){ - throw new \RuntimeException('Could not copy plugin some-external-plugin'); - } - - $hash = md5(microtime()); - $externalExplodingPlugin = sys_get_temp_dir() . '/' . $hash . '/exploding-plugin'; - if(!(mkdir($externalExplodingPlugin, 0777, true) && is_dir($externalExplodingPlugin))){ - throw new \RuntimeException('Could not create exploding plugin directory'); - } - if(!copy(codecept_data_dir('plugins/exploding-plugin/main.php'),$externalExplodingPlugin . '/main.php' )){ - throw new \RuntimeException('Could not copy exploding plugin file'); - } - $testPluginFileContents = <<< PHP -config = [ - 'multisite' => true, - 'wpRootFolder' => $wpRootDir, - 'dbUrl' => $installationDb->getDbUrl(), - 'tablePrefix' => 'test_', - 'plugins' => [ - 'test.php', // From the WordPress installation plugins directory. - $externalAbsolutePathPluginDir . '/woocommerce.php', // Absolute path. - 'vendor/acme/some-external-plugin/some-plugin.php', // Relative path to the project root folder. - 'my-plugin.php' // Relative path to the project root folder, development plugin file. - ], - 'silentlyActivatePlugins' => [ - $externalExplodingPlugin . '/main.php' // Absolute path. - ] - ]; - - $wpLoader = $this->module(); - $projectDirname = basename($projectDir); - - $this->assertInIsolation( - static function () use ($wpLoader, $projectDir) { - chdir($projectDir); - $projectDirname = basename($projectDir); - - $wpLoader->_initialize(); - - Assert::assertEquals([ - 'test.php', - 'external-woocommerce/woocommerce.php', - 'some-external-plugin/some-plugin.php', - "$projectDirname/my-plugin.php", - 'exploding-plugin/main.php' - ], array_keys(get_site_option('active_sitewide_plugins'))); - - // Test plugin from the WordPress installation plugins directory. - Assert::assertEquals('1', get_option('test_plugin_activated')); - Assert::assertTrue(function_exists('test_plugin_main')); - - // WooCommerce from the absolute path. - Assert::assertTrue(function_exists('wc_get_product')); - Assert::assertTrue(class_exists('WC_Product')); - $product = new \WC_Product(); - $product->set_name('Test Product'); - $product->set_price(10); - $product->set_status('publish'); - $product->save(); - Assert::assertInstanceOf(\WC_Product::class, $product); - Assert::assertInstanceOf(\WC_Product::class, wc_get_product($product->get_id())); - - // Some external plugin from the relative path. - Assert::assertTrue(function_exists('some_plugin_main')); - Assert::assertEquals('1', get_option('some_plugin_activated')); - - // My plugin from the relative path. - Assert::assertTrue(function_exists('my_plugin_main')); - Assert::assertEquals('1', get_option('my_plugin_activated')); - - // Exploding plugin from the absolute path. - Assert::assertTrue(function_exists('exploding_plugin_main')); - Assert::assertEquals('', get_option('exploding_plugin_activated')); - } - ); - } }