Repository: ddev/ddev
Pull request #6695 (2024-11-06 08:06:13)

feat: add WordPress WP_ENVIRONMENT_TYPE support to wp-config-ddev.php
## The Issue

`wp_get_environment_type()` returns `production` (which is expected as nothing is telling WordPress otherwise). This PR sets DDEV projects to identify themselves as a `local` environment.

## How This PR Solves The Issue

This PR defines your DDEV local site as a local environment using the `WP_ENVIRONMENT_TYPE` constant in the `wp-config-ddev.php` file. 

As outlined here: 
https://make.wordpress.org/core/2020/07/24/new-wp_get_environment_type-function-in-wordpress-5-5/

## Manual Testing Instructions

Execute `wp_get_environment_type()` anywhere and it should return `local`. 

## Automated Testing Overview

N/A

## Release/Deployment Notes

This would only affect WordPress projects using `wp_get_environment_type()` and expecting their local site to be one of the other values `development | staging | production`

github-actions[bot]: <!-- bot: nightly-link -->
Download the artifacts for this pull request:

* [all-ddev-executables.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150899231.zip)
* [ddev-macos-amd64.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150899361.zip)
* [ddev-macos-arm64.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150899530.zip)
* [ddev-linux-arm64.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150899690.zip)
* [ddev-linux-amd64.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150899844.zip)
* [ddev-windows-amd64.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150900008.zip)
* [ddev-windows-amd64-installer.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150900147.zip)
* [ddev-windows-arm64.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150900293.zip)
* [ddev-windows-arm64-installer.zip](https://nightly.link/ddev/ddev/actions/artifacts/2150900443.zip)

See [Testing a PR](https://ddev.readthedocs.io/en/latest/developers/building-contributing/#testing-a-pr).

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ddev/ddev/pull/6695)

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?ref=20241105_graham73may_adding_wordpress_environment_type_support&repo=349055178)
rfay: So the possible values are these?
> This would only affect WordPress projects using wp_get_environment_type() and expecting their local site to be one of the other values development | staging | production

Why "local" if the values might include "development" and not "local"?

Patch:
diff --git a/.buildkite/macos-colima.yml b/.buildkite/macos-colima.yml
index 999a9ddd4..14126c822 100644
--- a/.buildkite/macos-colima.yml
+++ b/.buildkite/macos-colima.yml
@@ -14,4 +14,5 @@
       DDEV_TEST_SHARE_CMD: "false"
       DDEV_RUN_GET_TESTS: "false"
       DOCKER_TYPE: "colima"
+      LIMA_SSH_PORT_FORWARDER: "true"
     parallelism: 1
diff --git a/.buildkite/macos-colima_vz.yml b/.buildkite/macos-colima_vz.yml
index fccaa2058..089e3f9eb 100644
--- a/.buildkite/macos-colima_vz.yml
+++ b/.buildkite/macos-colima_vz.yml
@@ -15,4 +15,5 @@
       DDEV_TEST_SHARE_CMD: "false"
       DDEV_RUN_GET_TESTS: "false"
       DOCKER_TYPE: "colima_vz"
+      LIMA_SSH_PORT_FORWARDER: "true"
     parallelism: 1
diff --git a/.buildkite/macos-lima.yml b/.buildkite/macos-lima.yml
index 1ba82bf18..4115ba90c 100644
--- a/.buildkite/macos-lima.yml
+++ b/.buildkite/macos-lima.yml
@@ -15,4 +15,5 @@
       DDEV_TEST_SHARE_CMD: "false"
       DDEV_RUN_GET_TESTS: "false"
       DOCKER_TYPE: "lima"
+      LIMA_SSH_PORT_FORWARDER: "true"
     parallelism: 1
diff --git a/.spellcheckwordlist.txt b/.spellcheckwordlist.txt
index f12f4dbe5..5eb442892 100644
--- a/.spellcheckwordlist.txt
+++ b/.spellcheckwordlist.txt
@@ -19,6 +19,7 @@ CMD
 CMS
 CMSes
 Colima
+Contao
 CPUs
 CTRL
 CentOS
diff --git a/docs/content/users/configuration/config.md b/docs/content/users/configuration/config.md
index 6d36fa0d6..a3e498e06 100644
--- a/docs/content/users/configuration/config.md
+++ b/docs/content/users/configuration/config.md
@@ -392,6 +392,8 @@ Node.js version for the web container’s “system” version. [`n`](https://ww
 
 There is no need to reconfigure `nodejs_version` unless you want a version other than the version already specified, which will be the default version at the time the project was configured.
 
+Note that specifying any non-default Node.js version will cause DDEV to download and install that version when running `ddev start` the first time on a project. If optimizing first-time startup speed (as in Continuous Integration) is your biggest concern, consider using the default version of Node.js.
+
 | Type | Default | Usage
 | -- | -- | --
 | :octicons-file-directory-16: project | current LTS version | any [node version](https://www.npmjs.com/package/n#specifying-nodejs-versions), like `16`, `18.2`, `18.19.2`, etc.
diff --git a/docs/content/users/quickstart.md b/docs/content/users/quickstart.md
index 212e0804c..48565a1b4 100644
--- a/docs/content/users/quickstart.md
+++ b/docs/content/users/quickstart.md
@@ -70,6 +70,54 @@ Please note that you will need to change the PHP version to 7.4 to be able to wo
     ddev launch
     ```
 
+## Contao
+
+Further information on the DDEV procedure can also be found in the [Contao documentation](https://docs.contao.org/manual/en/guides/local-installation/ddev/).
+
+=== "Composer"
+
+    ```bash
+    mkdir my-contao-site && cd my-contao-site
+    ddev config --project-type=php --docroot=public --webserver-type=apache-fpm --php-version=8.2
+    ddev composer create contao/managed-edition:5.3
+    
+    # Set DATABASE_URL and MAILER_DSN in .env.local
+    ddev dotenv set .env.local --database-url=mysql://db:db@db:3306/db --mailer-dsn=smtp://localhost:1025
+
+    # Create the database
+    ddev exec contao-console contao:migrate --no-interaction
+    
+    # Create backend user
+    ddev exec contao-console contao:user:create --username=admin --name=Administrator --email=admin@example.com --language=en --password=Password123 --admin
+    
+    # Access the administration area
+    ddev launch contao
+    ```
+
+=== "Contao Manager"
+
+    Like most PHP projects, Contao could be installed and updated with Composer. The [Contao Manager](https://docs.contao.org/manual/en/installation/contao-manager/) is a tool that provides a graphical user interface to manage a Contao installation.
+
+    ```bash
+    mkdir my-contao-site && cd my-contao-site
+    ddev config --project-type=php --docroot=public --webserver-type=apache-fpm --php-version=8.2
+
+    # set DATABASE_URL and MAILER_DSN in .env.local
+    ddev dotenv set .env.local --database-url=mysql://db:db@db:3306/db --mailer-dsn=smtp://localhost:1025
+
+    # Download the Contao Manager
+    ddev start
+    ddev exec "wget -O public/contao-manager.phar.php https://download.contao.org/contao-manager/stable/contao-manager.phar"
+
+    # Follow the further steps within the Contao Manager
+    ddev launch contao-manager.phar.php
+    ```
+
+=== "Demo Website"
+
+    The [Contao demo website](https://demo.contao.org/) is maintained for the currently supported Contao versions and can be [optionally installed](https://github.com/contao/contao-demo). 
+    Via the Contao Manager you can simply select this option during the first installation.
+
 ## Craft CMS
 
 Start a new [Craft CMS](https://craftcms.com) project or retrofit an existing one.
@@ -342,7 +390,7 @@ Start a new [Kirby CMS](https://getkirby.com) project or use an existing one.
     mkdir my-kirby-site && cd my-kirby-site
 
     # Set up the DDEV environment
-    ddev config --omit-containers=db
+    ddev config --omit-containers=db --webserver-type=apache-fpm
 
     # Spin up the project and install the Kirby Starterkit
     ddev start
@@ -361,7 +409,7 @@ Start a new [Kirby CMS](https://getkirby.com) project or use an existing one.
     cd my-kirby-site
 
     # Set up the DDEV environment
-    ddev config --omit-containers=db
+    ddev config --omit-containers=db --webserver-type=apache-fpm
 
     # Spin up the project
     ddev start
diff --git a/pkg/ddevapp/wordpress/wp-config-ddev.php b/pkg/ddevapp/wordpress/wp-config-ddev.php
index a6c0a600a..29a4e2c86 100644
--- a/pkg/ddevapp/wordpress/wp-config-ddev.php
+++ b/pkg/ddevapp/wordpress/wp-config-ddev.php
@@ -28,6 +28,9 @@ if ( getenv( 'IS_DDEV_PROJECT' ) == 'true' ) {
 	/** Enable debug */
 	defined( 'WP_DEBUG' ) || define( 'WP_DEBUG', true );
 
+	/** WordPress environment type. */
+	defined( 'WP_ENVIRONMENT_TYPE' ) || define( 'WP_ENVIRONMENT_TYPE', 'local' );
+
 	/**
 	 * Set WordPress Database Table prefix if not already set.
 	 *
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 42797481d..35e26a966 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -419,3 +419,22 @@ func GetTimezone(path string) (string, error) {
 	}
 	return timezone, nil
 }
+
+// SubtractSlices removes elements of slice b from slice a.
+func SubtractSlices(a, b []string) []string {
+	// Create a map to keep track of elements in slice b for quick lookup.
+	bMap := make(map[string]bool)
+	for _, elem := range b {
+		bMap[elem] = true
+	}
+
+	// Collect elements from a that are not in b.
+	var result []string
+	for _, elem := range a {
+		if _, found := bMap[elem]; !found {
+			result = append(result, elem)
+		}
+	}
+
+	return result
+}


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index fd345d6fd..f17d00264 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -124,8 +124,26 @@ jobs:
       - name: Override environment variables for push-pull-test-platforms
         run: |
           echo "MAKE_TARGET=test" >> $GITHUB_ENV
-          echo "TESTARGS=-failfast -run '(TestDdevFullSite.*|TestDdevImportFiles|TestDdevAllDatabases|TestComposerCreateCmd|Test.*(Push|Pull)|TestAutocomplet)'" >> $GITHUB_ENV
           echo "GOTEST_SHORT=" >> $GITHUB_ENV
+          # In push-pull-test-platforms we want to run all tests
+          # that limit their scope when running with any value of GOTEST_SHORT
+          TP=(
+            "TestAppdirAlreadyInUse"
+            "TestAutocomplet"
+            "TestAvailableUpdates"
+            "TestComposerCreateCmd"
+            "TestConfigDatabaseVersion"
+            "TestDdevAllDatabases"
+            "TestDdevFullSite.*"
+            "TestDdevImportFiles"
+            "TestDdevXdebugEnabled"
+            "TestDdevXhprofEnabled"
+            "TestPHPConfig"
+            "Test.*(Push|Pull)"
+          )
+          TESTARGS="-failfast -run '($(IFS="|"; echo "${TP[*]}"))'"
+          echo "pull-push-test-platforms TESTARGS=$TESTARGS"
+          echo "TESTARGS=${TESTARGS}" >> $GITHUB_ENV
         if: ${{ matrix.pull-push-test-platforms }}
 
       - name: Load 1password secret(s) for push-pull-test-platforms
diff --git a/pkg/ddevapp/config_test.go b/pkg/ddevapp/config_test.go
index d64edd071..41afdde3f 100644
--- a/pkg/ddevapp/config_test.go
+++ b/pkg/ddevapp/config_test.go
@@ -846,12 +846,11 @@ func TestPHPConfig(t *testing.T) {
 	})
 
 	// Most of the time there's no reason to do all versions of PHP
-	phpKeys := []string{}
+	// so we can subtract those if GOTEST_SHORT==""
+	phpKeys := nodeps.GetValidPHPVersions()
 	exclusions := []string{nodeps.PHP56, nodeps.PHP70, nodeps.PHP71, nodeps.PHP72, nodeps.PHP73, nodeps.PHP74, nodeps.PHP80, nodeps.PHP81}
-	for k := range nodeps.ValidPHPVersions {
-		if os.Getenv("GOTEST_SHORT") != "" && !nodeps.ArrayContainsString(exclusions, k) {
-			phpKeys = append(phpKeys, k)
-		}
+	if os.Getenv("GOTEST_SHORT") != "" {
+		phpKeys = util.SubtractSlices(phpKeys, exclusions)
 	}
 	sort.Strings(phpKeys)
 
diff --git a/pkg/ddevapp/ddevapp_test.go b/pkg/ddevapp/ddevapp_test.go
index 752502639..8f32c9695 100644
--- a/pkg/ddevapp/ddevapp_test.go
+++ b/pkg/ddevapp/ddevapp_test.go
@@ -952,20 +952,15 @@ func TestDdevXdebugEnabled(t *testing.T) {
 
 	testcommon.ClearDockerEnv()
 
-	// Most of the time there's no reason to do all versions of PHP
-	phpKeys := []string{}
-	exclusions := []string{"5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0"}
-	for k := range nodeps.ValidPHPVersions {
-		// TODO: Remove when xdebug available for php8.4
-		if k == nodeps.PHP84 {
-			continue
-		}
-		if os.Getenv("GOTEST_SHORT") != "" && !nodeps.ArrayContainsString(exclusions, k) {
-			phpKeys = append(phpKeys, k)
-		}
-	}
+	// Default to testing all versions if GOTEST_SHORT is not set
+	phpKeys := nodeps.GetValidPHPVersions()
+
+	// TODO: Remove the PHP 8.4 exclusion when php8.4-xhprof is available
+	exclusions := []string{nodeps.PHP84}
+	phpKeys = util.SubtractSlices(phpKeys, exclusions)
 	sort.Strings(phpKeys)
-	// Most of the time we can just test with the default PHP version
+
+	// Test only the default version if GOTEST_SHORT is set
 	if os.Getenv("GOTEST_SHORT") != "" {
 		phpKeys = []string{nodeps.PHPDefault}
 	}
@@ -1121,20 +1116,13 @@ func TestDdevXhprofEnabled(t *testing.T) {
 
 	// Does not work with php5.6 anyway (SEGV), for resource conservation
 	// skip older unsupported versions
-	phpKeys := []string{}
-	exclusions := []string{"5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0"}
-	for k := range nodeps.ValidPHPVersions {
-		// TODO: Remove when xhprof available for php8.4
-		if k == nodeps.PHP84 {
-			continue
-		}
-		if !nodeps.ArrayContainsString(exclusions, k) {
-			phpKeys = append(phpKeys, k)
-		}
-	}
+	phpKeys := nodeps.GetValidPHPVersions()
+	// TODO: Remove the PHP 8.4 exclusion when php8.4-xhprof is available
+	exclusions := []string{nodeps.PHP56, nodeps.PHP84}
+	phpKeys = util.SubtractSlices(phpKeys, exclusions)
 	sort.Strings(phpKeys)
 
-	// Most of the time we can just test with the default PHP version
+	// If GOTESt_SHORT is set, we'll just use the default version instead
 	if os.Getenv("GOTEST_SHORT") != "" {
 		phpKeys = []string{nodeps.PHPDefault}
 	}
@@ -1658,10 +1646,12 @@ func TestDdevAllDatabases(t *testing.T) {
 
 	dbVersions := nodeps.GetValidDatabaseVersions()
 	// Bug: PostgreSQL 9 doesn't work with snapshot restore, see https://github.com/ddev/ddev/issues/3583
-	dbVersions = nodeps.RemoveItemFromSlice(dbVersions, "postgres:9")
+	exclusions := []string{"postgres:9"}
+	dbVersions = util.SubtractSlices(dbVersions, exclusions)
+
 	//Use a smaller list if GOTEST_SHORT
 	if os.Getenv("GOTEST_SHORT") != "" {
-		dbVersions = []string{"postgres:14", "mariadb:10.11", "mariadb:10.6", "mariadb:10.4", "mysql:8.0", "mysql:5.7"}
+		dbVersions = []string{"postgres:17", "mariadb:10.11", "mariadb:10.6", "mysql:8.0", "mysql:5.7"}
 		t.Logf("Using limited set of database servers because GOTEST_SHORT is set (%v)", dbVersions)
 	}
 
diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go
index 8139b6258..a36a93023 100644
--- a/pkg/util/utils_test.go
+++ b/pkg/util/utils_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"sort"
 	"strings"
 	"testing"
 
@@ -230,3 +231,59 @@ func TestGetTimezone(t *testing.T) {
 		})
 	}
 }
+
+// compareSlices checks if two slices are equal after sorting them.
+func compareSlices(a, b []string) bool {
+	if len(a) != len(b) {
+		return false
+	}
+
+	// Sort both slices before comparing
+	sort.Strings(a)
+	sort.Strings(b)
+
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// TestSubtractSlices does simple test of SubtractSlices
+func TestSubtractSlices(t *testing.T) {
+	tests := []struct {
+		name     string
+		a        []string
+		b        []string
+		expected []string
+	}{
+		{
+			name:     "No overlap",
+			a:        []string{"apple", "banana", "cherry"},
+			b:        []string{"date", "fig"},
+			expected: []string{"apple", "banana", "cherry"},
+		},
+		{
+			name:     "Partial overlap",
+			a:        []string{"apple", "banana", "cherry"},
+			b:        []string{"banana"},
+			expected: []string{"apple", "cherry"},
+		},
+		{
+			name:     "Complete overlap",
+			a:        []string{"apple", "banana"},
+			b:        []string{"apple", "banana"},
+			expected: []string{},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := util.SubtractSlices(tt.a, tt.b)
+			if !compareSlices(result, tt.expected) {
+				t.Errorf("SubtractSlices(%v, %v) = %v; want %v", tt.a, tt.b, result, tt.expected)
+			}
+		})
+	}
+}