diff --git a/CHANGELOG.md b/CHANGELOG.md index 28324ac2b..cb8fe1d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Ensure the `WPLoader` module will initialize correctly when used in `loadOnly` mode not using the `EventDispatcherBridge` extension. (thanks @lxbdr) - Support loading the `wpdb` class from either the `class-wpdb.php` file or the `wp-db.php` one, supporting older versions of WordPress (thanks @BrianHenryIE) +- Read content, plugins and mu-plugins directories paths from the `WPLoader` configuration parameters correctly. ## [4.2.5] 2024-06-26; diff --git a/docs/modules/WPLoader.md b/docs/modules/WPLoader.md index 545bbc9a2..87a38311e 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: @@ -45,7 +47,18 @@ When used in this mode, the module supports the following configuration paramete * `configFile` - a configuration file, or a set of configuration files, to load before the tests to further customize and control the WordPress testing environment. * `pluginsFolder` - the path to the plugins folder to use when loading WordPress. Equivalent to defining the - `WP_PLUGIN_DIR` constant. + `WP_PLUGIN_DIR` constant. If both this parameter and the `WP_PLUGIN_DIR` parameter are set, the `WP_PLUGIN_DIR` + parameter will override the value of this one. +* `WP_CONTENT_DIR` - the path to the content folder to use when loading WordPress in the context of tests. If the + installation used by the `WPLoader` module defines a `WP_CONTENT_DIR` constant in its `wp-config.php` file, the module + will throw an exception if this parameter is set. Setting this parameter will affect the `WP_PLUGIN_DIR` and the `WPMU_PLUGIN_DIR` + parameters. +* `WP_PLUGIN_DIR` - the path to the plugins folder to use when loading WordPress in the context of tests. If the + installation used by the `WPLoader` module defines a `WP_PLUGIN_DIR` constant in its `wp-config.php` file, the module + will throw an exception if this parameter is set. +* `WPMU_PLUGIN_DIR` - the path to the mu-plugins folder to use when loading WordPress in the context of tests. If the + installation used by the `WPLoader` module defines a `WPMU_PLUGIN_DIR` constant in its `wp-config.php` file, the module + will throw an exception if this parameter is set. * `plugins` - a list of plugins to activate and load in the WordPress installation. If the plugin is located in the WordPress installation plugins directory, then the plugin name can be specified using the `directory/file.php` format. If the plugin is located in an arbitrary path inside or outiside of the WordPress installation or project, then the @@ -58,7 +71,9 @@ When used in this mode, the module supports the following configuration paramete * `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 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', '.']`. + 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. @@ -132,9 +147,9 @@ modules: title: 'Integration Tests' plugins: # This plugin will be loaded from the WordPress installation plugins directory. - - hello.php + - hello.php # This plugin will be loaded from an arbitrary absolute path. - - /home/plugins/woocommerce/woocommerce.php + - /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. @@ -163,7 +178,7 @@ modules: - 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] + theme: [ twentytwentythree, /home/themes/my-theme ] ``` The following example configuration uses a SQLite database and loads a database fixture before the tests run: @@ -189,7 +204,7 @@ modules: - hello.php - woocommerce/woocommerce.php - my-plugin/my-plugin.php - theme: + theme: # Parent theme from relative path. - vendor/acme/parent-theme # Child theme from the current working directory. diff --git a/src/Module/WPLoader.php b/src/Module/WPLoader.php index ed7b087ff..e9ca48129 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() ); } @@ -476,7 +502,16 @@ public function _initialize(): void if ($this->installation->isConfigured()) { foreach (['WP_CONTENT_DIR', 'WP_PLUGIN_DIR', 'WPMU_PLUGIN_DIR'] as $pathConst) { $constValue = $this->installation->getState()->getConstant($pathConst); + if ($constValue && is_string($constValue)) { + if (isset($config[$pathConst])) { + throw new ModuleConfigException( + $this, + "Both the installation wp-config.php file and the module configuration define a " . + "{$pathConst} constant: only one can be set." + ); + } + $config[$pathConst] = $constValue; } } @@ -610,8 +645,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 +662,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 +903,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 +919,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 @@ -1083,7 +1142,7 @@ private function activatePluginsTheme(array $plugins): array wp_cache_delete('alloptions', 'options'); // Do not include external plugins, it would create issues at this stage. - $pluginsDir = $this->installation->getPluginsDir(); + $pluginsDir = $this->getPluginsFolder(); return array_values( array_filter( @@ -1125,7 +1184,7 @@ private function muActivatePluginsTheme(array $plugins): array wp_cache_delete("1::active_sitewide_plugins", 'site-options'); // Do not include external plugins, it would create issues at this stage. - $pluginsDir = $this->installation->getPluginsDir(); + $pluginsDir = $this->getPluginsFolder(); $validPlugins = array_values( array_filter( $plugins, @@ -1168,7 +1227,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..241d52d05 --- /dev/null +++ b/tests/_support/Traits/InstallationMocks.php @@ -0,0 +1,100 @@ + [ + 'version.php' => <<< PHP + <<< PHP + ' ' [ + 'version.php' => <<< PHP + ' 'haveSiteMetaInDatabase(2, 'foo', ['bar' => 'baz']); * ``` * - * @param int $blogId The blog ID. + * @param int $blogId The blog ID. * @param string $string The meta key. - * @param mixed $value The meta value. + * @param mixed $value The meta value. * * @return int The inserted row ID. * @see \lucatume\WPBrowser\Module\WPDb::haveSiteMetaInDatabase() @@ -3077,7 +3077,7 @@ public function grabSiteMetaFromDatabase(int $blogId, string $key, bool $single) * $type = $I->grabPostFieldFromDatabase(1, 'post_type'); * ``` * - * @param int $postId The post ID. + * @param int $postId The post ID. * @param string $field The post field to get the value for. * * @return mixed The value of the post field. @@ -3129,7 +3129,7 @@ public function importSqlDumpFile(?string $dumpFile = NULL): void { * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -3157,7 +3157,7 @@ public function dontSeeOptionInDatabase(array|string $criteriaOrName, mixed $val * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -3387,14 +3387,14 @@ public function cantSeePostMetaInDatabase(array $criteria): void { * $I->seePostWithTermInDatabase($postId, $fiction['term_taxonomy_id']); * ``` * - * @param int $post_id The post ID. - * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is + * @param int $post_id The post ID. + * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is * passed this parameter will be interpreted as a `term_id`, else as a * `term_taxonomy_id`. - * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use + * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use * the * term order. - * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used + * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used * to build a `taxonomy_term_id` from the `term_id`. * * @@ -3419,14 +3419,14 @@ public function seePostWithTermInDatabase(int $post_id, int $term_taxonomy_id, ? * $I->seePostWithTermInDatabase($postId, $fiction['term_taxonomy_id']); * ``` * - * @param int $post_id The post ID. - * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is + * @param int $post_id The post ID. + * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is * passed this parameter will be interpreted as a `term_id`, else as a * `term_taxonomy_id`. - * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use + * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use * the * term order. - * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used + * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used * to build a `taxonomy_term_id` from the `term_id`. * * @@ -3631,7 +3631,7 @@ public function grabPostsTableName(): string { * ``` * * @param string $tableName The table to fetch the last insertion for. - * @param string $idColumn The column that is used, in the table, to uniquely identify + * @param string $idColumn The column that is used, in the table, to uniquely identify * items. * * @return int The last insertion id. @@ -3661,8 +3661,8 @@ public function grabLatestEntryByFromDatabase(string $tableName, string $idColum * } * ``` * - * @param int $postId The post ID. - * @param string $meta_key The meta key. + * @param int $postId The post ID. + * @param string $meta_key The meta key. * @param mixed $meta_value The value to insert in the database, objects and arrays will be serialized. * * @return int The inserted meta `meta_id`. @@ -3761,8 +3761,8 @@ public function grabTermsTableName(): string { * ]); * ``` * - * @param string $name The term name, e.g. "Fuzzy". - * @param string $taxonomy The term taxonomy + * @param string $name The term name, e.g. "Fuzzy". + * @param string $taxonomy The term taxonomy * @param array $overrides An array of values to override the default ones. * * @return array An array containing `term_id` and `term_taxonomy_id` of the inserted term. @@ -3813,8 +3813,8 @@ public function grabTermTaxonomyTableName(): string { * } * ``` * - * @param int $term_id The ID of the term to insert the meta for. - * @param string $meta_key The key of the meta to insert. + * @param int $term_id The ID of the term to insert the meta for. + * @param string $meta_key The key of the meta to insert. * @param mixed $meta_value The value of the meta to insert, if serializable it will be serialized. * * @return int The inserted term meta `meta_id`. @@ -3886,9 +3886,9 @@ public function grabTermTaxonomyIdFromDatabase(array $criteria): int|false { * $I->haveTermRelationshipInDatabase($bookId, $fictionId); * ``` * - * @param int $object_id A post ID, a user ID or anything that can be assigned a taxonomy term. + * @param int $object_id A post ID, a user ID or anything that can be assigned a taxonomy term. * @param int $term_taxonomy_id The `term_taxonomy_id` of the term and taxonomy to create a relation with. - * @param int $term_order Defaults to `0`. + * @param int $term_order Defaults to `0`. * @see \lucatume\WPBrowser\Module\WPDb::haveTermRelationshipInDatabase() */ public function haveTermRelationshipInDatabase(int $object_id, int $term_taxonomy_id, int $term_order = 0): void { @@ -4392,7 +4392,7 @@ public function dontHaveLinkInDatabase(array $criteria): void { * * @param array $criteria An associative array of the column names and values to use as deletion * criteria. - * @param string $table The table name. + * @param string $table The table name. * @see \lucatume\WPBrowser\Module\WPDb::dontHaveInDatabase() */ public function dontHaveInDatabase(string $table, array $criteria): void { @@ -4476,9 +4476,9 @@ public function dontHaveUserMetaInDatabase(array $criteria): void { * $I->grabUserMetaFromDatabase($userId, 'api_data'); * ``` * - * @param int $userId The ID of th user to get the meta for. + * @param int $userId The ID of th user to get the meta for. * @param string $meta_key The meta key to fetch the value for. - * @param bool $single Whether to return a single value or an array of values. + * @param bool $single Whether to return a single value or an array of values. * * @return array|mixed An array of the different meta key values or a single value if `$single` is set * to `true`. @@ -4502,8 +4502,8 @@ public function grabUserMetaFromDatabase(int $userId, string $meta_key, bool $si * $I->grabAllFromDatabase($books, 'title', ['genre' => 'fiction']); * ``` * - * @param string $table The table to grab the values from. - * @param string $column The column to fetch. + * @param string $table The table to grab the values from. + * @param string $column The column to fetch. * @param array $criteria The search criteria. * * @return array> An array of results. @@ -4532,7 +4532,7 @@ public function grabAllFromDatabase(string $table, string $column, array $criter * ``` * * @param string $transient The transient name. - * @param mixed $value The transient value. + * @param mixed $value The transient value. * * @return int The inserted option `option_id`. * @see \lucatume\WPBrowser\Module\WPDb::haveTransientInDatabase() @@ -4557,7 +4557,7 @@ public function haveTransientInDatabase(string $transient, mixed $value): int { * * @param string $option_name The option name. * @param mixed $option_value The option value; if an array or object it will be serialized. - * @param string $autoload Whether the option should be autoloaded by WordPress or not. + * @param string $autoload Whether the option should be autoloaded by WordPress or not. * * @return int The inserted option `option_id` * @see \lucatume\WPBrowser\Module\WPDb::haveOptionInDatabase() @@ -4599,7 +4599,7 @@ public function dontHaveTransientInDatabase(string $transient): void { * $I->dontHaveOptionInDatabase('bar', 'baz'); * ``` * - * @param string $key The option name. + * @param string $key The option name. * @param mixed|null $value If set the option will only be removed if its value matches the passed one. * @see \lucatume\WPBrowser\Module\WPDb::dontHaveOptionInDatabase() */ @@ -4620,7 +4620,7 @@ public function dontHaveOptionInDatabase(string $key, mixed $value = NULL): void * $fooCountOptionId = $I->haveSiteOptionInDatabase('foo_count','23'); * ``` * - * @param string $key The name of the option to insert. + * @param string $key The name of the option to insert. * @param mixed $value The value to insert for the option. * * @return int The inserted option `option_id`. @@ -4657,8 +4657,6 @@ public function useMainBlog(): void { * * This has nothing to do with WordPress `switch_to_blog` function, this code will affect the table prefixes used. * - * @param int $blogId The ID of the blog to use. - * @throws ModuleException If the blog ID is not an integer greater than or equal to 0. * @example * ```php * // Switch to the blog with ID 23. @@ -4668,6 +4666,8 @@ public function useMainBlog(): void { * // Switch to the main blog using this method. * $I->useBlog(1); * ``` + * @param int $blogId The ID of the blog to use. + * @throws ModuleException If the blog ID is not an integer greater than or equal to 0. * @see \lucatume\WPBrowser\Module\WPDb::useBlog() */ public function useBlog(int $blogId = 1): void { @@ -4680,11 +4680,6 @@ public function useBlog(int $blogId = 1): void { * * Gets the blog URL from the Blog ID. * - * @param int $blogId The ID of the blog to get the URL for. - * - * @return string The blog URL. - * @throws ModuleException If the blog ID is not found in the database. - * * @example * ```php * // Get the URL for the main blog. @@ -4692,6 +4687,11 @@ public function useBlog(int $blogId = 1): void { * // Get the URL for the blog with ID 23. * $blog23Url = $I->grabBlogUrl(23); * ``` + * @param int $blogId The ID of the blog to get the URL for. + * + * @return string The blog URL. + * @throws ModuleException If the blog ID is not found in the database. + * * @see \lucatume\WPBrowser\Module\WPDb::grabBlogUrl() */ public function grabBlogUrl(int $blogId = 1): string { @@ -4712,7 +4712,7 @@ public function grabBlogUrl(int $blogId = 1): string { * $I->dontHaveSiteOptionInDatabase('foo_count', 23); * ``` * - * @param string $key The option name. + * @param string $key The option name. * @param mixed|null $value If set the option will only be removed it its value matches the specified one. * @see \lucatume\WPBrowser\Module\WPDb::dontHaveSiteOptionInDatabase() */ @@ -4734,7 +4734,7 @@ public function dontHaveSiteOptionInDatabase(string $key, mixed $value = NULL): * $I->haveSiteTransientInDatabase('api_data', ['user' => 'luca', 'token' => '11ae3ijns-j83']); * ``` * - * @param string $key The key of the site transient to insert, w/o the `_site_transient_` prefix. + * @param string $key The key of the site transient to insert, w/o the `_site_transient_` prefix. * @param mixed $value The value to insert; if serializable the value will be serialized. * * @return int The inserted transient `option_id` @@ -4837,7 +4837,7 @@ public function grabSiteTransientFromDatabase(string $key): mixed { * $I->seeSiteSiteTransientInDatabase('total_counts', 23); * ``` * - * @param string $key The name of the transient to check for, w/o the `_site_transient_` prefix. + * @param string $key The name of the transient to check for, w/o the `_site_transient_` prefix. * @param mixed|null $value If provided then the assertion will include the value. * * @throws JsonException @@ -4860,7 +4860,7 @@ public function seeSiteSiteTransientInDatabase(string $key, mixed $value = NULL) * $I->seeSiteSiteTransientInDatabase('total_counts', 23); * ``` * - * @param string $key The name of the transient to check for, w/o the `_site_transient_` prefix. + * @param string $key The name of the transient to check for, w/o the `_site_transient_` prefix. * @param mixed|null $value If provided then the assertion will include the value. * * @throws JsonException @@ -4888,7 +4888,7 @@ public function canSeeSiteSiteTransientInDatabase(string $key, mixed $value = NU * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -4916,7 +4916,7 @@ public function seeOptionInDatabase(array|string $criteriaOrName, mixed $value = * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -4942,7 +4942,7 @@ public function canSeeOptionInDatabase(array|string $criteriaOrName, mixed $valu * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -4967,7 +4967,7 @@ public function seeSiteOptionInDatabase(array|string $criteriaOrName, mixed $val * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -5000,7 +5000,7 @@ public function canSeeSiteOptionInDatabase(array|string $criteriaOrName, mixed $ * `Post Title - 1` for the second one and so on. * The same applies to meta values as well. * - * @param int $count The number of posts to insert. + * @param int $count The number of posts to insert. * * @return array An array of the inserted post IDs. * @@ -5069,7 +5069,7 @@ public function canSeeTermInDatabase(array $criteria): void { * ``` * * @param array $criteria An array of search criteria. - * @param bool $purgeMeta Whether the terms meta should be purged along side with the meta or not. + * @param bool $purgeMeta Whether the terms meta should be purged along side with the meta or not. * * @throws Exception If there's an issue removing the rows. * @see \lucatume\WPBrowser\Module\WPDb::dontHaveTermInDatabase() @@ -5165,8 +5165,8 @@ public function cantSeeTermInDatabase(array $criteria): void { * $I->haveManyCommentsInDatabase(3, $postId, ['comment_content' => 'Comment {{n}}']); * ``` * - * @param int $count The number of comments to insert. - * @param int $comment_post_ID The comment parent post ID. + * @param int $count The number of comments to insert. + * @param int $comment_post_ID The comment parent post ID. * @param array $overrides An associative array to override the defaults. * * @return array An array containing the inserted comments IDs. @@ -5187,7 +5187,7 @@ public function haveManyCommentsInDatabase(int $count, int $comment_post_ID, arr * $I->haveCommentInDatabase($postId, ['comment_content' => 'Test Comment', 'comment_karma' => 23]); * ``` * - * @param int $comment_post_ID The id of the post the comment refers to. + * @param int $comment_post_ID The id of the post the comment refers to. * @param array $data The comment data overriding default and random generated values. * * @return int The inserted comment `comment_id`. @@ -5213,8 +5213,8 @@ public function haveCommentInDatabase(int $comment_post_ID, array $data = []): i * $I->haveCommentMetaInDatabase($commentId, 'api_data', $apiData); * ``` * - * @param int $comment_id The ID of the comment to insert the meta for. - * @param string $meta_key The key of the comment meta to insert. + * @param int $comment_id The ID of the comment to insert the meta for. + * @param string $meta_key The key of the comment meta to insert. * @param mixed $meta_value The value of the meta to insert, if serializable it will be serialized. * * @return int The inserted comment meta ID. @@ -5259,7 +5259,7 @@ public function grabCommentmetaTableName(): string { * $draftsCount = $I->countRowsInDatabase($postsTable, ['post_status' => 'draft']); * ``` * - * @param string $table The table to count the rows in. + * @param string $table The table to count the rows in. * @param array $criteria Search criteria, if empty all table rows will be counted. * * @return int The number of table rows matching the search criteria. @@ -5281,7 +5281,7 @@ public function countRowsInDatabase(string $table, array $criteria = []): int { * ``` * * @param array $criteria An array of search criteria. - * @param bool $purgeMeta If set to `true` then the meta for the comment will be purged too. + * @param bool $purgeMeta If set to `true` then the meta for the comment will be purged too. * * * @throws Exception In case of incoherent query criteria. @@ -5348,7 +5348,7 @@ public function dontHaveCommentMetaInDatabase(array $criteria): void { * $linkIds = $I->haveManyLinksInDatabase(3, ['link_url' => 'http://example.org/test-{{n}}']); * ``` * - * @param int $count The number of links to insert. + * @param int $count The number of links to insert. * @param array $overrides Overrides for the default arguments. * * @return array An array of inserted `link_id`s. @@ -5417,9 +5417,9 @@ public function grabLinksTableName(): string { * ); * ``` * - * @param int $count The number of users to insert. - * @param string $user_login The user login name. - * @param string $role The user role. + * @param int $count The number of users to insert. + * @param string $user_login The user login name. + * @param string $role The user role. * @param array $overrides An array of values to override the default ones. * * @return array An array of user IDs. @@ -5468,14 +5468,14 @@ public function haveManyUsersInDatabase(int $count, string $user_login, string $ * $userId = $I->haveUserInDatabase('luca', ''); * ``` * - * @param string|array $role The user role slug(s), e.g. `administrator` or `['author', 'editor']`; + * @param string|array $role The user role slug(s), e.g. `administrator` or `['author', 'editor']`; * defaults to `subscriber`. If more than one role is specified, then the * first role in the list will be the user primary role and the * `wp_user_level` will be set to that role. * @param array $overrides An associative array of column names and values overriding defaults * in the `users` and `usermeta` table. * - * @param string $user_login The user login name. + * @param string $user_login The user login name. * * @return int The inserted user ID. * @@ -5566,7 +5566,7 @@ public function grabUsersTableName(): string { * ); * ``` * - * @param int $userId The ID of the user to set the capabilities of. + * @param int $userId The ID of the user to set the capabilities of. * @param string|array|array> $role Either a role string (e.g. * `administrator`),an associative array of blog * IDs/roles for a multisite installation (e.g. `[1 @@ -5592,9 +5592,9 @@ public function haveUserCapabilitiesInDatabase(int $userId, array|string $role): * $I->haveUserMetaInDatabase($userId, 'karma', 23); * ``` * - * @param int $userId The user ID. - * @param string $meta_key The meta key to set the value for. - * @param mixed $meta_value Either a single value or an array of values; objects will be serialized while array of + * @param int $userId The user ID. + * @param string $meta_key The meta key to set the value for. + * @param mixed $meta_value Either a single value or an array of values; objects will be serialized while array of * values will trigger the insertion of multiple rows. * * @return array An array of inserted `umeta_id`s. @@ -5639,7 +5639,7 @@ public function grabUsermetaTableName(): string { * $I->haveUserLevelsInDatabase($userId, $moreThanAnEditorLessThanAnAdmin); * ``` * - * @param int $userId The ID of the user to set the + * @param int $userId The ID of the user to set the * level for. * @param string|array|array|array> $role Either a user role (e.g. * `editor`), a list of user @@ -5672,9 +5672,9 @@ public function haveUserLevelsInDatabase(int $userId, array|string $role): array * $termTaxonomyIds = array_column($terms, 1); * ``` * - * @param int $count The number of terms to insert. - * @param string $name The term name template, can include the `{{n}}` placeholder. - * @param string $taxonomy The taxonomy to insert the terms for. + * @param int $count The number of terms to insert. + * @param string $name The term name template, can include the `{{n}}` placeholder. + * @param string $taxonomy The taxonomy to insert the terms for. * @param array $overrides An associative array of default overrides. * * @return array> An array of arrays containing `term_id` and `term_taxonomy_id` of the inserted terms. @@ -6081,11 +6081,11 @@ public function grabBlogsTableName(): string { * } * ``` * - * @param int $count The number of blogs to create. + * @param int $count The number of blogs to create. * * @param array $overrides An array of values to override the default ones; `{{n}}` will be replaced * by the count. - * @param bool $subdomain Whether the new blogs should be created as a subdomain or subfolder. + * @param bool $subdomain Whether the new blogs should be created as a subdomain or subfolder. * * @return array An array of inserted blogs `blog_id`s. * @throws JsonException @@ -6110,9 +6110,9 @@ public function haveManyBlogsInDatabase(int $count, array $overrides = [], bool * $blogId = $I->haveBlogInDatabase('test', ['administrator' => $userId], false); * ``` * - * @param string $domainOrPath The subdomain or the path to the be used for the blog. + * @param string $domainOrPath The subdomain or the path to the be used for the blog. * @param array $overrides An array of values to override the defaults. - * @param bool $subdomain Whether the new blog should be created as a subdomain (`true`) + * @param bool $subdomain Whether the new blog should be created as a subdomain (`true`) * or subfolder (`true`) * * @return int The inserted blog `blog_id`. @@ -6163,8 +6163,8 @@ public function getSiteDomain(): string { * ``` * * @param array $criteria An array of search criteria to find the blog rows in the blogs table. - * @param bool $removeTables Remove the blog tables. - * @param bool $removeUploads Remove the blog uploads; requires the `WPFilesystem` module. + * @param bool $removeTables Remove the blog tables. + * @param bool $removeUploads Remove the blog uploads; requires the `WPFilesystem` module. * * @throws JsonException If there's any issue debugging the query. * @see \lucatume\WPBrowser\Module\WPDb::dontHaveBlogInDatabase() @@ -6277,10 +6277,10 @@ public function cantSeeBlogInDatabase(array $criteria): void { * $I->useTheme('acme', 'acme', 'Acme Theme'); * ``` * - * @param string $stylesheet The theme stylesheet slug, e.g. `twentysixteen`. - * @param string|null $template The theme template slug, e.g. `twentysixteen`, defaults to `$stylesheet`. + * @param string $stylesheet The theme stylesheet slug, e.g. `twentysixteen`. + * @param string|null $template The theme template slug, e.g. `twentysixteen`, defaults to `$stylesheet`. * - * @param string|null $themeName The theme name, e.g. `Acme`, defaults to the "title" version of + * @param string|null $themeName The theme name, e.g. `Acme`, defaults to the "title" version of * `$stylesheet`. * @see \lucatume\WPBrowser\Module\WPDb::useTheme() */ @@ -6299,8 +6299,8 @@ public function useTheme(string $stylesheet, ?string $template = NULL, ?string $ * list($termId, $termTaxId) = $I->haveMenuInDatabase('test', 'sidebar'); * ``` * - * @param string $slug The menu slug. - * @param string $location The theme menu location the menu will be assigned to. + * @param string $slug The menu slug. + * @param string $location The theme menu location the menu will be assigned to. * @param array $overrides An array of values to override the defaults. * * @return array An array containing the created menu `term_id` and `term_taxonomy_id`. @@ -6324,11 +6324,11 @@ public function haveMenuInDatabase(string $slug, string $location, array $overri * $I->haveMenuItemInDatabase('test', 'Test two', 1); * ``` * - * @param string $title The menu item title. - * @param int|null $menuOrder An optional menu order, `1` based. - * @param array $meta An associative array that will be prefixed with `_menu_item_` for the item + * @param string $title The menu item title. + * @param int|null $menuOrder An optional menu order, `1` based. + * @param array $meta An associative array that will be prefixed with `_menu_item_` for the item * post meta. - * @param string $menuSlug The menu slug the item should be added to. + * @param string $menuSlug The menu slug the item should be added to. * * @return int The menu item post `ID` * @throws ModuleException If there's an issue inserting the database row. @@ -6395,15 +6395,15 @@ public function canSeeTermRelationshipInDatabase(array $criteria): void { * * Requires the WPFilesystem module. * - * @param string|int $date Either a string supported by the `strtotime` function or a UNIX + * @param string|int $date Either a string supported by the `strtotime` function or a UNIX * timestamp that should be used to build the "year/time" uploads * sub-folder structure. - * @param array $overrides An associative array of values overriding the default ones. + * @param array $overrides An associative array of values overriding the default ones. * @param array>|null $imageSizes An associative array in the format [ => * [,]] to override the image sizes created by * default. * - * @param string $file The absolute path to the attachment file. + * @param string $file The absolute path to the attachment file. * * @return int The post ID of the inserted attachment. * @@ -6540,12 +6540,12 @@ public function cantSeeAttachmentInDatabase(array $criteria): void { * $I->dontHaveAttachmentInDatabase($thumbnailId, true, true); * ``` * - * @param bool $purgeMeta If set to `true` then the meta for the attachment will be purged too. - * @param bool $removeFiles Remove all files too, requires the `WPFilesystem` module to be loaded in + * @param bool $purgeMeta If set to `true` then the meta for the attachment will be purged too. + * @param bool $removeFiles Remove all files too, requires the `WPFilesystem` module to be loaded in * the suite. * * - * @param array $criteria An array of search criteria to find the attachment post in the posts + * @param array $criteria An array of search criteria to find the attachment post in the posts * table. * * @throws ModuleRequireException If the WPFilesystem module is not loaded in the suite and the `$removeFiles` @@ -6640,7 +6640,7 @@ public function grabAttachmentMetadata(int $attachmentPostId): array { * ``` * * @param array $criteria An array of search criteria. - * @param bool $purgeMeta If set to `true` then the meta for the post will be purged too. + * @param bool $purgeMeta If set to `true` then the meta for the post will be purged too. * @see \lucatume\WPBrowser\Module\WPDb::dontHavePostInDatabase() */ public function dontHavePostInDatabase(array $criteria, bool $purgeMeta = true): void { @@ -6679,7 +6679,7 @@ public function dontHavePostMetaInDatabase(array $criteria): void { * ``` * * @param string $userEmail The email of the user to remove. - * @param bool $purgeMeta Whether the user meta should be purged alongside the user or not. + * @param bool $purgeMeta Whether the user meta should be purged alongside the user or not. * * @return array An array of the deleted user(s) ID(s) * @@ -6729,7 +6729,7 @@ public function grabTablePrefix(): string { * ``` * * @param int|string $userIdOrLogin The user ID or login name. - * @param bool $purgeMeta Whether the user meta should be purged alongside the user or not. + * @param bool $purgeMeta Whether the user meta should be purged alongside the user or not. * @see \lucatume\WPBrowser\Module\WPDb::dontHaveUserInDatabase() */ public function dontHaveUserInDatabase(string|int $userIdOrLogin, bool $purgeMeta = true): void { @@ -6767,9 +6767,9 @@ public function grabUserIdFromDatabase(string $userLogin): int|false { * $thumbnail_id = $I->grabPostMetaFromDatabase($postId, '_thumbnail_id', true); * ``` * - * @param int $postId The post ID. + * @param int $postId The post ID. * @param string $metaKey The key of the meta to retrieve. - * @param bool $single Whether to return a single meta value or an array of all available meta values. + * @param bool $single Whether to return a single meta value or an array of all available meta values. * * @return mixed|array Either a single meta value or an array of all the available meta values. * @see \lucatume\WPBrowser\Module\WPDb::grabPostMetaFromDatabase() @@ -6789,7 +6789,7 @@ public function grabPostMetaFromDatabase(int $postId, string $metaKey, bool $sin * $blogOptionTable = $I->grabBlogTableName($blogId, 'option'); * ``` * - * @param int $blogId The blog ID. + * @param int $blogId The blog ID. * @param string $table The table name, without table prefix. * * @return string The full blog table name, including the table prefix or an empty string @@ -6929,14 +6929,14 @@ public function grabBlogPath(int $blogId): string { * $I->dontSeePostWithTermInDatabase($postId, $nonFiction['term_taxonomy_id], ); * ``` * - * @param int $post_id The post ID. - * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is + * @param int $post_id The post ID. + * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is * passed this parameter will be interpreted as a `term_id`, else as a * `term_taxonomy_id`. - * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use + * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use * the * term order. - * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used + * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used * to build a `taxonomy_term_id` from the `term_id`. * * @@ -6962,14 +6962,14 @@ public function dontSeePostWithTermInDatabase(int $post_id, int $term_taxonomy_i * $I->dontSeePostWithTermInDatabase($postId, $nonFiction['term_taxonomy_id], ); * ``` * - * @param int $post_id The post ID. - * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is + * @param int $post_id The post ID. + * @param int $term_taxonomy_id The term `term_id` or `term_taxonomy_id`; if the `$taxonomy` argument is * passed this parameter will be interpreted as a `term_id`, else as a * `term_taxonomy_id`. - * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use + * @param int|null $term_order The order the term applies to the post, defaults to `null` to not use * the * term order. - * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used + * @param string|null $taxonomy The taxonomy the `term_id` is for; if passed this parameter will be used * to build a `taxonomy_term_id` from the `term_id`. * * @@ -6993,7 +6993,7 @@ public function cantSeePostWithTermInDatabase(int $post_id, int $term_taxonomy_i * $I->havePostThumbnailInDatabase($postId, $attachmentId); * ``` * - * @param int $postId The post ID to assign the thumbnail (featured image) to. + * @param int $postId The post ID to assign the thumbnail (featured image) to. * @param int $thumbnailId The post ID of the attachment. * * @return int The inserted meta id. @@ -7070,7 +7070,7 @@ public function importSql(array $sql): void { * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * @@ -7096,7 +7096,7 @@ public function dontSeeSiteOptionInDatabase(array|string $criteriaOrName, mixed * ``` * * @param array|string $criteriaOrName An array of search criteria or the option name. - * @param mixed|null $value The optional value to try and match, only used if the option + * @param mixed|null $value The optional value to try and match, only used if the option * name is provided. * * diff --git a/tests/unit/lucatume/WPBrowser/Module/WPLoaderLoadOnlyTest.php b/tests/unit/lucatume/WPBrowser/Module/WPLoaderLoadOnlyTest.php index a09ee9bcd..bfd4dbc72 100644 --- a/tests/unit/lucatume/WPBrowser/Module/WPLoaderLoadOnlyTest.php +++ b/tests/unit/lucatume/WPBrowser/Module/WPLoaderLoadOnlyTest.php @@ -6,67 +6,15 @@ use Codeception\Lib\ModuleContainer; use Codeception\Test\Unit; use lucatume\WPBrowser\Tests\Traits\Fork; -use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Utils\Env; -use lucatume\WPBrowser\Utils\Filesystem as FS; +use lucatume\WPBrowser\Tests\Traits\InstallationMocks; class WPLoaderLoadOnlyTest extends Unit { - use TmpFilesCleanup; - - private function makeMockWordPressInstallation(): array - { - $dbUser = Env::get('WORDPRESS_DB_USER'); - $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); - $dbLocalhostPort = Env::get('WORDPRESS_DB_LOCALHOST_PORT'); - $dbName = Env::get('WORDPRESS_DB_NAME'); - $wpRootFolder = FS::tmpDir('wploader_', [ - 'wp-includes' => [ - '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..2c53b3405 --- /dev/null +++ b/tests/unit/lucatume/WPBrowser/Module/WPLoaderScaffoldedInstallationCustomLocationsTest.php @@ -0,0 +1,411 @@ +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/some-path', $module->getContentFolder('some-path')); + $this->assertEquals( + $wpRootFolder . '/wp-content/some/other/path/', + $module->getContentFolder('/some/other/path/') + ); + $this->assertEquals($wpRootFolder . '/wp-content/plugins/', $module->getPluginsFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomContentLocationFromConfigConstantInConfiguredInstallation(): 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 . '/some-path', $module->getContentFolder('some-path')); + $this->assertEquals($contentDir . '/some/other/path/', $module->getContentFolder('/some/other/path/')); + $this->assertEquals($contentDir . '/plugins/', $module->getPluginsFolder()); + $this->assertEquals($contentDir . '/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomPluginsLocationFromConfigParameterInConfiguredInstallation(): 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($pluginsDir . '/some-path', $module->getPluginsFolder('some-path')); + $this->assertEquals($pluginsDir . '/some/other/path/', $module->getPluginsFolder('/some/other/path/')); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomPluginsLocationFromConfigConstantInConfiguredInstallation(): 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($pluginsDir . '/some-path', $module->getPluginsFolder('some-path')); + $this->assertEquals($pluginsDir . '/some/other/path/', $module->getPluginsFolder('/some/other/path/')); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomMuPluginsLocationFromConfigConstantInConfiguredInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation(); + $muPluginsDir = FS::tmpDir('custom-plugins-dir'); + $contentDir = FS::tmpDir('custom-content-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'WPMU_PLUGIN_DIR' => $muPluginsDir, + 'WP_CONTENT_DIR' => $contentDir + ]); + + Fork::executeClosure(function () use ($contentDir, $wpRootFolder, $muPluginsDir, $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($muPluginsDir . '/', $module->getMuPluginsFolder()); + $this->assertEquals($muPluginsDir . '/some-path', $module->getMuPluginsFolder('some-path')); + $this->assertEquals($muPluginsDir . '/some/other/path/', $module->getMuPluginsFolder('/some/other/path/')); + }); + } + + 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/some-path', $module->getContentFolder('some-path')); + $this->assertEquals( + $wpRootFolder . '/wp-content/some/other/path/', + $module->getContentFolder('/some/other/path/') + ); + $this->assertEquals($wpRootFolder . '/wp-content/plugins/', $module->getPluginsFolder()); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomContentLocationFromConfigConstantInScaffoldedInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockScaffoldedInstallation(); + $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, $module, $contentDir) { + // 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($contentDir . '/', $module->getContentFolder()); + $this->assertEquals($contentDir . '/some-path', $module->getContentFolder('some-path')); + $this->assertEquals($contentDir . '/some/other/path/', $module->getContentFolder('/some/other/path/')); + $this->assertEquals($contentDir . '/plugins/', $module->getPluginsFolder()); + $this->assertEquals($contentDir . '/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomPluginsLocationFromConfigParameterInScaffoldedInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockScaffoldedInstallation(); + $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, $module, $pluginsDir) { + // 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($pluginsDir . '/', $module->getPluginsFolder()); + $this->assertEquals($pluginsDir . '/some-path', $module->getPluginsFolder('some-path')); + $this->assertEquals($pluginsDir . '/some/other/path/', $module->getPluginsFolder('/some/other/path/')); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomPluginsLocationFromConfigConstantInScaffoldedInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockScaffoldedInstallation(); + $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, + 'pluginsFolder' => $pluginsDir, + 'WP_PLUGIN_DIR' => $pluginsDir2 + ]); + + Fork::executeClosure(function () use ($wpRootFolder, $module, $pluginsDir2) { + // 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($pluginsDir2 . '/', $module->getPluginsFolder()); + $this->assertEquals($pluginsDir2 . '/some-path', $module->getPluginsFolder('some-path')); + $this->assertEquals($pluginsDir2 . '/some/other/path/', $module->getPluginsFolder('/some/other/path/')); + $this->assertEquals($wpRootFolder . '/wp-content/mu-plugins/', $module->getMuPluginsFolder()); + }); + } + + public function testUsesCustomMuPluginsLocationFromConfigConstantInScaffoldedInstallation(): void + { + [$wpRootFolder, $dbUrl] = $this->makeMockScaffoldedInstallation(); + $muPluginsDir = FS::tmpDir('custom-mu-plugins-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'WPMU_PLUGIN_DIR' => $muPluginsDir + ]); + + Fork::executeClosure(function () use ($wpRootFolder, $module, $muPluginsDir) { + // 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($muPluginsDir . '/', $module->getMuPluginsFolder()); + $this->assertEquals($muPluginsDir . '/some-path', $module->getMuPluginsFolder('some-path')); + $this->assertEquals($muPluginsDir . '/some/other/path/', $module->getMuPluginsFolder('/some/other/path/')); + }); + } + + public function testThrowsIfContentDirConstantIsSetInWpConfigInConfiguredInstallation(): void{ + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation( + <<< PHP + define('WP_CONTENT_DIR', '/some/other/path'); + PHP + ); + $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 ($module) { + try { + $module->_initialize(); + } catch (\Throwable $e) { + $this->assertInstanceOf(ModuleConfigException::class, $e); + $this->assertStringContainsString( + 'Both the installation wp-config.php file and the module configuration define a WP_CONTENT_DIR constant: only one can be set.', + $e->getMessage() + ); + } + }); + } + + public function testThrowsIfPluginsDirConstantIsSetInWpConfigInConfiguredInstallation(): void{ + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation( + <<< PHP + define('WP_PLUGIN_DIR', '/some/other/path'); + PHP + ); + $pluginsDir = FS::tmpDir('custom-plugins-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'WP_PLUGIN_DIR' => $pluginsDir + ]); + + Fork::executeClosure(function () use ($module) { + try { + $module->_initialize(); + } catch (\Throwable $e) { + $this->assertInstanceOf(ModuleConfigException::class, $e); + $this->assertStringContainsString( + 'Both the installation wp-config.php file and the module configuration define a WP_PLUGIN_DIR constant: only one can be set.', + $e->getMessage() + ); + } + }); + } + + public function testThrowsIfMuPluginsDirConstantIsSetInWpConfigInConfiguredInstallation(): void{ + [$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation( + <<< PHP + define('WPMU_PLUGIN_DIR', '/some/other/path'); + PHP + ); + $muPluginsDir = FS::tmpDir('custom-mu-plugins-dir'); + $moduleContainer = new ModuleContainer(new Di(), []); + $module = new WPLoader($moduleContainer, [ + 'dbUrl' => $dbUrl, + 'wpRootFolder' => $wpRootFolder, + 'loadOnly' => false, + 'WPMU_PLUGIN_DIR' => $muPluginsDir + ]); + + Fork::executeClosure(function () use ($module) { + try { + $module->_initialize(); + } catch (\Throwable $e) { + $this->assertInstanceOf(ModuleConfigException::class, $e); + $this->assertStringContainsString( + 'Both the installation wp-config.php file and the module configuration define a WPMU_PLUGIN_DIR constant: only one can be set.', + $e->getMessage() + ); + } + }); + } +}