From 82800eaf7b2faffaf67ac8549f6172e3c5dc8ec2 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Fri, 30 Dec 2022 14:43:47 +0000 Subject: [PATCH 1/6] feat: support for WP_ENVIRONMENT_TYPE BREAKING CHANGE: * Rename environment variable `WP_ENV` to `WP_ENVIRONMENT_TYPE` * Rename sensitive config file from wp-config.local.php to .wp-config.php * Update .env file to use dotenv variable format * Remove support for CLI --env param * Rename WP_ENV_* PHP constants to WP_ENVIRONMENT_* --- .github/workflows/release-please.yml | 2 +- CHANGELOG.md | 5 +- CONTRIBUTING.md | 4 +- LICENSE | 21 +++ README.md | 221 +++------------------------ docs/README.md | 11 ++ docs/accessing-the-environment.md | 18 +++ docs/how-it-works.md | 55 +++++++ docs/install.md | 77 ++++++++++ docs/setting-the-environment.md | 54 +++++++ docs/upgrading.md | 59 +++++++ docs/wp-config-env.md | 78 ++++++++++ wp-config.development.php | 2 +- wp-config.env.php | 4 +- wp-config.load.php | 159 +++++++++++-------- wp-config.production.php | 2 +- wp-config.staging.php | 2 +- 17 files changed, 499 insertions(+), 275 deletions(-) create mode 100644 LICENSE create mode 100644 docs/README.md create mode 100644 docs/accessing-the-environment.md create mode 100644 docs/how-it-works.md create mode 100644 docs/install.md create mode 100644 docs/setting-the-environment.md create mode 100644 docs/upgrading.md create mode 100644 docs/wp-config-env.md diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 78b1391..7513392 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -20,7 +20,7 @@ jobs: if: ${{ steps.release.outputs.release_created }} - name: Create release files run: | - zip -r wordpress-multi-env-config.zip wp-config.* + zip -r wordpress-multi-env-config.zip .wp-config.php wp-config.* if: ${{ steps.release.outputs.release_created }} - name: Upload release files uses: svenstaro/upload-release-action@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 792dc7c..e8c9234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ ## [2.0.1](https://github.com/studio24/wordpress-multi-env-config/compare/v2.0.0...v2.0.1) (2022-08-23) - ### Bug Fixes * 29 - escape * wildcard match ([923c69c](https://github.com/studio24/wordpress-multi-env-config/commit/923c69ceb2044a0338e0b049eecc5db379522e35)) * 32: Can't use array for multisite across multiple environments ([7675bc2](https://github.com/studio24/wordpress-multi-env-config/commit/7675bc275b790e4c7fa1efe6807223d53f758596)) * Create release ZIP file and add docs on contributing and security reports ([057d949](https://github.com/studio24/wordpress-multi-env-config/commit/057d949629c9e24dd2d4da928d12bcf520bb0187)) + +## [1.0.2](https://github.com/studio24/wordpress-multi-env-config/releases/tag/v1.0.2) (2020-07-15) + +* Previous v1 release \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbfbb5e..c8d0057 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,9 @@ All Pull Requests need at least one approval from the Studio 24 development team This repo uses [Release Please](https://github.com/marketplace/actions/release-please-action) to automatically create releases, based on [semantic versioning](https://semver.org/). -To create a new release use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) in your commit message. This will automatically create a Release PR, which is kept up to date with further commits and you can merge it to create the new release when you are ready. +To create a new release use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) in your commit message. +On merging new code to the `main` branch this will automatically create a release PR, which you can merge to create the +new release when you are ready. Use the following keywords in your commits: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dc34d6e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Studio 24 Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 058119d..8735669 100644 --- a/README.md +++ b/README.md @@ -1,217 +1,34 @@ # Studio 24 WordPress Multi-Environment Config -This repository contains Studio 24's standard config setup for WordPress, which -loads different config based on current environment. This allows you to have different -site configuration (e.g. debug mode) for different environments (e.g. production and staging). +It is common practise in web development to create multiple environments for a website when testing it, for example +development, staging and production. It is also a common requirement to need different configuration depending on the +environment type, for example different database credentials. + +This repository supports different configuration for different environments. It is Studio 24's standard config setup for +WordPress sites. Credit is due to FocusLabs [EE Master Config](https://github.com/focuslabllc/ee-master-config) who gave me the inspiration for the organisation of the config files. -Please note the current version is v2, if you need to use the older v1 version [please see the v1 release](https://github.com/studio24/wordpress-multi-env-config/releases/tag/v1.0.2). - -## Contributing - -Strata is an Open Source project. Find out more about how to [contribute](CONTRIBUTING.md). - -## Security Issues - -If you discover a security vulnerability within WordPress Multi-Environment Config, please follow our [disclosure procedure](SECURITY.md). - -## How it works - -The system detects what environment the current website is in and loads the relevant config file for that environment. - -By default the environment is defined by the hostname, though you can also set this as an environment variable. - -Config files are then loaded according to the current environment. There is support for loading a local config file -for sensitive data, which is intended to not be committed to version control. - -### Config files - -Up to three different config files are loaded: - -1. **Default configuration** (in `wp-config.default.php`, e.g. shared settings such as `$table_prefix`) -2. **Environment configuration** (in `wp-config.{ENVIRONMENT}.php`, e.g. any setting specific to the environment such as database name or debug mode) -3. **Optional local settings** (in `wp-config.local.php`, e.g. any sensitive settings you do not want to commit to version control, e.g. database password) - -### Environment values - -By default, environment values are: - -* `production` (live website) -* `staging` (test website for client review) -* `development` (local development copy of the website) - -You can add other environment values by adding these to the `wp-config.env.php` file. - -## Setting the environment - -The current environment is detected in one of three ways: - -### Environment variable - -You can set an environment variable called `WP_ENV` to set which environment the website uses in your webserver configuration. - -This is commonly done via Apache in your virtual host declaration: - - SetEnv WP_ENV production - -If you don't use Apache consult your webserver documentation. - -### Server hostname +## Current version -The current environment can also be detected from matching the hostname with the domain setup in `wp-config.env.php`. +The current version is v3, see [upgrading from v2](docs/upgrading.md) for details on how to use the new version. -### WP-CLI -If you're using [WP-CLI](http://wp-cli.org/) you can specify your environment using an '.env' file. +## Installation -You need to create a file called `.env` and simply write the current environment in this file as a string. +See [installation](docs/install.md) docs. -Example: +## Documentation -``` -development -``` -This file needs to be placed among the wp-config.*.php files (this applies if you keep these files in a sub-folder and the wp-config.php file in the web root). +* [Docs](docs/README.md) + * [How it works](docs/how-it-works.md) + * [Setting the environment](docs/setting-the-environment.md) + * [Accessing the environment](docs/accessing-the-environment.md) -It is recommended you do not add this file to version control. - -## The wp-config.env.php file - -You need to edit the `wp-config.env.php` file to define some settings related to the current environment URL. This needs to -be set regardless of which method is used to set the environment, since all methods set the WordPress URL via settings -contained in this file. - -This file contains a simple array, made up of: - -``` -environment names => - domain => The domain name. - This can also be an array of multiple domains. - You can also use a wildcard * to indicate all sub-domains at a domain, which is useful when using - WordPress Multisite. If you use wildcards, set the domain should to a single string, not an array. - path => If WordPress is installed to a sub-folder set it here. - ssl => Whether SSL should be used on this domain. If set, this also sets FORCE_SSL_ADMIN to true. -``` - -Example usage: - -``` -$env = [ - 'production' => [ - 'domain' => 'domain.com', - 'path' => '', - 'ssl' => false, - ], - 'staging' => [ - 'domain' => 'staging.domain.com', - 'path' => '', - 'ssl' => false, - ], - 'development' => [ - 'domain' => 'domain.local', - 'path' => '', - 'ssl' => false, - ], -]; -``` - -If you use localhost for your local test website, just set the development hostname case to `localhost` rather than `domain.local`. - -Example usage when setting a sub-folder, and also serving the live site via SSL: - -``` - 'production' => [ - 'domain' => 'domain.com', - 'path' => 'blog', - 'ssl' => true, - ], -``` - -Example usage for using more than one domain for an environment. - -``` - 'production' => [ - 'domain' => ['domain.com', 'domain2.com'], - 'path' => '', - 'ssl' => false, - ], -``` - -Example usage when using a wildcard for WordPress multi-site. - -``` - 'production' => [ - 'domain' => '*.domain.com', - 'path' => '', - 'ssl' => false, - ], -``` - -## Installing - -Please note this requires PHP5.4 or above. You should really be on PHP5.6 at a minimum! - -1. Download the required files via [wordpress-multi-env-config.zip](https://github.com/studio24/wordpress-multi-env-config/releases/latest/download/wordpress-multi-env-config.zip) -2. First make a backup of your existing `wp-config.php` file. -3. Copy the following files from this repository to your WordPress installation: - -``` -wp-config.default.php -wp-config.env.php -wp-config.php -wp-config.load.php -``` - -3. Set the correct environments you wish to support via the file `wp-config.env.php`, see the documentation above. -4. Create one `wp-config.{environment}.php` file for each environment. You can use the sample files provided in this repository: - -``` -wp-config.development.php -wp-config.production.php -wp-config.staging.php -wp-config.local.php -``` - -5. Review your backup `wp-config.php` file and copy config settings to either the default config file or the environment config files as appropriate. It is suggested to: - * If the setting is the same across all environments, add to `wp-config.default.php` - * If the setting is unique to one environment, add to `wp-config.{environment}.php` - * If the setting is sensitive (e.g. database password) add to `wp-config.local.php` -6. Remember to update the authentication unique keys and salts in `wp-config.default.php` -7. If you use version control exclude `wp-config.local.php`, an example below for Git: - -``` -# .gitignore -wp-config.local.php -``` - -You should now be able to load up the website in each different environment and everything should work just fine! It should now be safe to delete your backup *wp-config.php* file. - -## Moving your config files outside of the document root - -If you want to store your config files outside of the document root for additional security, this is very easy. - -Simply move all the config files except for `wp-config.php` itself into another folder (which can be outside the doc root). -Next amend the require path for `wp-config.load.php` in `wp-config.php` to point to the new location and everything will work just fine! +## Contributing -Example directory structure: +WordPress Multi-Environment Config is an Open Source project. Find out more about how to [contribute](CONTRIBUTING.md). -``` -config/ - wp-config.default.php (Config folder outside of doc root) - wp-config.development.php - wp-config.env.php - wp-config.load.php - wp-config.local.php - wp-config.production.php - wp-config.staging.php -web/ - wp-config.php (Your website doc root, where WordPress is installed) -``` - -Example `wp-config.php` +## Security Issues -``` -/** Load the Studio 24 WordPress Multi-Environment Config. */ -require_once(ABSPATH . '../config/wp-config.load.php'); -``` \ No newline at end of file +If you discover a security vulnerability within WordPress Multi-Environment Config, please follow our [disclosure procedure](SECURITY.md). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..1aed06a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# Studio 24 WordPress Multi-Environment Config documentation + +* [How it works](how-it-works.md) +* [Installation](install.md) + * [The wp-config.env.php file](wp-config-env.md) +* [Upgrading](upgrading.md) +* [Setting the environment](setting-the-environment.md) +* [Accessing the environment](accessing-the-environment.md) +* [Changelog](../CHANGELOG.md) +* [Contributing](../CONTRIBUTING.md) +* [Security issues](../SECURITY.md) \ No newline at end of file diff --git a/docs/accessing-the-environment.md b/docs/accessing-the-environment.md new file mode 100644 index 0000000..fdaca76 --- /dev/null +++ b/docs/accessing-the-environment.md @@ -0,0 +1,18 @@ +# Accessing the environment + +## Returning the current environment type + +You can use the WordPress function `wp_get_environment_type()` to return the current environment. This defaults to `production` +if the current environment cannot be determined. + +You can see example usage on the [wp_get_environment_type() reference](https://developer.wordpress.org/reference/functions/wp_get_environment_type/) +documentation page. + +## Environment PHP constants + +The following PHP constants are available for the current environment: + +* `WP_ENVIRONMENT_TYPE` - environment type +* `WP_ENV_DOMAIN` - +* `WP_ENV_PATH` - +* `WP_ENV_SSL` - \ No newline at end of file diff --git a/docs/how-it-works.md b/docs/how-it-works.md new file mode 100644 index 0000000..d7444b1 --- /dev/null +++ b/docs/how-it-works.md @@ -0,0 +1,55 @@ +# How it works + +## The problem + +It is common practise in web development to create multiple environments for a website when testing it, for example +development, staging and production. It is also a common requirement to need different configuration depending on the +environment type, for example different database credentials. WordPress has no out-of-the box support for this. + +## Environment types in WordPress + +Since [version 5.5](https://make.wordpress.org/core/2020/07/24/new-wp_get_environment_type-function-in-wordpress-5-5/) +WordPress has supported environment types which allow you to set which environment type your website is running as, to +help toggle functionality in your website and plugins. + +Supported environment types are: + +* `production` +* `staging` +* `development` +* `local` + +Production represents the live website, and is the default if an environment cannot be detected. + +Typically, staging is a test version of a site for client review. + +Development is normally the local test version of a site. However, some people use development to indicate a test version +of a site for internal review (not for review by the client). In this instance, local is used to indicate the local test +version of a site. + +Please note, the intention is for the environment type to represent the type of environment you are running +rather than a specific instance, which is why the WordPress Core team decided to settle on a limited list of environment +types. These should be enough to toggle different functionality. + +## How this package works + +The `wp-config.load.php` file detects what environment the current website is in and loads the relevant config for +that environment. + +### Detecting the environment + +The environment is detected is done in one of the following ways (the first match wins): + +1. Environment variable `WP_ENVIRONMENT_TYPE` is set +2. Environment is detected in an `.env` file +3. Hostname matches an expected list of environment hostnames + +See [installation](install.md) and [wp-config.env.php file](wp-config-env.md) docs for how to set this up. + +### Config files + +Config files are loaded in the following order: + +1. `wp-config.default.php` the default configuration (e.g. shared settings such as `$table_prefix`) +2. `wp-config.{ENVIRONMENT}.php` environment configuration (e.g. any setting specific to the environment such as database name or debug mode) +3. `.wp-config.php`, local settings (e.g. any sensitive settings you do not want to commit to version control such as database password) diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..b4e5684 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,77 @@ +# Installation + +Please note this requires PHP 5.6 or above. You should really be on PHP 7.4 at a minimum! + +1. Download the required files via [wordpress-multi-env-config.zip](https://github.com/studio24/wordpress-multi-env-config/releases/latest/download/wordpress-multi-env-config.zip) +2. First make a backup of your existing `wp-config.php` file. + +1. Copy the following files from this repository to your WordPress installation: + +``` +wp-config.default.php +wp-config.env.php +wp-config.php +wp-config.load.php +``` + +3. Set the correct environments you wish to support via the file `wp-config.env.php`, see the [wp-config.env.php file](wp-config-env.md) docs. +4. Decide how to set the environment for your website, see [setting the environment](setting-the-environment.md) for options. +5. Create one `wp-config.{environment}.php` file for each environment. You can use the sample files provided in this repository if you wish: + +``` +wp-config.development.php +wp-config.production.php +wp-config.staging.php +``` + +6. Add the local-only config file if you wish to use it for storing sensitive settings (this step is optional): + +``` +.wp-config.php +``` + +7. If you use Git version control exclude `.wp-config.php` in your `.gitignore` file: + +``` +.wp-config.php +``` + +8. Review your backup `wp-config.php` file and copy config settings to either the default config file or the environment config files as appropriate. It is suggested to: + * If the setting is the same across all environments, add to `wp-config.default.php` + * If the setting is unique to one environment, add to `wp-config.{environment}.php` + * If the setting is sensitive (e.g. database password) add to `.wp-config.php` +9. Remember to update the authentication unique keys and salts in `wp-config.default.php` + + +You should now be able to load up the website in each different environment and everything should work just fine! It should now be safe to delete your backup *wp-config.php* file. + +## Moving your config files outside of the document root + +It is recommended to store your config files outside of the document root for additional security. + +First, simply move all the config files except for `wp-config.php` itself into another folder (which must be outside the +document root). + +Example directory structure: + +``` +├── config/ (config folder outside of doc root) +│ ├── .wp-config.php +│ ├── wp-config.default.php +│ ├── wp-config.development.php +│ ├── wp-config.env.php +│ ├── wp-config.load.php +│ ├── wp-config.production.php +│ └── wp-config.staging.php +└── web/ (your website document root, where WordPress is installed) + └── wp-config.php +``` + +Next, amend the require path for the `wp-config.load.php` file in `wp-config.php` to point to the new location and everything will work just fine! + +Example `wp-config.php`: + +``` +/** Load the Studio 24 WordPress Multi-Environment Config. */ +require_once(ABSPATH . '../config/wp-config.load.php'); +``` \ No newline at end of file diff --git a/docs/setting-the-environment.md b/docs/setting-the-environment.md new file mode 100644 index 0000000..8cf2cc7 --- /dev/null +++ b/docs/setting-the-environment.md @@ -0,0 +1,54 @@ +# Setting the environment + +The current environment is detected in one of three ways: + +1. Environment variable `WP_ENVIRONMENT_TYPE` is set +2. Environment is detected in an `.env` file +3. Hostname matches an expected list of environment hostnames + +## Environment variable + +You can set an environment variable called `WP_ENVIRONMENT_TYPE` to set which environment the website uses in your +webserver configuration. + +### Apache + +This is commonly done via Apache in your virtual host declaration: + +``` +SetEnv WP_ENVIRONMENT_TYPE development +``` + +If you don't use Apache consult your webserver documentation. + +### Command line + +If you are using WP CLI then you can set an environment variable on the command line via: + +``` +export WP_ENVIRONMENT_TYPE="development" +``` + +## .env file + +You can also specify your environment using an `.env` file. Simply create a file called `.env` and set the environment using +the following format: + +``` +WP_ENVIRONMENT_TYPE=development +``` + +This file must either exist in the same folder as `wp-config.load.php` or the parent folder (this is useful if you store +config files in the `config/` sub-folder. + +It is recommended you do not add this file to version control. You can exclude the file in your `.gitignore` file via: + +``` +.env +``` + +## Server hostname + +The current environment can also be detected from matching the hostname with the domain setup in `wp-config.env.php`. + +See the [wp-config.env.php file](wp-config-env.md). \ No newline at end of file diff --git a/docs/upgrading.md b/docs/upgrading.md new file mode 100644 index 0000000..80706c9 --- /dev/null +++ b/docs/upgrading.md @@ -0,0 +1,59 @@ +# Upgrading + +## Upgrade from v2 + +To upgrade please follow these steps. + +### wp-config.local.php + +WordPress now includes `local` as a valid environment type. Therefore, `wp-config.local.php` is no longer used for a +local-only sensitive config and is used instead as a valid environment config file (that can be comitted to git). + +In its place `.wp-config.php` is now used for storing sensitive config settings. + +1. If you are using `wp-config.local.php` for sensitive config settings, rename this file to `.wp-config.php` +2. Remove `wp-config.local.php` from your `.gitignore` file if it exists +3. Add the following to your `.gitignore` file: + +``` +# .gitignore +.wp-config.php +``` + +### WP_ENV + +In v2 this package used the environment variable `WP_ENV` to set the current environment. With the support of environment +types in WordPress this has now changed to `WP_ENVIRONMENT_TYPE` + +1. Update your environment variable from `WP_ENV` to `WP_ENVIRONMENT_TYPE` + +### Renamed PHP constants + +If you have any code that relies on the following PHP constants please update to the new value. You can also use +`wp_get_environment_type()` to return the current environment type. + +* `WP_ENV` to `WP_ENVIRONMENT_TYPE` +* `WP_ENV_DOMAIN` to `WP_ENVIRONMENT_DOMAIN` +* `WP_ENV_PATH` to `WP_ENVIRONMENT_PATH` +* `WP_ENV_SSL` to `WP_ENVIRONMENT_SSL` + +### .env file + +If you use an .env file to set your environment type, the format of this has changed to match the standard dotenv format. + +Old version: + +``` +development +``` + +New version: + +``` +WP_ENVIRONMENT_TYPE=development +``` + +### CLI --env param + +Removed the `--env` CLI parameter since this does not work reliably. Instead, set the environment in the `.env` file. See +[setting the environment](setting-the-environment.md). \ No newline at end of file diff --git a/docs/wp-config-env.md b/docs/wp-config-env.md new file mode 100644 index 0000000..ab75420 --- /dev/null +++ b/docs/wp-config-env.md @@ -0,0 +1,78 @@ +# The wp-config.env.php file + +You need to edit the `wp-config.env.php` file to define some settings related to the current environment URL. This needs to +be set regardless of which method is used to set the environment, since all methods set the WordPress URL via settings +contained in this file. + +This file contains a simple array, made up of: + +``` +environment names => + domain => The domain name. + This can also be an array of multiple domains. + You can also use a wildcard * to indicate all sub-domains at a domain, which is useful when using + WordPress Multisite. If you use wildcards, set the domain should to a single string, not an array. + path => If WordPress is installed to a sub-folder set it here. + ssl => Whether SSL should be used on this domain. If set, this also sets FORCE_SSL_ADMIN to true. +``` + +Supported environment types are: + +* `production` +* `staging` +* `development` +* `local` + +Example usage: + +``` +$env = [ + 'production' => [ + 'domain' => 'domain.com', + 'path' => '', + 'ssl' => true, + ], + 'staging' => [ + 'domain' => 'staging.domain.com', + 'path' => '', + 'ssl' => true, + ], + 'development' => [ + 'domain' => 'domain.local', + 'path' => '', + 'ssl' => false, + ], +]; +``` + +If you use localhost for your local test website, just set the development hostname case to `localhost` rather than `domain.local`. + +Example usage when setting a sub-folder, and also serving the live site via SSL: + +``` + 'production' => [ + 'domain' => 'domain.com', + 'path' => 'blog', + 'ssl' => true, + ], +``` + +Example usage for using more than one domain for an environment. + +``` + 'production' => [ + 'domain' => ['domain.com', 'domain2.com'], + 'path' => '', + 'ssl' => false, + ], +``` + +Example usage when using a wildcard for WordPress multi-site. + +``` + 'production' => [ + 'domain' => '*.domain.com', + 'path' => '', + 'ssl' => false, + ], +``` diff --git a/wp-config.development.php b/wp-config.development.php index 82286fc..b9cc652 100644 --- a/wp-config.development.php +++ b/wp-config.development.php @@ -19,7 +19,7 @@ /** MySQL database username */ define('DB_USER', ''); -/** MySQL database password - set in wp-config.local.php */ +/** MySQL database password - set in .wp-config.php */ /** * For developers: WordPress debugging mode. diff --git a/wp-config.env.php b/wp-config.env.php index 4fa0c48..dbfd5b4 100644 --- a/wp-config.env.php +++ b/wp-config.env.php @@ -24,12 +24,12 @@ 'production' => [ 'domain' => 'domain.com', 'path' => '', - 'ssl' => false, + 'ssl' => true, ], 'staging' => [ 'domain' => 'staging.domain.com', 'path' => '', - 'ssl' => false, + 'ssl' => true, ], 'development' => [ 'domain' => 'domain.local', diff --git a/wp-config.load.php b/wp-config.load.php index 4a88c42..a89a9e9 100644 --- a/wp-config.load.php +++ b/wp-config.load.php @@ -10,39 +10,39 @@ function s24_load_environment_config() { /** - * Setup environment + * Detect environment from environment variable */ - // Set env if set via environment variable - if (getenv('WP_ENV') !== false) { - define('WP_ENV', preg_replace('/[^a-z]/', '', getenv('WP_ENV'))); + $environmentType = getenv('WP_ENVIRONMENT_TYPE'); + if ($environmentType !== false) { + if (s24_allowed_environment($environmentType)) { + define('WP_ENVIRONMENT_TYPE', $environmentType); + } } - // Set env via --env= argument if running via WP-CLI - if (!defined('WP_ENV') && PHP_SAPI == "cli" && defined('WP_CLI_ROOT')) { - - // We need to set $argv as global to be able to access it - global $argv; + /** + * Detect environment from .env file + */ + if (!defined('WP_ENVIRONMENT_TYPE')) { + $envFile = null; - if (isset($argv)) { - foreach ($argv as $arg) { - if (preg_match('/--env=(.+)/', $arg, $m)) { - define('WP_ENV', preg_replace('/[^a-z]/', '', $m[1])); - break; - } - } + if (file_exists(__DIR__ . '/.env')) { + $envFile = file_get_contents(__DIR__ . '/.env'); + } elseif (file_exists(__DIR__ . '/../.env')) { + $envFile = file_get_contents(__DIR__ . '/../.env'); } - // Also support via .env file in config directory - if (!defined('WP_ENV')) { - if (file_exists(__DIR__ . '/.env')) { - $environment = trim(file_get_contents(__DIR__ . '/.env')); - define('WP_ENV', preg_replace('/[^a-z]/', '', $environment)); + if ($envFile !== null && preg_match('/WP_ENVIRONMENT_TYPE=(.+)/', $envFile, $m)) { + $environmentType = $m[1]; + if (s24_allowed_environment($environmentType)) { + define('WP_ENVIRONMENT_TYPE', $environmentType); } } } - // Define ENV from hostname - if (!defined('WP_ENV')) { + /** + * Detect environment from hostname + */ + if (!defined('WP_ENVIRONMENT_TYPE')) { if (isset($_SERVER['HTTP_X_FORWARDED_HOST']) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { $hostname = strtolower(filter_var($_SERVER['HTTP_X_FORWARDED_HOST'], FILTER_SANITIZE_STRING)); } else { @@ -50,58 +50,65 @@ function s24_load_environment_config() { } } - // Load environments + /** + * Load environments + */ require __DIR__ . '/wp-config.env.php'; if (!isset($env) || !is_array($env)) { - throw new Exception('$env array not detected, you must set this in wp-config.env.php'); + throw new Studio24_MultiEnvConfig_Exception('$env array not detected, you must set this in wp-config.env.php'); } // Set environment constants - if (defined('WP_ENV')) { - if (isset($env[WP_ENV])) { - define('WP_ENV_DOMAIN', $env[WP_ENV]['domain']); - define('WP_ENV_PATH', trim($env[WP_ENV]['path'], '/')); - define('WP_ENV_SSL', (bool) $env[WP_ENV]['ssl']); + if (defined('WP_ENVIRONMENT_TYPE')) { + if (isset($env[WP_ENVIRONMENT_TYPE])) { + define('WP_ENVIRONMENT_DOMAIN', $env[WP_ENVIRONMENT_TYPE]['domain']); + define('WP_ENVIRONMENT_PATH', trim($env[WP_ENVIRONMENT_TYPE]['path'], '/')); + define('WP_ENVIRONMENT_SSL', (bool) $env[WP_ENVIRONMENT_TYPE]['ssl']); } } else { - // Detect environment from hostname - foreach ($env as $environment => $env_vars) { + /** + * Detect environment from hostname + */ + foreach ($env as $envFile => $env_vars) { if (!isset($env_vars['domain'])) { - throw new Exception('You must set the domain value in your environment array, see wp-config.env.php'); + throw new Studio24_MultiEnvConfig_Exception('You must set the domain value in your environment array, see wp-config.env.php'); } $domain = $env_vars['domain']; - $wildcard = (is_string($domain) && strpos($domain, '*') !== false) ? true : false; + $wildcard = is_string($domain) && strpos($domain, '*') !== false; if ($wildcard) { $match = '/' . str_replace('\*', '([^.]+)', preg_quote($domain, '/')) . '/'; if (preg_match($match, $hostname, $m)) { - if (!defined('WP_ENV')) { - define('WP_ENV', preg_replace('/[^a-z]/', '', $environment)); + if (!defined('WP_ENVIRONMENT_TYPE')) { + $environmentType = preg_replace('/[^a-z]/', '', $envFile); + if (s24_allowed_environment($environmentType)) { + define('WP_ENVIRONMENT_TYPE', $environmentType); + } } - define('WP_ENV_DOMAIN', str_replace('*', $m[1], $domain)); + define('WP_ENVIRONMENT_DOMAIN', str_replace('*', $m[1], $domain)); if (isset($env_vars['ssl'])) { - define('WP_ENV_SSL', (bool)$env_vars['ssl']); + define('WP_ENVIRONMENT_SSL', (bool)$env_vars['ssl']); } else { - define('WP_ENV_SSL', false); + define('WP_ENVIRONMENT_SSL', false); } if (isset($env_vars['path'])) { - define('WP_ENV_PATH', trim($env_vars['path'], '/')); + define('WP_ENVIRONMENT_PATH', trim($env_vars['path'], '/')); } /** * Define WordPress Site URLs */ - $protocol = (WP_ENV_SSL) ? 'https://' : 'http://'; - $path = (defined('WP_ENV_PATH')) ? '/' . trim(WP_ENV_PATH, '/') : ''; + $protocol = (WP_ENVIRONMENT_SSL) ? 'https://' : 'http://'; + $path = (defined('WP_ENVIRONMENT_PATH')) ? '/' . trim(WP_ENVIRONMENT_PATH, '/') : ''; if (!defined('WP_SITEURL')) { - define('WP_SITEURL', $protocol . trim(WP_ENV_DOMAIN, '/') . $path); + define('WP_SITEURL', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); } if (!defined('WP_HOME')) { - define('WP_HOME', $protocol . trim(WP_ENV_DOMAIN, '/') . $path); + define('WP_HOME', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); } break; } @@ -111,30 +118,30 @@ function s24_load_environment_config() { } foreach ($domain as $domain_name) { if ($hostname === $domain_name) { - if (!defined('WP_ENV')) { - define('WP_ENV', preg_replace('/[^a-z]/', '', $environment)); + if (!defined('WP_ENVIRONMENT_TYPE')) { + define('WP_ENVIRONMENT_TYPE', preg_replace('/[^a-z]/', '', $envFile)); } - define('WP_ENV_DOMAIN', $domain_name); + define('WP_ENVIRONMENT_DOMAIN', $domain_name); if (isset($env_vars['ssl'])) { - define('WP_ENV_SSL', (bool)$env_vars['ssl']); + define('WP_ENVIRONMENT_SSL', (bool)$env_vars['ssl']); } else { - define('WP_ENV_SSL', false); + define('WP_ENVIRONMENT_SSL', false); } if (isset($env_vars['path'])) { - define('WP_ENV_PATH', trim($env_vars['path'], '/')); + define('WP_ENVIRONMENT_PATH', trim($env_vars['path'], '/')); } /** * Define WordPress Site URLs */ - $protocol = (WP_ENV_SSL) ? 'https://' : 'http://'; - $path = (defined('WP_ENV_PATH')) ? '/' . trim(WP_ENV_PATH, '/') : ''; + $protocol = (WP_ENVIRONMENT_SSL) ? 'https://' : 'http://'; + $path = (defined('WP_ENVIRONMENT_PATH')) ? '/' . trim(WP_ENVIRONMENT_PATH, '/') : ''; if (!defined('WP_SITEURL')) { - define('WP_SITEURL', $protocol . trim(WP_ENV_DOMAIN, '/') . $path); + define('WP_SITEURL', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); } if (!defined('WP_HOME')) { - define('WP_HOME', $protocol . trim(WP_ENV_DOMAIN, '/') . $path); + define('WP_HOME', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); } break; } @@ -142,25 +149,47 @@ function s24_load_environment_config() { } } - if (!defined('WP_ENV')) { - throw new Exception("Cannot determine current environment"); + if (!defined('WP_ENVIRONMENT_TYPE')) { + throw new Studio24_MultiEnvConfig_Exception("Cannot determine current environment"); } - if (!defined('WP_ENV_DOMAIN')) { - throw new Exception("Cannot determine current environment domain, make sure this is set in wp-config.env.php"); + if (!defined('WP_ENVIRONMENT_DOMAIN')) { + throw new Studio24_MultiEnvConfig_Exception("Cannot determine current environment domain, make sure this is set in wp-config.env.php"); } - if (!defined('WP_ENV_SSL')) { - define('WP_ENV_SSL', false); + if (!defined('WP_ENVIRONMENT_SSL')) { + define('WP_ENVIRONMENT_SSL', false); } - if (WP_ENV_SSL && (!defined('FORCE_SSL_ADMIN'))) { + if (WP_ENVIRONMENT_SSL && (!defined('FORCE_SSL_ADMIN'))) { define('FORCE_SSL_ADMIN', true); } // Define W3 Total Cache hostname if (defined('WP_CACHE')) { - define('COOKIE_DOMAIN', $hostname); + define('COOKIE_DOMAIN', WP_ENVIRONMENT_DOMAIN); } } + +/** + * Is this environment type allowed? + * @param string $name Environment type + * @return bool + */ +function s24_allowed_environment($name) +{ + $environments = [ + 'local', + 'development', + 'staging', + 'production', + ]; + return in_array((string) $name, $environments); +} + +/** + * Custom exception for any fatal errors + */ +class Studio24_MultiEnvConfig_Exception extends Exception { } + s24_load_environment_config(); @@ -172,9 +201,9 @@ function s24_load_environment_config() { require __DIR__ . '/wp-config.default.php'; // 2nd - Load config file for current environment -require __DIR__ . '/wp-config.' . WP_ENV . '.php'; +require __DIR__ . '/wp-config.' . WP_ENVIRONMENT_TYPE . '.php'; // 3rd - Load local config file with any sensitive settings -if (file_exists( __DIR__ . '/wp-config.local.php')) { - require __DIR__ . '/wp-config.local.php'; +if (file_exists(__DIR__ . '/.wp-config.php')) { + require __DIR__ . '/.wp-config.php'; } diff --git a/wp-config.production.php b/wp-config.production.php index d50c04a..db77a7c 100644 --- a/wp-config.production.php +++ b/wp-config.production.php @@ -19,7 +19,7 @@ /** MySQL database username */ define('DB_USER', ''); -/** MySQL database password - set in wp-config.local.php */ +/** MySQL database password - set in .wp-config.php */ /** * For developers: WordPress debugging mode. diff --git a/wp-config.staging.php b/wp-config.staging.php index cacbcd1..8538d9d 100644 --- a/wp-config.staging.php +++ b/wp-config.staging.php @@ -19,7 +19,7 @@ /** MySQL database username */ define('DB_USER', ''); -/** MySQL database password - set in wp-config.local.php */ +/** MySQL database password - set in .wp-config.php */ /** * For developers: WordPress debugging mode. From 84aa475213f6e890971fc79bbed5a0e4d510f562 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Fri, 30 Dec 2022 14:44:51 +0000 Subject: [PATCH 2/6] fix: rename wp-config.local.php to .wp-config.php since clashes with new "local" environment type --- .gitignore | 14 +------------- wp-config.local.php => .wp-config.php | 0 2 files changed, 1 insertion(+), 13 deletions(-) rename wp-config.local.php => .wp-config.php (100%) diff --git a/.gitignore b/.gitignore index 325c5cc..22d0d82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1 @@ -# OS generated files # -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db -/nbproject -/.project -/.settings -/.buildpath -.idea +vendor diff --git a/wp-config.local.php b/.wp-config.php similarity index 100% rename from wp-config.local.php rename to .wp-config.php From a9dd3fa0f27f5d74125fafcec5a5c247324fe54d Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Fri, 30 Dec 2022 19:25:09 +0000 Subject: [PATCH 3/6] Refactoring code into a class to make it testable --- .github/workflows/php.yml | 54 +++ .gitignore | 2 + README.md | 4 + composer.json | 6 + docs/README.md | 2 +- docs/accessing-the-environment.md | 6 +- docs/upgrading.md | 15 +- docs/wp-config-env.md | 2 +- phpstan.neon.dist | 5 + phpunit.xml.dist | 21 + tests/EnvironmentTypeTest.php | 45 ++ tests/LoadFromEnvFileTest.php | 31 ++ tests/LoadFromEnvironmentVariableTest.php | 27 ++ tests/LoadFromHostnameTest.php | 111 +++++ tests/bootstrap.php | 4 + tests/env-files/.env | 1 + tests/env-files/.env.invalid1 | 1 + tests/env-files/.env.invalid2 | 1 + tests/env-files/LoadDefaultEnvFileTest.php | 15 + .../LoadDefaultEnvFileOneFolderAboveTest.php | 14 + .../LoadDefaultEnvFileTwoFoldersAboveTest.php | 14 + wp-config.env.php | 2 +- wp-config.load.php | 387 +++++++++++------- 23 files changed, 614 insertions(+), 156 deletions(-) create mode 100644 .github/workflows/php.yml create mode 100644 composer.json create mode 100644 phpstan.neon.dist create mode 100644 phpunit.xml.dist create mode 100644 tests/EnvironmentTypeTest.php create mode 100644 tests/LoadFromEnvFileTest.php create mode 100644 tests/LoadFromEnvironmentVariableTest.php create mode 100644 tests/LoadFromHostnameTest.php create mode 100644 tests/bootstrap.php create mode 100644 tests/env-files/.env create mode 100644 tests/env-files/.env.invalid1 create mode 100644 tests/env-files/.env.invalid2 create mode 100644 tests/env-files/LoadDefaultEnvFileTest.php create mode 100644 tests/env-files/sub-folder/LoadDefaultEnvFileOneFolderAboveTest.php create mode 100644 tests/env-files/sub-folder/sub-folder2/LoadDefaultEnvFileTwoFoldersAboveTest.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..8fc8043 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,54 @@ +name: PHP tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + strategy: + matrix: + php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2'] + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + # https://github.com/marketplace/actions/setup-php-action + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, intl + ini-values: post_max_size=256M, max_execution_time=180 + + - uses: actions/checkout@v2 + + - name: Check PHP version + run: php -v + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: PHPStan + run: ./vendor/bin/phpstan analyse + + - name: PHPUnit + run: ./vendor/bin/phpunit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 22d0d82..fb32d52 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vendor +.phpunit.result.cache +composer.lock \ No newline at end of file diff --git a/README.md b/README.md index 8735669..2182c45 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ WordPress sites. Credit is due to FocusLabs [EE Master Config](https://github.com/focuslabllc/ee-master-config) who gave me the inspiration for the organisation of the config files. +## Requirements + +* PHP 7.3+ + ## Current version The current version is v3, see [upgrading from v2](docs/upgrading.md) for details on how to use the new version. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c4c6f84 --- /dev/null +++ b/composer.json @@ -0,0 +1,6 @@ +{ + "require-dev": { + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.9" + } +} diff --git a/docs/README.md b/docs/README.md index 1aed06a..82ae669 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,8 +3,8 @@ * [How it works](how-it-works.md) * [Installation](install.md) * [The wp-config.env.php file](wp-config-env.md) + * [Setting the environment](setting-the-environment.md) * [Upgrading](upgrading.md) -* [Setting the environment](setting-the-environment.md) * [Accessing the environment](accessing-the-environment.md) * [Changelog](../CHANGELOG.md) * [Contributing](../CONTRIBUTING.md) diff --git a/docs/accessing-the-environment.md b/docs/accessing-the-environment.md index fdaca76..8f20fbb 100644 --- a/docs/accessing-the-environment.md +++ b/docs/accessing-the-environment.md @@ -13,6 +13,6 @@ documentation page. The following PHP constants are available for the current environment: * `WP_ENVIRONMENT_TYPE` - environment type -* `WP_ENV_DOMAIN` - -* `WP_ENV_PATH` - -* `WP_ENV_SSL` - \ No newline at end of file +* `WP_ENVIRONMENT_DOMAIN` - the environment hostname +* `WP_ENVIRONMENT_PATH` - the path to the WordPress installation +* `WP_ENVIRONMENT_SSL` - whether the current environment supports SSL \ No newline at end of file diff --git a/docs/upgrading.md b/docs/upgrading.md index 80706c9..d6b4cb3 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -27,15 +27,16 @@ types in WordPress this has now changed to `WP_ENVIRONMENT_TYPE` 1. Update your environment variable from `WP_ENV` to `WP_ENVIRONMENT_TYPE` -### Renamed PHP constants +### Removed custom PHP constants -If you have any code that relies on the following PHP constants please update to the new value. You can also use -`wp_get_environment_type()` to return the current environment type. +If you have any code that relies on the PHP constant `WP_ENV` please update to use `wp_get_environment_type()` to return +the current environment type. -* `WP_ENV` to `WP_ENVIRONMENT_TYPE` -* `WP_ENV_DOMAIN` to `WP_ENVIRONMENT_DOMAIN` -* `WP_ENV_PATH` to `WP_ENVIRONMENT_PATH` -* `WP_ENV_SSL` to `WP_ENVIRONMENT_SSL` +The following custom PHP constants have been removed since they are no longer required: +* `WP_ENV` +* `WP_ENV_DOMAIN` +* `WP_ENV_PATH` +* `WP_ENV_SSL` ### .env file diff --git a/docs/wp-config-env.md b/docs/wp-config-env.md index ab75420..6ff6e0b 100644 --- a/docs/wp-config-env.md +++ b/docs/wp-config-env.md @@ -67,7 +67,7 @@ Example usage for using more than one domain for an environment. ], ``` -Example usage when using a wildcard for WordPress multi-site. +Example usage when using a wildcard for WordPress multi-site. Please note the wildcard `*` must appear at the start of the domain string. ``` 'production' => [ diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..7daa289 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,5 @@ +parameters: + level: 5 + paths: + - wp-config.load.php + - tests \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..5c85144 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + + + + + + + + tests + + + + diff --git a/tests/EnvironmentTypeTest.php b/tests/EnvironmentTypeTest.php new file mode 100644 index 0000000..110bbaf --- /dev/null +++ b/tests/EnvironmentTypeTest.php @@ -0,0 +1,45 @@ +assertTrue($bootstrap->validEnvironment('development')); + $this->assertTrue($bootstrap->validEnvironment('staging')); + $this->assertTrue($bootstrap->validEnvironment('production')); + $this->assertFalse($bootstrap->validEnvironment('stage')); + $this->assertFalse($bootstrap->validEnvironment('PRODUCTION')); + } + + public function testSetEnvType(): void + { + $bootstrap = new MultiEnvConfig_Bootstrap(); + $bootstrap->setEnvironmentType('stage'); + $this->assertFalse($bootstrap->hasEnvironment()); + + $bootstrap->setEnvironmentType('FISH'); + $this->assertFalse($bootstrap->hasEnvironment()); + $this->assertEmpty($bootstrap->getEnvironmentType()); + + $bootstrap->setEnvironmentType('production'); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('production', $bootstrap->getEnvironmentType()); + } + + public function testCustomSettings(): void + { + $settings = [ + 'production' => [ + 'domain' => 'test.com', + 'path' => '', + 'ssl' => true, + ], + ]; + $bootstrap = new MultiEnvConfig_Bootstrap($settings); + $this->assertEquals($settings, $bootstrap->getEnvironmentSettings()); + } + +} + diff --git a/tests/LoadFromEnvFileTest.php b/tests/LoadFromEnvFileTest.php new file mode 100644 index 0000000..6afad42 --- /dev/null +++ b/tests/LoadFromEnvFileTest.php @@ -0,0 +1,31 @@ +loadFromEnvFile(__DIR__ . '/env-files/.env'); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('staging', $bootstrap->getEnvironmentType()); + } + + public function testInvalid1(): void + { + $bootstrap = new MultiEnvConfig_Bootstrap(); + $bootstrap->loadFromEnvFile(__DIR__ . '/env-files/.env.invalid1'); + $this->assertFalse($bootstrap->hasEnvironment()); + $this->assertEmpty($bootstrap->getEnvironmentType()); + } + + public function testInvalid2(): void + { + $bootstrap = new MultiEnvConfig_Bootstrap(); + $bootstrap->loadFromEnvFile(__DIR__ . '/env-files/.env.invalid2'); + $this->assertFalse($bootstrap->hasEnvironment()); + $this->assertEmpty($bootstrap->getEnvironmentType()); + } + +} + diff --git a/tests/LoadFromEnvironmentVariableTest.php b/tests/LoadFromEnvironmentVariableTest.php new file mode 100644 index 0000000..749a410 --- /dev/null +++ b/tests/LoadFromEnvironmentVariableTest.php @@ -0,0 +1,27 @@ +loadFromEnvironmentVariable(); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('staging', $bootstrap->getEnvironmentType()); + } + + public function testSetInvalid(): void + { + $bootstrap = new MultiEnvConfig_Bootstrap(); + + putenv('WP_ENVIRONMENT_TYPE=stage'); + $bootstrap->loadFromEnvironmentVariable(); + $this->assertFalse($bootstrap->hasEnvironment()); + $this->assertEmpty($bootstrap->getEnvironmentType()); + } + +} + diff --git a/tests/LoadFromHostnameTest.php b/tests/LoadFromHostnameTest.php new file mode 100644 index 0000000..c5d121e --- /dev/null +++ b/tests/LoadFromHostnameTest.php @@ -0,0 +1,111 @@ +assertTrue($bootstrap->isWildcard('*.test.com')); + $this->assertTrue($bootstrap->isWildcard('*.subdomain.test.com')); + $this->assertTrue($bootstrap->isWildcard('*.subdomain.www.test.com')); + $this->assertFalse($bootstrap->isWildcard('www.*.test.com')); + $this->assertFalse($bootstrap->isWildcard('www.test.com')); + $this->assertFalse($bootstrap->isWildcard('test.*')); + } + + public function testMatchWildcard(): void + { + $bootstrap = new MultiEnvConfig_Bootstrap(); + $this->assertTrue($bootstrap->matchWildcardDomain('*.test.com', 'www.test.com')); + $this->assertTrue($bootstrap->matchWildcardDomain('*.test.com', 'www2.test.com')); + $this->assertTrue($bootstrap->matchWildcardDomain('*.test.com', 'fishcake.test.com')); + $this->assertFalse($bootstrap->matchWildcardDomain('*.test.com', 'www.test2.com')); + } + + public function testValid(): void + { + $settings = [ + 'production' => [ + 'domain' => 'www.test.com', + 'path' => '', + 'ssl' => true, + ], + 'staging' => [ + 'domain' => 'staging.test.com', + 'path' => '', + 'ssl' => true, + ], + ]; + $bootstrap = new MultiEnvConfig_Bootstrap($settings); + $bootstrap->loadFromHostname('staging.test.com'); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('staging', $bootstrap->getEnvironmentType()); + } + + public function testInvalid(): void + { + $settings = [ + 'production' => [ + 'domain' => 'www.test.com', + 'path' => '', + 'ssl' => true, + ], + 'staging' => [ + 'domain' => 'staging.test.com', + 'path' => '', + 'ssl' => true, + ], + ]; + $bootstrap = new MultiEnvConfig_Bootstrap($settings); + $bootstrap->loadFromHostname('fishcake.test.com'); + $this->assertFalse($bootstrap->hasEnvironment()); + $this->assertEmpty($bootstrap->getEnvironmentType()); + } + + public function testWildcard(): void + { + $settings = [ + 'production' => [ + 'domain' => 'www.test.com', + 'path' => '', + 'ssl' => true, + ], + 'staging' => [ + 'domain' => '*.staging-sites.com', + 'path' => '', + 'ssl' => true, + ], + ]; + $bootstrap = new MultiEnvConfig_Bootstrap($settings); + $bootstrap->loadFromHostname('mysite.staging-sites.com'); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('staging', $bootstrap->getEnvironmentType()); + $this->assertNotEquals('*.staging-sites.com', $bootstrap->getEnvironmentHostname()); + $this->assertEquals('mysite.staging-sites.com', $bootstrap->getEnvironmentHostname()); + } + + public function testArray(): void + { + $settings = [ + 'production' => [ + 'domain' => ['www.test.com', 'www2.test.com', 'www3.test.com'], + 'path' => '', + 'ssl' => true, + ], + 'staging' => [ + 'domain' => '*.staging-sites.com', + 'path' => '', + 'ssl' => true, + ], + ]; + $bootstrap = new MultiEnvConfig_Bootstrap($settings); + $bootstrap->loadFromHostname('www2.test.com'); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('production', $bootstrap->getEnvironmentType()); + $this->assertNotEquals('www.test.com', $bootstrap->getEnvironmentHostname()); + $this->assertEquals('www2.test.com', $bootstrap->getEnvironmentHostname()); + } + +} + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..2ad5277 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,4 @@ +loadFromEnvFile(null, __DIR__); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('staging', $bootstrap->getEnvironmentType()); + } + +} + diff --git a/tests/env-files/sub-folder/LoadDefaultEnvFileOneFolderAboveTest.php b/tests/env-files/sub-folder/LoadDefaultEnvFileOneFolderAboveTest.php new file mode 100644 index 0000000..4088e5c --- /dev/null +++ b/tests/env-files/sub-folder/LoadDefaultEnvFileOneFolderAboveTest.php @@ -0,0 +1,14 @@ +loadFromEnvFile(null, __DIR__); + $this->assertTrue($bootstrap->hasEnvironment()); + $this->assertEquals('staging', $bootstrap->getEnvironmentType()); + } +} + diff --git a/tests/env-files/sub-folder/sub-folder2/LoadDefaultEnvFileTwoFoldersAboveTest.php b/tests/env-files/sub-folder/sub-folder2/LoadDefaultEnvFileTwoFoldersAboveTest.php new file mode 100644 index 0000000..a2c9386 --- /dev/null +++ b/tests/env-files/sub-folder/sub-folder2/LoadDefaultEnvFileTwoFoldersAboveTest.php @@ -0,0 +1,14 @@ +loadFromEnvFile(null, __DIR__); + $this->assertFalse($bootstrap->hasEnvironment()); + $this->assertEmpty($bootstrap->getEnvironmentType()); + } +} + diff --git a/wp-config.env.php b/wp-config.env.php index dbfd5b4..8c636ab 100644 --- a/wp-config.env.php +++ b/wp-config.env.php @@ -7,7 +7,7 @@ /** - * Define array of environment URLs + * Define array of environment settings * * Array of: * environment names => diff --git a/wp-config.load.php b/wp-config.load.php index a89a9e9..f5f83f7 100644 --- a/wp-config.load.php +++ b/wp-config.load.php @@ -1,4 +1,6 @@ */ -function s24_load_environment_config() { +/** + * Custom exceptions for any fatal errors + */ +class MultiEnvConfig_Exception extends Exception { } + +/** + * Class to bootstrap the environment and load the appropriate config + */ +final class MultiEnvConfig_Bootstrap { + + private $environmentType = null; + private $environmentHostname = null; + private $environmentSettings = []; /** - * Detect environment from environment variable + * Load environments + * @param array|null $environmentSettings + * @throws MultiEnvConfig_Exception */ - $environmentType = getenv('WP_ENVIRONMENT_TYPE'); - if ($environmentType !== false) { - if (s24_allowed_environment($environmentType)) { - define('WP_ENVIRONMENT_TYPE', $environmentType); + public function __construct(?array $environmentSettings = null) + { + if (null === $environmentSettings) { + require __DIR__ . '/wp-config.env.php'; + + /* @phpstan-ignore-next-line */ + if (!isset($env) || !is_array($env)) { + throw new MultiEnvConfig_Exception('$env array not detected, you must set this in wp-config.env.php'); + } + /* @phpstan-ignore-next-line */ + $environmentSettings = $env; } + + $this->environmentSettings = $environmentSettings; } /** - * Detect environment from .env file + * Is this environment type allowed? + * @see https://developer.wordpress.org/reference/functions/wp_get_environment_type/ + * @param string $name Environment type + * @return bool */ - if (!defined('WP_ENVIRONMENT_TYPE')) { - $envFile = null; + function validEnvironment(string $name): bool + { + $environments = [ + 'local', + 'development', + 'staging', + 'production', + ]; + return in_array($name, $environments); + } - if (file_exists(__DIR__ . '/.env')) { - $envFile = file_get_contents(__DIR__ . '/.env'); - } elseif (file_exists(__DIR__ . '/../.env')) { - $envFile = file_get_contents(__DIR__ . '/../.env'); + /** + * Whether the current environment type is set + * @return bool + */ + public function hasEnvironment(): bool + { + if (null === $this->environmentType) { + return false; } - - if ($envFile !== null && preg_match('/WP_ENVIRONMENT_TYPE=(.+)/', $envFile, $m)) { - $environmentType = $m[1]; - if (s24_allowed_environment($environmentType)) { - define('WP_ENVIRONMENT_TYPE', $environmentType); - } + if (!$this->validEnvironment((string) $this->environmentType)) { + $this->environmentType = null; + return false; } + return true; } /** - * Detect environment from hostname + * Set environment type + * @param string $type + * @return void */ - if (!defined('WP_ENVIRONMENT_TYPE')) { - if (isset($_SERVER['HTTP_X_FORWARDED_HOST']) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { - $hostname = strtolower(filter_var($_SERVER['HTTP_X_FORWARDED_HOST'], FILTER_SANITIZE_STRING)); - } else { - $hostname = strtolower(filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_STRING)); + public function setEnvironmentType(string $type): void + { + if ($this->validEnvironment($type)) { + $this->environmentType = $type; } } /** - * Load environments + * Return current environment type + * @return string|null + */ + public function getEnvironmentType(): ?string + { + return $this->environmentType; + } + + /** + * Return the environment settings + * @return array */ - require __DIR__ . '/wp-config.env.php'; + public function getEnvironmentSettings(): array + { + return $this->environmentSettings; + } - if (!isset($env) || !is_array($env)) { - throw new Studio24_MultiEnvConfig_Exception('$env array not detected, you must set this in wp-config.env.php'); + /** + * Return environment hostname, or null if not set + * @return string|null + */ + public function getEnvironmentHostname(): ?string + { + return $this->environmentHostname; } - // Set environment constants - if (defined('WP_ENVIRONMENT_TYPE')) { - if (isset($env[WP_ENVIRONMENT_TYPE])) { - define('WP_ENVIRONMENT_DOMAIN', $env[WP_ENVIRONMENT_TYPE]['domain']); - define('WP_ENVIRONMENT_PATH', trim($env[WP_ENVIRONMENT_TYPE]['path'], '/')); - define('WP_ENVIRONMENT_SSL', (bool) $env[WP_ENVIRONMENT_TYPE]['ssl']); + /** + * Detect environment from environment variable + * @return void + */ + public function loadFromEnvironmentVariable(): void + { + $environmentType = getenv('WP_ENVIRONMENT_TYPE'); + if (false !== $environmentType) { + $this->setEnvironmentType($environmentType); } + } - } else { + /** + * Detect environment from .env file + * @param string|null $filepath + * @param string|null $dir + * @return void + */ + public function loadFromEnvFile(?string $filepath = null, ?string $dir = null): void + { + if (null === $filepath) { + if (null == $dir) { + $dir = __DIR__; + } + if (file_exists($dir . '/.env')) { + $filepath = $dir . '/.env'; + } elseif (file_exists($dir . '/../.env')) { + $filepath = $dir . '/../.env'; + } else { + return; + } + } + $envFile = file_get_contents($filepath); + if ($envFile !== false && preg_match('/WP_ENVIRONMENT_TYPE=(.+)/', $envFile, $m)) { + $this->setEnvironmentType($m[1]); + } + } - /** - * Detect environment from hostname - */ - foreach ($env as $envFile => $env_vars) { - if (!isset($env_vars['domain'])) { - throw new Studio24_MultiEnvConfig_Exception('You must set the domain value in your environment array, see wp-config.env.php'); + /** + * Detect environment from hostname + * @param string|null $hostname + * @return void + */ + public function loadFromHostname(?string $hostname = null): void + { + if (null === $hostname) { + if (isset($_SERVER['HTTP_X_FORWARDED_HOST']) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + $hostname = strtolower(filter_var($_SERVER['HTTP_X_FORWARDED_HOST'], FILTER_SANITIZE_STRING)); + } else { + $hostname = strtolower(filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_STRING)); } - $domain = $env_vars['domain']; - - $wildcard = is_string($domain) && strpos($domain, '*') !== false; - if ($wildcard) { - $match = '/' . str_replace('\*', '([^.]+)', preg_quote($domain, '/')) . '/'; - if (preg_match($match, $hostname, $m)) { - if (!defined('WP_ENVIRONMENT_TYPE')) { - $environmentType = preg_replace('/[^a-z]/', '', $envFile); - if (s24_allowed_environment($environmentType)) { - define('WP_ENVIRONMENT_TYPE', $environmentType); - } - } - define('WP_ENVIRONMENT_DOMAIN', str_replace('*', $m[1], $domain)); - if (isset($env_vars['ssl'])) { - define('WP_ENVIRONMENT_SSL', (bool)$env_vars['ssl']); - } else { - define('WP_ENVIRONMENT_SSL', false); - } - if (isset($env_vars['path'])) { - define('WP_ENVIRONMENT_PATH', trim($env_vars['path'], '/')); - } - - /** - * Define WordPress Site URLs - */ - $protocol = (WP_ENVIRONMENT_SSL) ? 'https://' : 'http://'; - $path = (defined('WP_ENVIRONMENT_PATH')) ? '/' . trim(WP_ENVIRONMENT_PATH, '/') : ''; - - if (!defined('WP_SITEURL')) { - define('WP_SITEURL', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); - } - if (!defined('WP_HOME')) { - define('WP_HOME', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); - } - break; - } + } + if (empty($hostname)) { + return; + } + + // Try to match hostname against environment domain + foreach ($this->getEnvironmentSettings() as $environment => $envVars) { + if (!isset($envVars['domain'])) { + throw new MultiEnvConfig_Exception('You must set the domain value in your environment array, see wp-config.env.php'); } - if (!is_array($domain)) { - $domain = [$domain]; + + $domains = $envVars['domain']; + if (!is_array($domains)) { + $domains = [$domains]; } - foreach ($domain as $domain_name) { - if ($hostname === $domain_name) { - if (!defined('WP_ENVIRONMENT_TYPE')) { - define('WP_ENVIRONMENT_TYPE', preg_replace('/[^a-z]/', '', $envFile)); - } - define('WP_ENVIRONMENT_DOMAIN', $domain_name); - if (isset($env_vars['ssl'])) { - define('WP_ENVIRONMENT_SSL', (bool)$env_vars['ssl']); - } else { - define('WP_ENVIRONMENT_SSL', false); - } - if (isset($env_vars['path'])) { - define('WP_ENVIRONMENT_PATH', trim($env_vars['path'], '/')); - } - - /** - * Define WordPress Site URLs - */ - $protocol = (WP_ENVIRONMENT_SSL) ? 'https://' : 'http://'; - $path = (defined('WP_ENVIRONMENT_PATH')) ? '/' . trim(WP_ENVIRONMENT_PATH, '/') : ''; - - if (!defined('WP_SITEURL')) { - define('WP_SITEURL', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); - } - if (!defined('WP_HOME')) { - define('WP_HOME', $protocol . trim(WP_ENVIRONMENT_DOMAIN, '/') . $path); - } + + foreach ($domains as $domain) { + if ($this->isWildcard($domain) && $this->matchWildcardDomain($domain, $hostname)) { + $this->setEnvironmentType($environment); + break; + } elseif ($hostname === $domain) { + $this->setEnvironmentType($environment); break; } + $this->environmentHostname = $hostname; } } } - if (!defined('WP_ENVIRONMENT_TYPE')) { - throw new Studio24_MultiEnvConfig_Exception("Cannot determine current environment"); - } - if (!defined('WP_ENVIRONMENT_DOMAIN')) { - throw new Studio24_MultiEnvConfig_Exception("Cannot determine current environment domain, make sure this is set in wp-config.env.php"); - } - if (!defined('WP_ENVIRONMENT_SSL')) { - define('WP_ENVIRONMENT_SSL', false); + /** + * Whether a URL contains a wildcard pattern + * @param string $domain + * @return bool + */ + public function isWildcard(string $domain): bool + { + return (preg_match('/^\*/', $domain) === 1); } - if (WP_ENVIRONMENT_SSL && (!defined('FORCE_SSL_ADMIN'))) { - define('FORCE_SSL_ADMIN', true); + + /** + * Whether the hostname matches a wildcard domain + * @param string $domain + * @param string $hostname + * @return bool + */ + public function matchWildcardDomain(string $domain, string $hostname): bool + { + $match = '/' . str_replace('\*', '([^.]+)', preg_quote($domain, '/')) . '/'; + return (preg_match($match, $hostname, $m) === 1); } - // Define W3 Total Cache hostname - if (defined('WP_CACHE')) { - define('COOKIE_DOMAIN', WP_ENVIRONMENT_DOMAIN); + /** + * Return envUrls array for the current environment + * @return array|null + */ + public function getCurrentEnvUrls(): ?array + { + $settings = $this->getEnvironmentSettings(); + if (isset($settings[$this->getEnvironmentType()])) { + return $settings[$this->getEnvironmentType()]; + } + return null; } -} + /** + * Set WordPress constants from current environment + * @return void + * @throws MultiEnvConfig_Exception + */ + public function setWordPressConstants(): void + { + if (!$this->hasEnvironment()) { + throw new MultiEnvConfig_Exception('Current environment is not set, please ensure you have set the environment type correctly'); + } -/** - * Is this environment type allowed? - * @param string $name Environment type - * @return bool - */ -function s24_allowed_environment($name) -{ - $environments = [ - 'local', - 'development', - 'staging', - 'production', - ]; - return in_array((string) $name, $environments); -} + // Load URLs config for the environment + $envUrls = $this->getCurrentEnvUrls(); + if (null === $envUrls) { + throw new MultiEnvConfig_Exception(sprintf('Cannot detect current environment %s in wp-config.env.php', $this->getEnvironmentType())); + } + if (null !== $this->getEnvironmentHostname()) { + $hostname = $this->getEnvironmentHostname(); + } else { + $hostname = $envUrls['domain']; + } + $ssl = (bool) $envUrls['ssl']; + $protocol = ($ssl) ? 'https://' : 'http://'; + $path = !empty($envUrls['path']) ? '/' . trim($envUrls['path'], '/') : ''; -/** - * Custom exception for any fatal errors - */ -class Studio24_MultiEnvConfig_Exception extends Exception { } + // @see https://developer.wordpress.org/apis/wp-config-php/#wp-environment-type + define('WP_ENVIRONMENT_TYPE', $this->getEnvironmentType()); -s24_load_environment_config(); + // @see https://developer.wordpress.org/apis/wp-config-php/#wp-siteurl + if (!defined('WP_SITEURL')) { + define('WP_SITEURL', $protocol . rtrim($hostname, '/') . $path); + } + + // @see https://developer.wordpress.org/apis/wp-config-php/#blog-address-url + if (!defined('WP_HOME')) { + define('WP_HOME', $protocol . rtrim($hostname, '/') . $path); + } + + // @see https://developer.wordpress.org/apis/wp-config-php/#require-ssl-for-admin-and-logins + if (!defined('FORCE_SSL_ADMIN') && $ssl) { + define('FORCE_SSL_ADMIN', true); + } + + // @see https://developer.wordpress.org/apis/wp-config-php/#set-cookie-domain + if (!defined('COOKIE_DOMAIN')) { + define('COOKIE_DOMAIN', $hostname); + } + } + + +} /** * Load config */ +$bootstrap = new MultiEnvConfig_Bootstrap(); +$bootstrap->loadFromEnvironmentVariable(); +if (!$bootstrap->hasEnvironment()) { + $bootstrap->loadFromEnvFile(); +} +if (!$bootstrap->hasEnvironment()) { + $bootstrap->loadFromHostname(); +} +$bootstrap->setWordPressConstants(); + // 1st - Load default config require __DIR__ . '/wp-config.default.php'; // 2nd - Load config file for current environment -require __DIR__ . '/wp-config.' . WP_ENVIRONMENT_TYPE . '.php'; +require __DIR__ . '/wp-config.' . $bootstrap->getEnvironmentType() . '.php'; -// 3rd - Load local config file with any sensitive settings +// 3rd - Optionally load local-only config file with any sensitive settings if (file_exists(__DIR__ . '/.wp-config.php')) { require __DIR__ . '/.wp-config.php'; } From 3c3ecdfa691420da02617311c7fee78fe77521d4 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Fri, 30 Dec 2022 19:27:47 +0000 Subject: [PATCH 4/6] Remove Composer validation --- .github/workflows/php.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 8fc8043..6244004 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -32,9 +32,6 @@ jobs: - name: Check PHP version run: php -v - - name: Validate composer.json and composer.lock - run: composer validate - - name: Cache Composer packages id: composer-cache uses: actions/cache@v2 From 6eb6e7a8162280a36080dc1e3df2477debbb7da3 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Fri, 30 Dec 2022 19:38:16 +0000 Subject: [PATCH 5/6] Corrections for required PHP version, remove available constants, testing notes --- CONTRIBUTING.md | 24 ++++++++++++++++++++++++ docs/accessing-the-environment.md | 9 --------- docs/install.md | 2 +- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8d0057..9c58ee7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,30 @@ All contributions must be made on a branch and must be merged into the main bran All Pull Requests need at least one approval from the Studio 24 development team. +## Tests + +Simple PHPUnit tests can be added to the `tests/` folder. Please try to add tests for any future changes. + +## Continuous integration + +GitHub actions runs PHPUnit tests and PHPStan for code quality (checks `tests/*` and the `wp-config.load.php` file). You can set this up localy via: + +``` +composer install +``` + +Run PHPUnit via: + +``` +vendor/bin/phpunit +``` + +Run PHPStan via: + +``` +vendor/bin/phpstan analyse +``` + ## Creating new releases This repo uses [Release Please](https://github.com/marketplace/actions/release-please-action) to automatically create releases, based on [semantic versioning](https://semver.org/). diff --git a/docs/accessing-the-environment.md b/docs/accessing-the-environment.md index 8f20fbb..0423737 100644 --- a/docs/accessing-the-environment.md +++ b/docs/accessing-the-environment.md @@ -7,12 +7,3 @@ if the current environment cannot be determined. You can see example usage on the [wp_get_environment_type() reference](https://developer.wordpress.org/reference/functions/wp_get_environment_type/) documentation page. - -## Environment PHP constants - -The following PHP constants are available for the current environment: - -* `WP_ENVIRONMENT_TYPE` - environment type -* `WP_ENVIRONMENT_DOMAIN` - the environment hostname -* `WP_ENVIRONMENT_PATH` - the path to the WordPress installation -* `WP_ENVIRONMENT_SSL` - whether the current environment supports SSL \ No newline at end of file diff --git a/docs/install.md b/docs/install.md index b4e5684..068bd82 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,6 +1,6 @@ # Installation -Please note this requires PHP 5.6 or above. You should really be on PHP 7.4 at a minimum! +Please note this requires PHP 7.3 or above. 1. Download the required files via [wordpress-multi-env-config.zip](https://github.com/studio24/wordpress-multi-env-config/releases/latest/download/wordpress-multi-env-config.zip) 2. First make a backup of your existing `wp-config.php` file. From a9cb811ad61827638ee105b3c9a70b41e3689a39 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Fri, 30 Dec 2022 19:39:39 +0000 Subject: [PATCH 6/6] Typo, add link to GH actions --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c58ee7..7575ca7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,8 @@ Simple PHPUnit tests can be added to the `tests/` folder. Please try to add test ## Continuous integration -GitHub actions runs PHPUnit tests and PHPStan for code quality (checks `tests/*` and the `wp-config.load.php` file). You can set this up localy via: +[GitHub actions](https://github.com/studio24/wordpress-multi-env-config/actions) runs PHPUnit tests and PHPStan for code +quality (checks `tests/*` and the `wp-config.load.php` file). You can set this up locally via: ``` composer install