From 403c547d4c66dc73a96b158dbc31246b9e61757a Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:28:01 +1000 Subject: [PATCH 01/42] Add wp-env as an alternative local development environment Introduces @wordpress/env alongside the existing Docker setup, providing a simpler way to run the multi-network WordCamp.org environment locally. Key changes: - Create .wp-env.json with multisite config, plugin slugs, and mappings - Add afterStart lifecycle script to seed networks and sites via WP-CLI - Make sunrise files detect network from HTTP_HOST instead of static constants, enabling wp-env (which uses a single wp-config.php) to serve all three networks correctly - Update phpunit-bootstrap.php to detect wp-env paths - Move wordpress.org plugins/themes from composer to wp-env management - Add convenience yarn scripts for wp-env operations Co-Authored-By: Claude Opus 4.6 --- .gitignore | 6 + .wp-env.json | 89 ++++++++++ .wp-env/setup.mjs | 183 ++++++++++++++++++++ composer.json | 18 -- package.json | 7 + phpunit-bootstrap.php | 29 +++- public_html/wp-content/sunrise-events.php | 13 +- public_html/wp-content/sunrise-wordcamp.php | 5 +- public_html/wp-content/sunrise.php | 5 +- 9 files changed, 324 insertions(+), 31 deletions(-) create mode 100644 .wp-env.json create mode 100644 .wp-env/setup.mjs diff --git a/.gitignore b/.gitignore index ffbd5f9501..156b4360e9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,12 @@ docker-compose.override.yaml +# +# wp-env +# +.wp-env-home + + # # Misc # diff --git a/.wp-env.json b/.wp-env.json new file mode 100644 index 0000000000..dfe8fa339c --- /dev/null +++ b/.wp-env.json @@ -0,0 +1,89 @@ +{ + "core": null, + "phpVersion": "8.1", + "multisite": true, + "port": 80, + "testsPort": 8889, + "plugins": [ + "akismet", + "classic-editor", + "custom-content-width", + "edit-flow", + "email-post-changes", + "gutenberg", + "jetpack", + "liveblog", + "public-post-preview", + "pwa", + "wordpress-importer", + "wp-cldr", + "wp-super-cache" + ], + "themes": [ + "p2", + "twentyten", + "twentytwentytwo", + "twentytwentythree", + "twentytwentyfour" + ], + "config": { + "IS_WORDCAMP_NETWORK": true, + "WORDCAMP_ENVIRONMENT": "local", + "WP_ENVIRONMENT_TYPE": "local", + "SUNRISE": true, + "SUBDOMAIN_INSTALL": true, + "DOMAIN_CURRENT_SITE": "wordcamp.test", + "PATH_CURRENT_SITE": "/", + "SITE_ID_CURRENT_SITE": 1, + "BLOG_ID_CURRENT_SITE": 5, + "WORDCAMP_NETWORK_ID": 1, + "WORDCAMP_ROOT_BLOG_ID": 5, + "EVENTS_NETWORK_ID": 2, + "EVENTS_ROOT_BLOG_ID": 47, + "CAMPUS_NETWORK_ID": 3, + "CAMPUS_ROOT_BLOG_ID": 47, + "NOBLOGREDIRECT": "http://central.wordcamp.test", + "WP_DEBUG": true, + "WP_DEBUG_LOG": true, + "WP_DEBUG_DISPLAY": true, + "SAVEQUERIES": true, + "SCRIPT_DEBUG": false, + "JETPACK_DEV_DEBUG": true, + "DISALLOW_UNFILTERED_HTML": true, + "WORDCAMP_QBO_HMAC_KEY": "localhmac", + "EMAIL_DEVELOPER_NOTIFICATIONS": "developers@example.test", + "EMAIL_CENTRAL_SUPPORT": "support@central.wordcamp.test" + }, + "mappings": { + "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", + "wp-content/plugins": "./public_html/wp-content/plugins", + "wp-content/sunrise.php": "./public_html/wp-content/sunrise.php", + "wp-content/sunrise-wordcamp.php": "./public_html/wp-content/sunrise-wordcamp.php", + "wp-content/sunrise-events.php": "./public_html/wp-content/sunrise-events.php", + "wp-content/themes/campsite-2017": "./public_html/wp-content/themes/campsite-2017", + "wp-content/themes/wordcamp-base-v2": "./public_html/wp-content/themes/wordcamp-base-v2", + "wp-content/themes/wporg-events-2023": "./public_html/wp-content/themes/wporg-events-2023", + "wp-content/themes/wporg-flagship-landing": "./public_html/wp-content/themes/wporg-flagship-landing" + }, + "lifecycleScripts": { + "afterStart": "node .wp-env/setup.mjs" + }, + "env": { + "tests": { + "config": { + "WP_TESTS_MULTISITE": 1 + }, + "mappings": { + "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", + "wp-content/plugins": "./public_html/wp-content/plugins", + "wp-content/sunrise.php": "./public_html/wp-content/sunrise.php", + "wp-content/sunrise-wordcamp.php": "./public_html/wp-content/sunrise-wordcamp.php", + "wp-content/sunrise-events.php": "./public_html/wp-content/sunrise-events.php", + "phpunit.xml.dist": "./phpunit.xml.dist", + "phpunit-bootstrap.php": "./phpunit-bootstrap.php", + "phpunit-database-testcase.php": "./phpunit-database-testcase.php", + "vendor": "./public_html/wp-content/mu-plugins/vendor" + } + } + } +} diff --git a/.wp-env/setup.mjs b/.wp-env/setup.mjs new file mode 100644 index 0000000000..e722d001c1 --- /dev/null +++ b/.wp-env/setup.mjs @@ -0,0 +1,183 @@ +/** + * wp-env afterStart lifecycle script. + * + * Creates the multi-network structure that mirrors production: + * - Network 1: WordCamp (wordcamp.test) + * - Network 2: Events (events.wordpress.test) + * - Network 3: Campus (campus.wordpress.test) + * + * Idempotent — safe to run on every `wp-env start`. + */ + +import { execSync } from 'node:child_process'; + +/** + * Run a WP-CLI command inside the wp-env development container. + * + * @param {string} cmd WP-CLI command (without the `wp` prefix). + * @return {string} Command output. + */ +function wp( cmd ) { + return execSync( `npx wp-env run cli wp ${ cmd }`, { + encoding: 'utf-8', + stdio: [ 'pipe', 'pipe', 'pipe' ], + } ).trim(); +} + +/** + * Run a raw SQL query via WP-CLI. + * + * @param {string} sql SQL query. + * @return {string} Query output. + */ +function sql( sql ) { + return wp( `db query "${ sql.replace( /"/g, '\\"' ) }"` ); +} + +// Check if setup has already run by counting sites. +const siteCount = parseInt( wp( 'site list --format=count' ), 10 ); + +if ( siteCount > 2 ) { + console.log( `Setup already complete (${ siteCount } sites exist). Skipping.` ); + process.exit( 0 ); +} + +console.log( 'Setting up WordCamp.org multi-network environment...' ); + +// ────────────────────────────────────────────── +// 1. Create additional networks via raw SQL. +// wp-env creates network 1 automatically. +// ────────────────────────────────────────────── + +console.log( 'Creating Events network (ID 2)...' ); +sql( "INSERT IGNORE INTO wp_site (id, domain, path) VALUES (2, 'events.wordpress.test', '/')" ); +sql( "INSERT IGNORE INTO wp_sitemeta (site_id, meta_key, meta_value) VALUES (2, 'site_name', 'Events (local)')" ); +sql( "INSERT IGNORE INTO wp_sitemeta (site_id, meta_key, meta_value) VALUES (2, 'admin_email', 'admin@wordcamp.test')" ); + +console.log( 'Creating Campus network (ID 3)...' ); +sql( "INSERT IGNORE INTO wp_site (id, domain, path) VALUES (3, 'campus.wordpress.test', '/')" ); +sql( "INSERT IGNORE INTO wp_sitemeta (site_id, meta_key, meta_value) VALUES (3, 'site_name', 'Campus (local)')" ); +sql( "INSERT IGNORE INTO wp_sitemeta (site_id, meta_key, meta_value) VALUES (3, 'admin_email', 'admin@wordcamp.test')" ); + +// ────────────────────────────────────────────── +// 2. Create sites with specific blog IDs. +// Using SQL to guarantee exact IDs. +// ────────────────────────────────────────────── + +const sites = [ + { + blogId: 5, + domain: 'central.wordcamp.test', + path: '/', + siteId: 1, + title: 'WordCamp Central', + }, + { + blogId: 47, + domain: 'events.wordpress.test', + path: '/', + siteId: 2, + title: 'Events', + }, + { + blogId: 48, + domain: 'events.wordpress.test', + path: '/rome/2024/training/', + siteId: 2, + title: 'WordPress Training Day Rome 2024', + }, + { + blogId: 49, + domain: 'narnia.wordcamp.test', + path: '/2026/', + siteId: 1, + title: 'WordCamp Narnia 2026', + }, +]; + +for ( const site of sites ) { + console.log( `Creating site ${ site.domain }${ site.path } (blog_id ${ site.blogId })...` ); + + // Check if the blog_id already exists. + const exists = sql( + `SELECT blog_id FROM wp_blogs WHERE blog_id = ${ site.blogId }` + ); + + if ( exists.includes( String( site.blogId ) ) ) { + console.log( ` Blog ID ${ site.blogId } already exists, skipping.` ); + continue; + } + + const now = new Date().toISOString().slice( 0, 19 ).replace( 'T', ' ' ); + + sql( + `INSERT INTO wp_blogs (blog_id, site_id, domain, path, registered, last_updated, public) VALUES (${ site.blogId }, ${ site.siteId }, '${ site.domain }', '${ site.path }', '${ now }', '${ now }', 1)` + ); + + // Create the site's tables by switching to it and running an option insert. + // WP will auto-create tables when the blog is accessed. + const prefix = `wp_${ site.blogId }_`; + + // Create minimal options table. + sql( + `CREATE TABLE IF NOT EXISTS ${ prefix }options LIKE wp_options` + ); + sql( + `INSERT IGNORE INTO ${ prefix }options (option_name, option_value, autoload) VALUES ('siteurl', 'http://${ site.domain }${ site.path }', 'yes')` + ); + sql( + `INSERT IGNORE INTO ${ prefix }options (option_name, option_value, autoload) VALUES ('home', 'http://${ site.domain }${ site.path }', 'yes')` + ); + sql( + `INSERT IGNORE INTO ${ prefix }options (option_name, option_value, autoload) VALUES ('blogname', '${ site.title }', 'yes')` + ); + + // Create other required tables. + const coreTables = [ + 'posts', 'postmeta', 'comments', 'commentmeta', 'links', + 'terms', 'termmeta', 'term_taxonomy', 'term_relationships', + ]; + + for ( const table of coreTables ) { + sql( `CREATE TABLE IF NOT EXISTS ${ prefix }${ table } LIKE wp_${ table }` ); + } + + console.log( ` Created site: ${ site.title }` ); +} + +// ────────────────────────────────────────────── +// 3. Set admin password to 'password'. +// ────────────────────────────────────────────── + +console.log( 'Setting admin password...' ); +try { + wp( 'user update admin --user_pass=password --skip-email' ); +} catch { + // User may not be named 'admin'; try ID 1. + wp( 'user update 1 --user_pass=password --skip-email' ); +} + +// ────────────────────────────────────────────── +// 4. Create placeholder build directories for +// mu-plugins/blocks to prevent errors. +// ────────────────────────────────────────────── + +console.log( 'Creating placeholder build directories...' ); +try { + execSync( 'npx wp-env run cli -- mkdir -p /var/www/html/wp-content/mu-plugins/blocks/build', { + encoding: 'utf-8', + stdio: 'pipe', + } ); +} catch { + // Directory may already exist. +} + +console.log( 'Setup complete!' ); +console.log( '' ); +console.log( 'Sites available:' ); +console.log( ' http://central.wordcamp.test/' ); +console.log( ' http://narnia.wordcamp.test/2026/' ); +console.log( ' http://events.wordpress.test/' ); +console.log( ' http://events.wordpress.test/rome/2024/training/' ); +console.log( '' ); +console.log( 'Login: admin / password' ); diff --git a/composer.json b/composer.json index f5ad46ed73..2cc5e450ec 100644 --- a/composer.json +++ b/composer.json @@ -124,7 +124,6 @@ "spatie/phpunit-watcher": "^1.23", "yoast/phpunit-polyfills": "^4.0", "composer/installers": "^2.2", - "wpackagist-plugin/akismet": "*", "wordpress-plugin/bbpress": "2.6.*", "wordpress-plugin/camptix-bd-payments": "1.2", "wordpress-plugin/camptix-mercadopago": "1.0.6", @@ -134,27 +133,10 @@ "wpackagist-plugin/camptix-trustpay": "*", "wordpress-plugin/camptix-paystack": "1.0.0", "wordpress-plugin/camptix-paynow": "1.0.4", - "wpackagist-plugin/classic-editor": "dev-trunk", - "wpackagist-plugin/custom-content-width": "*", - "wpackagist-plugin/edit-flow": "*", - "wpackagist-plugin/email-post-changes": "dev-trunk", - "wpackagist-plugin/gutenberg": "*", - "wpackagist-plugin/jetpack": "*", - "wpackagist-plugin/liveblog": "*", - "wpackagist-plugin/public-post-preview": "*", - "wpackagist-plugin/pwa": "*", "wpackagist-plugin/supportflow": "dev-trunk", "wpackagist-plugin/tagregator": "dev-trunk", - "wpackagist-plugin/wordpress-importer": "*", - "wpackagist-plugin/wp-cldr": "*", - "wpackagist-plugin/wp-super-cache": "*", "wordpress-meta/wporg-profiles-wp-activity-notifier": "1.1", "wporg/wporg-mu-plugins": "dev-build", - "wpackagist-theme/p2": "*", - "wpackagist-theme/twentyten": "*", - "wpackagist-theme/twentytwentytwo": "*", - "wpackagist-theme/twentytwentythree": "*", - "wpackagist-theme/twentytwentyfour": "*", "quickbooks/v3-php-sdk": "*" }, "scripts": { diff --git a/package.json b/package.json index bb700d3303..80bd5fc182 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,16 @@ "public_html/wp-content/themes/wporg-flagship-landing" ], "devDependencies": { + "@wordpress/env": "^10.0.0", "@wordpress/eslint-plugin": "24.1.0", "@wordpress/jest-preset-default": "12.39.0" }, + "scripts": { + "wp-env:start": "wp-env start", + "wp-env:stop": "wp-env stop", + "wp-env:test:php": "wp-env run tests-cli --env-cwd=/var/www/html phpunit -c phpunit.xml.dist", + "wp-env:destroy": "wp-env destroy" + }, "browserslist": [ "extends @wordpress/browserslist-config" ] diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php index 1e9de559c9..bcbd2916c8 100644 --- a/phpunit-bootstrap.php +++ b/phpunit-bootstrap.php @@ -1,8 +1,15 @@ id; $path = stripslashes( $_SERVER['REQUEST_URI'] ); @@ -80,7 +83,7 @@ function set_network_and_site() { list( $path ) = explode( '?', $path ); - $current_blog = get_site_by_path( DOMAIN_CURRENT_SITE, $path, 3 ); + $current_blog = get_site_by_path( $host, $path, 3 ); } elseif ( CAMPUS_NETWORK_ID === $site_id && @@ -92,9 +95,9 @@ function set_network_and_site() { list( $path ) = explode( '?', $path ); - $current_blog = get_site_by_path( DOMAIN_CURRENT_SITE, $path, 2 ); + $current_blog = get_site_by_path( $host, $path, 2 ); } else { - $current_blog = WP_Site::get_instance( BLOG_ID_CURRENT_SITE ); // The Root site constant defined in wp-config.php. + $current_blog = WP_Site::get_instance( $current_site->blog_id ); } if ( ! $current_blog ) { diff --git a/public_html/wp-content/sunrise-wordcamp.php b/public_html/wp-content/sunrise-wordcamp.php index a1ddc3e202..b9b9fc3ca0 100644 --- a/public_html/wp-content/sunrise-wordcamp.php +++ b/public_html/wp-content/sunrise-wordcamp.php @@ -136,8 +136,11 @@ function ( array $settings ): array { function set_network_and_site( object $site ) { global $current_site, $current_blog, $blog_id, $site_id, $domain, $path, $public; + $host = strtolower( strtok( $_SERVER['HTTP_HOST'] ?? '', ':' ) ); + $network_id = \WordCamp\Sunrise\get_domain_network_id( $host ); + // Originally WP referred to networks as "sites" and sites as "blogs". - $current_site = WP_Network::get_instance( WORDCAMP_NETWORK_ID ); + $current_site = WP_Network::get_instance( $network_id ); $site_id = $current_site->id; $path = stripslashes( $_SERVER['REQUEST_URI'] ); $current_blog = WP_Site::get_instance( $site->blog_id ); diff --git a/public_html/wp-content/sunrise.php b/public_html/wp-content/sunrise.php index 34427371e1..52656dd005 100644 --- a/public_html/wp-content/sunrise.php +++ b/public_html/wp-content/sunrise.php @@ -87,7 +87,10 @@ * Load the sunrise file for the current network. */ function load_network_sunrise() { - switch ( SITE_ID_CURRENT_SITE ) { + $host = strtolower( strtok( $_SERVER['HTTP_HOST'] ?? '', ':' ) ); + $network_id = get_domain_network_id( $host ); + + switch ( $network_id ) { case CAMPUS_NETWORK_ID: // Intentional Fall through. Load Events plugins for now. case EVENTS_NETWORK_ID: From 6e829a5586153dd5c4903ab017c189f4caff9f94 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:40:57 +1000 Subject: [PATCH 02/42] Fix CI: define WORDCAMP_ENVIRONMENT in PHPUnit bootstrap The sunrise.php change to use HTTP_HOST-based network detection calls get_domain_network_id() which relies on get_top_level_domain(), and that function references the WORDCAMP_ENVIRONMENT constant. This constant was not defined in the test bootstrap, causing failures. Co-Authored-By: Claude Opus 4.6 --- phpunit-bootstrap.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php index bcbd2916c8..15f3e91912 100644 --- a/phpunit-bootstrap.php +++ b/phpunit-bootstrap.php @@ -12,6 +12,7 @@ require_once $vendor_autoload; } +const WORDCAMP_ENVIRONMENT = 'local'; const WORDCAMP_NETWORK_ID = 1; const WORDCAMP_ROOT_BLOG_ID = 5; const EVENTS_NETWORK_ID = 2; From 3fbe57f7f388bfd2b32176bfb3139f8f21fd5f21 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:41:48 +1000 Subject: [PATCH 03/42] Move camptix-* plugins from Composer to wp-env Remove all 8 CampTix payment gateway plugins from composer.json (both require-dev entries and custom SVN package definitions) and add them to .wp-env.json using their wordpress.org plugin slugs. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 8 ++++++ composer.json | 70 ++++++++++++--------------------------------------- 2 files changed, 24 insertions(+), 54 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index dfe8fa339c..1e83da4530 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -6,15 +6,23 @@ "testsPort": 8889, "plugins": [ "akismet", + "bd-payments-camptix", + "camptix-mercadopago", + "camptix-pagseguro", + "camptix-payfast-gateway", + "camptix-trustcard", + "camptix-trustpay", "classic-editor", "custom-content-width", "edit-flow", "email-post-changes", + "gateway-camptix-paynow-payment", "gutenberg", "jetpack", "liveblog", "public-post-preview", "pwa", + "tbz-camptix-paystack", "wordpress-importer", "wp-cldr", "wp-super-cache" diff --git a/composer.json b/composer.json index 2cc5e450ec..a4daa86d26 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,21 @@ }, "extra": { "installer-paths": { - "public_html/wp-content/themes/{$name}": ["wporg/wporg-parent-2021"], - "public_html/wp-content/mu-plugins-private/{$name}/tmp": ["wporg/wporg-mu-plugins"], - "public_html/wp-content/plugins/{$name}/": ["type:wordpress-plugin"], - "public_html/wp-content/mu-plugins/{$name}/": ["type:wordpress-muplugin"], - "public_html/wp-content/themes/{$name}/": ["type:wordpress-theme"] + "public_html/wp-content/themes/{$name}": [ + "wporg/wporg-parent-2021" + ], + "public_html/wp-content/mu-plugins-private/{$name}/tmp": [ + "wporg/wporg-mu-plugins" + ], + "public_html/wp-content/plugins/{$name}/": [ + "type:wordpress-plugin" + ], + "public_html/wp-content/mu-plugins/{$name}/": [ + "type:wordpress-muplugin" + ], + "public_html/wp-content/themes/{$name}/": [ + "type:wordpress-theme" + ] } }, "repositories": [ @@ -57,46 +67,6 @@ "reference": "branches/2.6/" } }, - { - "name": "wordpress-plugin/camptix-bd-payments", - "type": "wordpress-plugin", - "version": "1.2", - "source": { - "type": "svn", - "url": "https://plugins.svn.wordpress.org/bd-payments-camptix/", - "reference": "tags/1.2/" - } - }, - { - "name": "wordpress-plugin/camptix-mercadopago", - "type": "wordpress-plugin", - "version": "1.0.6", - "source": { - "type": "svn", - "url": "https://plugins.svn.wordpress.org/camptix-mercadopago/", - "reference": "tags/1.0.6/" - } - }, - { - "name": "wordpress-plugin/camptix-paystack", - "type": "wordpress-plugin", - "version": "1.0.0", - "source": { - "type": "svn", - "url": "https://plugins.svn.wordpress.org/tbz-camptix-paystack/", - "reference": "tags/1.0.0/" - } - }, - { - "name": "wordpress-plugin/camptix-paynow", - "type": "wordpress-plugin", - "version": "1.0.4", - "source": { - "type": "svn", - "url": "https://plugins.svn.wordpress.org/gateway-camptix-paynow-payment/", - "reference": "tags/1.0.4/" - } - }, { "name": "wordpress-meta/wporg-profiles-wp-activity-notifier", "type": "wordpress-plugin", @@ -125,14 +95,6 @@ "yoast/phpunit-polyfills": "^4.0", "composer/installers": "^2.2", "wordpress-plugin/bbpress": "2.6.*", - "wordpress-plugin/camptix-bd-payments": "1.2", - "wordpress-plugin/camptix-mercadopago": "1.0.6", - "wpackagist-plugin/camptix-pagseguro": "*", - "wpackagist-plugin/camptix-payfast-gateway": "*", - "wpackagist-plugin/camptix-trustcard": "*", - "wpackagist-plugin/camptix-trustpay": "*", - "wordpress-plugin/camptix-paystack": "1.0.0", - "wordpress-plugin/camptix-paynow": "1.0.4", "wpackagist-plugin/supportflow": "dev-trunk", "wpackagist-plugin/tagregator": "dev-trunk", "wordpress-meta/wporg-profiles-wp-activity-notifier": "1.1", @@ -148,7 +110,7 @@ "_test:watch:fast_comment": "This can't use `@test:watch`, because that only works if `@` is the first character in the string. It can't manually call `composer run test:watch`, because that strips out extra arguments like `-- group=sunrise`. This must be manually kept in sync with the `test:watch` command.", "test:watch:fast": "WP_TESTS_SKIP_INSTALL=1 phpunit-watcher watch < /dev/tty", "test:db:reset": "/usr/bin/env php .docker/bin/reset-tests-database.php", - "test:coverage": "php -d xdebug.mode=coverage public_html/wp-content/mu-plugins/vendor/bin/phpunit --coverage-html .phpunit/coverage-report", + "test:coverage": "php -d xdebug.mode=coverage public_html/wp-content/mu-plugins/vendor/bin/phpunit --coverage-html .phpunit/coverage-report", "phpcs-changed": "BASE_REF=production php .github/bin/phpcs-branch.php ", "_comment": "Below script names left in for back-compat", "phpcs": "phpcs", From f4b245b50e4fc67a0d9bc02c259db3b9c87db5dd Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:42:11 +1000 Subject: [PATCH 04/42] Remove supportflow, move tagregator to wp-env Supportflow is explicitly deactivated network-wide in wcorg-network-plugin-control.php and is not actively used. Remove it entirely rather than migrating to wp-env. Move tagregator from Composer to wp-env plugin slug. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 1 + composer.json | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 1e83da4530..1b374df27c 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -22,6 +22,7 @@ "liveblog", "public-post-preview", "pwa", + "tagregator", "tbz-camptix-paystack", "wordpress-importer", "wp-cldr", diff --git a/composer.json b/composer.json index a4daa86d26..0a64e9c2e3 100644 --- a/composer.json +++ b/composer.json @@ -95,8 +95,6 @@ "yoast/phpunit-polyfills": "^4.0", "composer/installers": "^2.2", "wordpress-plugin/bbpress": "2.6.*", - "wpackagist-plugin/supportflow": "dev-trunk", - "wpackagist-plugin/tagregator": "dev-trunk", "wordpress-meta/wporg-profiles-wp-activity-notifier": "1.1", "wporg/wporg-mu-plugins": "dev-build", "quickbooks/v3-php-sdk": "*" From d5c9bb5d061b8610b45da1fae7bfd5c5a0cd6914 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:42:20 +1000 Subject: [PATCH 05/42] Update wp-env PHP version to 8.5 PHP 8.5 is fully supported by the official WordPress Docker images and has beta support in WordPress 6.9+. This matches the latest PHP version tested in CI. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index 1b374df27c..c9a4b7ccd6 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,6 +1,6 @@ { "core": null, - "phpVersion": "8.1", + "phpVersion": "8.5", "multisite": true, "port": 80, "testsPort": 8889, From 244fab073197d534ac149fe4d237d48990d2b8b1 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:44:20 +1000 Subject: [PATCH 06/42] Update GitHub Actions to use wp-env for PHP tests Replace the manual MySQL service, SVN, install-wp-tests.sh setup with wp-env. The PHP version matrix is handled via .wp-env.override.json which overrides the phpVersion per matrix entry. This eliminates the need for the MySQL service container, SVN installation, and the custom WP test suite installer script. wp-env provides WordPress, the PHPUnit test suite, and MySQL out of the box. The linter workflow is unchanged since it does not run WordPress. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 50 +++++++++++++++----------------- package.json | 2 +- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index dbec3deb39..5006366ae7 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -55,45 +55,41 @@ jobs: matrix: php-version: ['8.1', '8.4', '8.5'] - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: false - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: wcorg_test - ports: - - 3306/tcp - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - steps: - uses: actions/checkout@v4 - - name: Install SVN - run: | - sudo rm -f /var/lib/man-db/auto-update # https://github.com/actions/runner-images/issues/10977 - sudo apt-get update && sudo apt-get install -y subversion + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x - - name: Set PHP version + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Node dependencies + run: yarn + + - name: Set PHP version for Composer uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-version }} + php-version: '8.1' coverage: none tools: composer:v2 env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Start mysql service - run: sudo /etc/init.d/mysql start - - - name: Install dependencies + - name: Install Composer dependencies run: | rm composer.lock || true composer install - - name: Install WordPress - run: | - bash .docker/bin/install-wp-tests.sh wcorg_test root root 127.0.0.1 latest + - name: Configure wp-env for PHP ${{ matrix.php-version }} + run: echo '{"phpVersion":"${{ matrix.php-version }}","port":8888}' > .wp-env.override.json + + - name: Start wp-env + run: npx wp-env start - - name: Running unit tests - run: ./public_html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist + - name: Run PHPUnit + run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/package.json b/package.json index 80bd5fc182..90569290e6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "scripts": { "wp-env:start": "wp-env start", "wp-env:stop": "wp-env stop", - "wp-env:test:php": "wp-env run tests-cli --env-cwd=/var/www/html phpunit -c phpunit.xml.dist", + "wp-env:test:php": "wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist", "wp-env:destroy": "wp-env destroy" }, "browserslist": [ From 1a143abab461c05025b282eed4989d0950fae33d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:49:06 +1000 Subject: [PATCH 07/42] Remove camptix-* plugins from wp-env These payment gateway plugins are not needed for local development. Code references to them can remain for production use. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index c9a4b7ccd6..695e02b27a 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -6,24 +6,16 @@ "testsPort": 8889, "plugins": [ "akismet", - "bd-payments-camptix", - "camptix-mercadopago", - "camptix-pagseguro", - "camptix-payfast-gateway", - "camptix-trustcard", - "camptix-trustpay", "classic-editor", "custom-content-width", "edit-flow", "email-post-changes", - "gateway-camptix-paynow-payment", "gutenberg", "jetpack", "liveblog", "public-post-preview", "pwa", "tagregator", - "tbz-camptix-paystack", "wordpress-importer", "wp-cldr", "wp-super-cache" From 89380f08a7a6c56acc2edb78b042b0b4a44e025e Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:49:31 +1000 Subject: [PATCH 08/42] Move bbpress from Composer SVN to wp-env plugin slug Install bbpress from the latest wordpress.org release instead of pinning to the 2.6 SVN branch via a custom Composer package. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 1 + composer.json | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 695e02b27a..ea09e65913 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -6,6 +6,7 @@ "testsPort": 8889, "plugins": [ "akismet", + "bbpress", "classic-editor", "custom-content-width", "edit-flow", diff --git a/composer.json b/composer.json index 0a64e9c2e3..85aa2b8152 100644 --- a/composer.json +++ b/composer.json @@ -57,16 +57,6 @@ { "type": "package", "package": [ - { - "name": "wordpress-plugin/bbpress", - "type": "wordpress-plugin", - "version": "2.6", - "source": { - "type": "svn", - "url": "https://plugins.svn.wordpress.org/bbpress/", - "reference": "branches/2.6/" - } - }, { "name": "wordpress-meta/wporg-profiles-wp-activity-notifier", "type": "wordpress-plugin", @@ -94,7 +84,6 @@ "spatie/phpunit-watcher": "^1.23", "yoast/phpunit-polyfills": "^4.0", "composer/installers": "^2.2", - "wordpress-plugin/bbpress": "2.6.*", "wordpress-meta/wporg-profiles-wp-activity-notifier": "1.1", "wporg/wporg-mu-plugins": "dev-build", "quickbooks/v3-php-sdk": "*" From 05b888b5d50ae69830fe10a727b31bdb3f75105e Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 17:49:48 +1000 Subject: [PATCH 09/42] Restore SVN install in CI for Composer SVN packages The wporg-profiles-wp-activity-notifier package is installed from meta.svn.wordpress.org and requires SVN to be available. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5006366ae7..3045d77c1b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -80,6 +80,11 @@ jobs: env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install SVN + run: | + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get update && sudo apt-get install -y subversion + - name: Install Composer dependencies run: | rm composer.lock || true From dc61918d210ed3956a2b237e0c3c822aa9633ae4 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 18:08:41 +1000 Subject: [PATCH 10/42] Use download URLs for wp-env plugins and themes wp-env requires full download URLs for wordpress.org plugins and themes, not bare slugs. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index ea09e65913..14cc916bee 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -5,28 +5,28 @@ "port": 80, "testsPort": 8889, "plugins": [ - "akismet", - "bbpress", - "classic-editor", - "custom-content-width", - "edit-flow", - "email-post-changes", - "gutenberg", - "jetpack", - "liveblog", - "public-post-preview", - "pwa", - "tagregator", - "wordpress-importer", - "wp-cldr", - "wp-super-cache" + "https://downloads.wordpress.org/plugin/akismet.zip", + "https://downloads.wordpress.org/plugin/bbpress.zip", + "https://downloads.wordpress.org/plugin/classic-editor.zip", + "https://downloads.wordpress.org/plugin/custom-content-width.zip", + "https://downloads.wordpress.org/plugin/edit-flow.zip", + "https://downloads.wordpress.org/plugin/email-post-changes.zip", + "https://downloads.wordpress.org/plugin/gutenberg.zip", + "https://downloads.wordpress.org/plugin/jetpack.zip", + "https://downloads.wordpress.org/plugin/liveblog.zip", + "https://downloads.wordpress.org/plugin/public-post-preview.zip", + "https://downloads.wordpress.org/plugin/pwa.zip", + "https://downloads.wordpress.org/plugin/tagregator.zip", + "https://downloads.wordpress.org/plugin/wordpress-importer.zip", + "https://downloads.wordpress.org/plugin/wp-cldr.zip", + "https://downloads.wordpress.org/plugin/wp-super-cache.zip" ], "themes": [ - "p2", - "twentyten", - "twentytwentytwo", - "twentytwentythree", - "twentytwentyfour" + "https://downloads.wordpress.org/theme/p2.zip", + "https://downloads.wordpress.org/theme/twentyten.zip", + "https://downloads.wordpress.org/theme/twentytwentytwo.zip", + "https://downloads.wordpress.org/theme/twentytwentythree.zip", + "https://downloads.wordpress.org/theme/twentytwentyfour.zip" ], "config": { "IS_WORDCAMP_NETWORK": true, From 4dffc68310b8a559f77285182625d5cddd173023 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 18:14:44 +1000 Subject: [PATCH 11/42] Remove p2 theme from wp-env (not available on wordpress.org) The p2 theme returns a 404 from downloads.wordpress.org. It is no longer distributed as a standard theme download. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index 14cc916bee..680b889781 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -22,7 +22,6 @@ "https://downloads.wordpress.org/plugin/wp-super-cache.zip" ], "themes": [ - "https://downloads.wordpress.org/theme/p2.zip", "https://downloads.wordpress.org/theme/twentyten.zip", "https://downloads.wordpress.org/theme/twentytwentytwo.zip", "https://downloads.wordpress.org/theme/twentytwentythree.zip", From b05cd731da30ad4026e19aa912fdc4bc5dd0140e Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 18:35:50 +1000 Subject: [PATCH 12/42] Add defensive checks for constants in mu-plugins wp-env runs wp core install before wp config set, so custom constants like WORDCAMP_ENVIRONMENT are not defined when mu-plugins first load during installation. Add defined() checks to prevent fatal errors during wp-env startup. Also add file_exists() check for pub-sync/loader.php which may not exist in all environments (e.g. wp-env where mu-plugins-private is not mapped). Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/cron.php | 2 +- .../wp-content/mu-plugins/load-other-mu-plugins.php | 7 +++++-- .../wp-content/mu-plugins/service-worker-caching.php | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/public_html/wp-content/mu-plugins/cron.php b/public_html/wp-content/mu-plugins/cron.php index e6a1a734d1..a1e7499790 100644 --- a/public_html/wp-content/mu-plugins/cron.php +++ b/public_html/wp-content/mu-plugins/cron.php @@ -19,7 +19,7 @@ wp_raise_memory_limit( 'wordcamp_high' ); } -if ( 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { +if ( defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { add_action( 'init', __NAMESPACE__ . '\schedule_daily_jobs' ); add_action( 'wordcamp_daily_jobs', __NAMESPACE__ . '\execute_daily_jobs' ); } diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index 14a5394f37..4c05e9cc65 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -18,7 +18,10 @@ function wcorg_include_common_plugins() { // Include the public `wporg-mu-plugins` that are synced from Git to SVN. These are different than the // ones included in `wcorg_include_common_plugins()`. - require_once dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; + $pub_sync_loader = dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; + if ( file_exists( $pub_sync_loader ) ) { + require_once $pub_sync_loader; + } wcorg_include_individual_mu_plugins(); wcorg_include_mu_plugin_folders(); @@ -28,7 +31,7 @@ function wcorg_include_common_plugins() { * Include mu-plugins that should only run on a specific network. */ function wcorg_include_network_only_plugins() { - if ( EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { + if ( defined( 'EVENTS_NETWORK_ID' ) && defined( 'SITE_ID_CURRENT_SITE' ) && EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { $network_folder = 'events'; } else { diff --git a/public_html/wp-content/mu-plugins/service-worker-caching.php b/public_html/wp-content/mu-plugins/service-worker-caching.php index 173e8c315b..6f3a6f30d3 100644 --- a/public_html/wp-content/mu-plugins/service-worker-caching.php +++ b/public_html/wp-content/mu-plugins/service-worker-caching.php @@ -15,7 +15,7 @@ $caching_enabled = apply_filters( 'wordcamp_service_worker_caching_enabled', - 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled + defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled ); if ( ! $caching_enabled ) { From 1a88e399e14d6a57527429803f9c3a244f8a1be3 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 18:42:23 +1000 Subject: [PATCH 13/42] Add fallback constants mu-plugin for wp-env compatibility wp-env runs wp core install before wp config set, so custom constants are not available when mu-plugins first load during installation. This early-loading mu-plugin provides safe defaults that prevent fatal errors during the initial WordPress bootstrap. In production and Docker environments where constants are already defined in wp-config.php, this file is a no-op. Co-Authored-By: Claude Opus 4.6 --- .../wp-content/mu-plugins/0-aaa-constants.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 public_html/wp-content/mu-plugins/0-aaa-constants.php diff --git a/public_html/wp-content/mu-plugins/0-aaa-constants.php b/public_html/wp-content/mu-plugins/0-aaa-constants.php new file mode 100644 index 0000000000..775beaf964 --- /dev/null +++ b/public_html/wp-content/mu-plugins/0-aaa-constants.php @@ -0,0 +1,31 @@ + 'local', + 'WORDCAMP_NETWORK_ID' => 1, + 'WORDCAMP_ROOT_BLOG_ID' => 5, + 'EVENTS_NETWORK_ID' => 2, + 'EVENTS_ROOT_BLOG_ID' => 47, + 'CAMPUS_NETWORK_ID' => 3, + 'CAMPUS_ROOT_BLOG_ID' => 47, + 'SITE_ID_CURRENT_SITE' => 1, + 'BLOG_ID_CURRENT_SITE' => 5, +); + +foreach ( $defaults as $name => $value ) { + if ( ! defined( $name ) ) { + define( $name, $value ); + } +} From cdb1175f6dad86e8711bfa9edf5af7face5fbde2 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 18:47:41 +1000 Subject: [PATCH 14/42] Add JS build step to CI before wp-env start wp-env starts a full WordPress instance that loads mu-plugins, which require built JS/CSS assets (blocks, virtual-embeds). Add a build step before starting wp-env to generate these artifacts. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3045d77c1b..b09f8ce2c4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -71,6 +71,9 @@ jobs: - name: Install Node dependencies run: yarn + - name: Build JS assets + run: yarn workspaces run build + - name: Set PHP version for Composer uses: shivammathur/setup-php@v2 with: From 1264cb41192141e57be3399ae59b574b2ead8f6e Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 18:55:32 +1000 Subject: [PATCH 15/42] Add MULTISITE and SUBDOMAIN_INSTALL to fallback constants During wp-env initial install, WordPress loads without multisite since those constants are not yet in wp-config.php. This means multisite functions like get_site_meta() are undefined. Adding MULTISITE and SUBDOMAIN_INSTALL as fallback constants ensures WordPress loads the multisite function stack during initial bootstrap. Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/0-aaa-constants.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public_html/wp-content/mu-plugins/0-aaa-constants.php b/public_html/wp-content/mu-plugins/0-aaa-constants.php index 775beaf964..210715ab39 100644 --- a/public_html/wp-content/mu-plugins/0-aaa-constants.php +++ b/public_html/wp-content/mu-plugins/0-aaa-constants.php @@ -22,6 +22,11 @@ 'CAMPUS_ROOT_BLOG_ID' => 47, 'SITE_ID_CURRENT_SITE' => 1, 'BLOG_ID_CURRENT_SITE' => 5, + + // Multisite constants needed so WordPress loads the multisite + // function stack (get_site_meta, etc.) during initial install. + 'MULTISITE' => true, + 'SUBDOMAIN_INSTALL' => true, ); foreach ( $defaults as $name => $value ) { From a0e9703bd199ef0afd79a3caec5e720d41d2084d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 19:06:26 +1000 Subject: [PATCH 16/42] Revert CI to original workflow, keep wp-env for local dev only wp-env runs wp core install before wp config set, and MULTISITE must be in wp-config.php before wp-settings.php processes the multisite check. Since mu-plugins load after that check, fallback constants in a mu-plugin cannot make multisite functions available during the initial WordPress installation. The original CI approach (MySQL service + install-wp-tests.sh) avoids this entirely because PHPUnit loads WordPress via its own bootstrap where all constants are pre-defined. Also remove MULTISITE/SUBDOMAIN_INSTALL from the fallback constants since they have no effect as a mu-plugin (too late in load order). Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 56 +++++++++---------- .../wp-content/mu-plugins/0-aaa-constants.php | 5 -- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index b09f8ce2c4..dbec3deb39 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -55,49 +55,45 @@ jobs: matrix: php-version: ['8.1', '8.4', '8.5'] + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: false + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: wcorg_test + ports: + - 3306/tcp + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: 20.x - - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install Node dependencies - run: yarn - - - name: Build JS assets - run: yarn workspaces run build + - name: Install SVN + run: | + sudo rm -f /var/lib/man-db/auto-update # https://github.com/actions/runner-images/issues/10977 + sudo apt-get update && sudo apt-get install -y subversion - - name: Set PHP version for Composer + - name: Set PHP version uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: ${{ matrix.php-version }} coverage: none tools: composer:v2 env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install SVN - run: | - sudo rm -f /var/lib/man-db/auto-update - sudo apt-get update && sudo apt-get install -y subversion + - name: Start mysql service + run: sudo /etc/init.d/mysql start - - name: Install Composer dependencies + - name: Install dependencies run: | rm composer.lock || true composer install - - name: Configure wp-env for PHP ${{ matrix.php-version }} - run: echo '{"phpVersion":"${{ matrix.php-version }}","port":8888}' > .wp-env.override.json - - - name: Start wp-env - run: npx wp-env start + - name: Install WordPress + run: | + bash .docker/bin/install-wp-tests.sh wcorg_test root root 127.0.0.1 latest - - name: Run PHPUnit - run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist + - name: Running unit tests + run: ./public_html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/public_html/wp-content/mu-plugins/0-aaa-constants.php b/public_html/wp-content/mu-plugins/0-aaa-constants.php index 210715ab39..775beaf964 100644 --- a/public_html/wp-content/mu-plugins/0-aaa-constants.php +++ b/public_html/wp-content/mu-plugins/0-aaa-constants.php @@ -22,11 +22,6 @@ 'CAMPUS_ROOT_BLOG_ID' => 47, 'SITE_ID_CURRENT_SITE' => 1, 'BLOG_ID_CURRENT_SITE' => 5, - - // Multisite constants needed so WordPress loads the multisite - // function stack (get_site_meta, etc.) during initial install. - 'MULTISITE' => true, - 'SUBDOMAIN_INSTALL' => true, ); foreach ( $defaults as $name => $value ) { From 03ad3c8066a4083a4d6df59ffd6faddda9618f4d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 19:15:02 +1000 Subject: [PATCH 17/42] Revert "Add defensive checks for constants in mu-plugins" This reverts commit b05cd731da30ad4026e19aa912fdc4bc5dd0140e. --- public_html/wp-content/mu-plugins/cron.php | 2 +- .../wp-content/mu-plugins/load-other-mu-plugins.php | 7 ++----- .../wp-content/mu-plugins/service-worker-caching.php | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/public_html/wp-content/mu-plugins/cron.php b/public_html/wp-content/mu-plugins/cron.php index a1e7499790..e6a1a734d1 100644 --- a/public_html/wp-content/mu-plugins/cron.php +++ b/public_html/wp-content/mu-plugins/cron.php @@ -19,7 +19,7 @@ wp_raise_memory_limit( 'wordcamp_high' ); } -if ( defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { +if ( 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { add_action( 'init', __NAMESPACE__ . '\schedule_daily_jobs' ); add_action( 'wordcamp_daily_jobs', __NAMESPACE__ . '\execute_daily_jobs' ); } diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index 4c05e9cc65..14a5394f37 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -18,10 +18,7 @@ function wcorg_include_common_plugins() { // Include the public `wporg-mu-plugins` that are synced from Git to SVN. These are different than the // ones included in `wcorg_include_common_plugins()`. - $pub_sync_loader = dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; - if ( file_exists( $pub_sync_loader ) ) { - require_once $pub_sync_loader; - } + require_once dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; wcorg_include_individual_mu_plugins(); wcorg_include_mu_plugin_folders(); @@ -31,7 +28,7 @@ function wcorg_include_common_plugins() { * Include mu-plugins that should only run on a specific network. */ function wcorg_include_network_only_plugins() { - if ( defined( 'EVENTS_NETWORK_ID' ) && defined( 'SITE_ID_CURRENT_SITE' ) && EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { + if ( EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { $network_folder = 'events'; } else { diff --git a/public_html/wp-content/mu-plugins/service-worker-caching.php b/public_html/wp-content/mu-plugins/service-worker-caching.php index 6f3a6f30d3..173e8c315b 100644 --- a/public_html/wp-content/mu-plugins/service-worker-caching.php +++ b/public_html/wp-content/mu-plugins/service-worker-caching.php @@ -15,7 +15,7 @@ $caching_enabled = apply_filters( 'wordcamp_service_worker_caching_enabled', - defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled + 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled ); if ( ! $caching_enabled ) { From 8ce5febaa12f0ffac67598207756757aa2050afd Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 21:26:02 +1000 Subject: [PATCH 18/42] Use wp-env for CI, guard sunrise and mu-plugins for missing constants Instead of a fallback constants mu-plugin (0-aaa-constants.php), guard individual files against missing constants during wp-env initial bootstrap (wp core install runs before wp config set): - sunrise.php: return early if WORDCAMP_ENVIRONMENT is not defined - cron.php, service-worker-caching.php: add defined() checks - load-other-mu-plugins.php: add file_exists() for pub-sync/loader.php and defined() checks for network constants Move WORDCAMP_ENVIRONMENT to last in .wp-env.json config so all other constants are set in wp-config.php first. Switch CI from manual MySQL + install-wp-tests.sh to wp-env. PHP version matrix handled via .wp-env.override.json. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 39 +++++++++---------- .gitignore | 1 + .wp-env.json | 4 +- .../wp-content/mu-plugins/0-aaa-constants.php | 31 --------------- public_html/wp-content/mu-plugins/cron.php | 2 +- .../mu-plugins/load-other-mu-plugins.php | 7 +++- .../mu-plugins/service-worker-caching.php | 2 +- public_html/wp-content/sunrise.php | 6 +++ 8 files changed, 34 insertions(+), 58 deletions(-) delete mode 100644 public_html/wp-content/mu-plugins/0-aaa-constants.php diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index dbec3deb39..170644d49d 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -55,17 +55,6 @@ jobs: matrix: php-version: ['8.1', '8.4', '8.5'] - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: false - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: wcorg_test - ports: - - 3306/tcp - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - steps: - uses: actions/checkout@v4 @@ -74,26 +63,34 @@ jobs: sudo rm -f /var/lib/man-db/auto-update # https://github.com/actions/runner-images/issues/10977 sudo apt-get update && sudo apt-get install -y subversion - - name: Set PHP version + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: Set up PHP for Composer uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-version }} + php-version: '8.1' coverage: none tools: composer:v2 env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Start mysql service - run: sudo /etc/init.d/mysql start + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install dependencies run: | rm composer.lock || true composer install + yarn - - name: Install WordPress - run: | - bash .docker/bin/install-wp-tests.sh wcorg_test root root 127.0.0.1 latest + - name: Build JS assets + run: yarn workspaces run build + + - name: Configure wp-env PHP version + run: echo '{ "phpVersion":"${{ matrix.php-version }}" }' > .wp-env.override.json + + - name: Start wp-env + run: npx wp-env start - name: Running unit tests - run: ./public_html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist + run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/.gitignore b/.gitignore index 156b4360e9..01e4107081 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ docker-compose.override.yaml # wp-env # .wp-env-home +.wp-env.override.json # diff --git a/.wp-env.json b/.wp-env.json index 680b889781..b65249152d 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -29,7 +29,6 @@ ], "config": { "IS_WORDCAMP_NETWORK": true, - "WORDCAMP_ENVIRONMENT": "local", "WP_ENVIRONMENT_TYPE": "local", "SUNRISE": true, "SUBDOMAIN_INSTALL": true, @@ -53,7 +52,8 @@ "DISALLOW_UNFILTERED_HTML": true, "WORDCAMP_QBO_HMAC_KEY": "localhmac", "EMAIL_DEVELOPER_NOTIFICATIONS": "developers@example.test", - "EMAIL_CENTRAL_SUPPORT": "support@central.wordcamp.test" + "EMAIL_CENTRAL_SUPPORT": "support@central.wordcamp.test", + "WORDCAMP_ENVIRONMENT": "local" }, "mappings": { "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", diff --git a/public_html/wp-content/mu-plugins/0-aaa-constants.php b/public_html/wp-content/mu-plugins/0-aaa-constants.php deleted file mode 100644 index 775beaf964..0000000000 --- a/public_html/wp-content/mu-plugins/0-aaa-constants.php +++ /dev/null @@ -1,31 +0,0 @@ - 'local', - 'WORDCAMP_NETWORK_ID' => 1, - 'WORDCAMP_ROOT_BLOG_ID' => 5, - 'EVENTS_NETWORK_ID' => 2, - 'EVENTS_ROOT_BLOG_ID' => 47, - 'CAMPUS_NETWORK_ID' => 3, - 'CAMPUS_ROOT_BLOG_ID' => 47, - 'SITE_ID_CURRENT_SITE' => 1, - 'BLOG_ID_CURRENT_SITE' => 5, -); - -foreach ( $defaults as $name => $value ) { - if ( ! defined( $name ) ) { - define( $name, $value ); - } -} diff --git a/public_html/wp-content/mu-plugins/cron.php b/public_html/wp-content/mu-plugins/cron.php index e6a1a734d1..a1e7499790 100644 --- a/public_html/wp-content/mu-plugins/cron.php +++ b/public_html/wp-content/mu-plugins/cron.php @@ -19,7 +19,7 @@ wp_raise_memory_limit( 'wordcamp_high' ); } -if ( 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { +if ( defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { add_action( 'init', __NAMESPACE__ . '\schedule_daily_jobs' ); add_action( 'wordcamp_daily_jobs', __NAMESPACE__ . '\execute_daily_jobs' ); } diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index 14a5394f37..4c05e9cc65 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -18,7 +18,10 @@ function wcorg_include_common_plugins() { // Include the public `wporg-mu-plugins` that are synced from Git to SVN. These are different than the // ones included in `wcorg_include_common_plugins()`. - require_once dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; + $pub_sync_loader = dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; + if ( file_exists( $pub_sync_loader ) ) { + require_once $pub_sync_loader; + } wcorg_include_individual_mu_plugins(); wcorg_include_mu_plugin_folders(); @@ -28,7 +31,7 @@ function wcorg_include_common_plugins() { * Include mu-plugins that should only run on a specific network. */ function wcorg_include_network_only_plugins() { - if ( EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { + if ( defined( 'EVENTS_NETWORK_ID' ) && defined( 'SITE_ID_CURRENT_SITE' ) && EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { $network_folder = 'events'; } else { diff --git a/public_html/wp-content/mu-plugins/service-worker-caching.php b/public_html/wp-content/mu-plugins/service-worker-caching.php index 173e8c315b..6f3a6f30d3 100644 --- a/public_html/wp-content/mu-plugins/service-worker-caching.php +++ b/public_html/wp-content/mu-plugins/service-worker-caching.php @@ -15,7 +15,7 @@ $caching_enabled = apply_filters( 'wordcamp_service_worker_caching_enabled', - 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled + defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled ); if ( ! $caching_enabled ) { diff --git a/public_html/wp-content/sunrise.php b/public_html/wp-content/sunrise.php index 52656dd005..9cb1e79aff 100644 --- a/public_html/wp-content/sunrise.php +++ b/public_html/wp-content/sunrise.php @@ -134,4 +134,10 @@ function get_domain_network_id( string $domain ): int { } } +// During wp-env initial install, constants aren't yet in wp-config.php. +// Return early to avoid fatal errors; sunrise will work on subsequent loads. +if ( ! defined( 'WORDCAMP_ENVIRONMENT' ) ) { + return; +} + load_network_sunrise(); From 7d3f89bef9b4a0f61823f6800b5aaae38580ede8 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 21:50:26 +1000 Subject: [PATCH 19/42] Simplify sunrise early return: only define WORDCAMP_ENVIRONMENT Since sunrise loads before mu-plugins, defining WORDCAMP_ENVIRONMENT there is sufficient. All other constants are already in wp-config.php because WORDCAMP_ENVIRONMENT is set last in the wp-env config. Also disable plugin loading during initial setup via filters, and revert the defensive defined() checks in mu-plugins since they are no longer needed. Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/cron.php | 2 +- .../wp-content/mu-plugins/load-other-mu-plugins.php | 7 ++----- .../wp-content/mu-plugins/service-worker-caching.php | 2 +- public_html/wp-content/sunrise.php | 12 ++++++++++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/public_html/wp-content/mu-plugins/cron.php b/public_html/wp-content/mu-plugins/cron.php index a1e7499790..e6a1a734d1 100644 --- a/public_html/wp-content/mu-plugins/cron.php +++ b/public_html/wp-content/mu-plugins/cron.php @@ -19,7 +19,7 @@ wp_raise_memory_limit( 'wordcamp_high' ); } -if ( defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { +if ( 'production' === WORDCAMP_ENVIRONMENT && is_main_site() ) { add_action( 'init', __NAMESPACE__ . '\schedule_daily_jobs' ); add_action( 'wordcamp_daily_jobs', __NAMESPACE__ . '\execute_daily_jobs' ); } diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index 4c05e9cc65..14a5394f37 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -18,10 +18,7 @@ function wcorg_include_common_plugins() { // Include the public `wporg-mu-plugins` that are synced from Git to SVN. These are different than the // ones included in `wcorg_include_common_plugins()`. - $pub_sync_loader = dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; - if ( file_exists( $pub_sync_loader ) ) { - require_once $pub_sync_loader; - } + require_once dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; wcorg_include_individual_mu_plugins(); wcorg_include_mu_plugin_folders(); @@ -31,7 +28,7 @@ function wcorg_include_common_plugins() { * Include mu-plugins that should only run on a specific network. */ function wcorg_include_network_only_plugins() { - if ( defined( 'EVENTS_NETWORK_ID' ) && defined( 'SITE_ID_CURRENT_SITE' ) && EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { + if ( EVENTS_NETWORK_ID === SITE_ID_CURRENT_SITE ) { $network_folder = 'events'; } else { diff --git a/public_html/wp-content/mu-plugins/service-worker-caching.php b/public_html/wp-content/mu-plugins/service-worker-caching.php index 6f3a6f30d3..173e8c315b 100644 --- a/public_html/wp-content/mu-plugins/service-worker-caching.php +++ b/public_html/wp-content/mu-plugins/service-worker-caching.php @@ -15,7 +15,7 @@ $caching_enabled = apply_filters( 'wordcamp_service_worker_caching_enabled', - defined( 'WORDCAMP_ENVIRONMENT' ) && 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled + 'production' === WORDCAMP_ENVIRONMENT && 'off' === $coming_soon_enabled ); if ( ! $caching_enabled ) { diff --git a/public_html/wp-content/sunrise.php b/public_html/wp-content/sunrise.php index 9cb1e79aff..0cd34c1a5a 100644 --- a/public_html/wp-content/sunrise.php +++ b/public_html/wp-content/sunrise.php @@ -134,9 +134,17 @@ function get_domain_network_id( string $domain ): int { } } -// During wp-env initial install, constants aren't yet in wp-config.php. -// Return early to avoid fatal errors; sunrise will work on subsequent loads. +// During wp-env setup, WORDCAMP_ENVIRONMENT is set last in wp-config.php so +// all other constants are available first. Define a safe default and return +// early until setup is complete. Sunrise loads before mu-plugins, so this +// constant is available everywhere. if ( ! defined( 'WORDCAMP_ENVIRONMENT' ) ) { + define( 'WORDCAMP_ENVIRONMENT', 'local' ); + + // Prevent plugins from loading during initial setup. + add_filter( 'option_active_plugins', '__return_empty_array' ); + add_filter( 'site_option_active_sitewide_plugins', '__return_empty_array' ); + return; } From fbba7148df39366166852a590af8a613fe81605d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:07:24 +1000 Subject: [PATCH 20/42] Map wp-env-only mu-plugin for fallback constants during bootstrap wp-env runs wp core install before wp config set, so custom constants aren't in wp-config.php when mu-plugins first load. Instead of adding defensive checks to production code, map a wp-env-only mu-plugin from .wp-env/0-early-mu-plugin.php that defines safe defaults. The file sorts first alphabetically (0-aaa-*) so all constants are available before other mu-plugins load. In production, this file does not exist. Simplify the sunrise.php early return to just define WORDCAMP_ENVIRONMENT (the only constant sunrise itself needs) since the mu-plugin handles the rest. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 2 ++ .wp-env/0-early-mu-plugin.php | 32 ++++++++++++++++++++++++++++++ public_html/wp-content/sunrise.php | 11 ++-------- 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 .wp-env/0-early-mu-plugin.php diff --git a/.wp-env.json b/.wp-env.json index b65249152d..5a45359592 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -56,6 +56,7 @@ "WORDCAMP_ENVIRONMENT": "local" }, "mappings": { + "wp-content/mu-plugins/0-aaa-wp-env-constants.php": "./.wp-env/0-early-mu-plugin.php", "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", "wp-content/plugins": "./public_html/wp-content/plugins", "wp-content/sunrise.php": "./public_html/wp-content/sunrise.php", @@ -75,6 +76,7 @@ "WP_TESTS_MULTISITE": 1 }, "mappings": { + "wp-content/mu-plugins/0-aaa-wp-env-constants.php": "./.wp-env/0-early-mu-plugin.php", "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", "wp-content/plugins": "./public_html/wp-content/plugins", "wp-content/sunrise.php": "./public_html/wp-content/sunrise.php", diff --git a/.wp-env/0-early-mu-plugin.php b/.wp-env/0-early-mu-plugin.php new file mode 100644 index 0000000000..1a2581885d --- /dev/null +++ b/.wp-env/0-early-mu-plugin.php @@ -0,0 +1,32 @@ + 'local', + 'WORDCAMP_NETWORK_ID' => 1, + 'WORDCAMP_ROOT_BLOG_ID' => 5, + 'EVENTS_NETWORK_ID' => 2, + 'EVENTS_ROOT_BLOG_ID' => 47, + 'CAMPUS_NETWORK_ID' => 3, + 'CAMPUS_ROOT_BLOG_ID' => 47, + 'SITE_ID_CURRENT_SITE' => 1, + 'BLOG_ID_CURRENT_SITE' => 5, +); + +foreach ( $defaults as $name => $value ) { + if ( ! defined( $name ) ) { + define( $name, $value ); + } +} diff --git a/public_html/wp-content/sunrise.php b/public_html/wp-content/sunrise.php index 0cd34c1a5a..907a8f482b 100644 --- a/public_html/wp-content/sunrise.php +++ b/public_html/wp-content/sunrise.php @@ -134,17 +134,10 @@ function get_domain_network_id( string $domain ): int { } } -// During wp-env setup, WORDCAMP_ENVIRONMENT is set last in wp-config.php so -// all other constants are available first. Define a safe default and return -// early until setup is complete. Sunrise loads before mu-plugins, so this -// constant is available everywhere. +// During wp-env setup, WORDCAMP_ENVIRONMENT is set last in wp-config.php +// so all other constants are available first. Return early until it's set. if ( ! defined( 'WORDCAMP_ENVIRONMENT' ) ) { define( 'WORDCAMP_ENVIRONMENT', 'local' ); - - // Prevent plugins from loading during initial setup. - add_filter( 'option_active_plugins', '__return_empty_array' ); - add_filter( 'site_option_active_sitewide_plugins', '__return_empty_array' ); - return; } From 8b68b2e6bb0577134efa032871ee25ea043444e0 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:13:41 +1000 Subject: [PATCH 21/42] Fix CI: mapping order, missing file guard, PHPCS lint - Move mu-plugin file mapping after directory mapping in .wp-env.json so the directory mount doesn't overwrite the individual file. - Add file_exists guard for mu-plugins-private/wporg-mu-plugins/pub-sync which doesn't exist in wp-env environments. - Fix PHPCS doc comment capitalization in 0-early-mu-plugin.php. - Add retry to wp-env start for transient 429 rate limiting. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 2 +- .wp-env.json | 4 ++-- .wp-env/0-early-mu-plugin.php | 4 ++-- public_html/wp-content/mu-plugins/load-other-mu-plugins.php | 4 +++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 170644d49d..1d4e646677 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -90,7 +90,7 @@ jobs: run: echo '{ "phpVersion":"${{ matrix.php-version }}" }' > .wp-env.override.json - name: Start wp-env - run: npx wp-env start + run: npx wp-env start || (sleep 30 && npx wp-env start) - name: Running unit tests run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/.wp-env.json b/.wp-env.json index 5a45359592..9d4594e423 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -56,8 +56,8 @@ "WORDCAMP_ENVIRONMENT": "local" }, "mappings": { - "wp-content/mu-plugins/0-aaa-wp-env-constants.php": "./.wp-env/0-early-mu-plugin.php", "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", + "wp-content/mu-plugins/0-aaa-wp-env-constants.php": "./.wp-env/0-early-mu-plugin.php", "wp-content/plugins": "./public_html/wp-content/plugins", "wp-content/sunrise.php": "./public_html/wp-content/sunrise.php", "wp-content/sunrise-wordcamp.php": "./public_html/wp-content/sunrise-wordcamp.php", @@ -76,8 +76,8 @@ "WP_TESTS_MULTISITE": 1 }, "mappings": { - "wp-content/mu-plugins/0-aaa-wp-env-constants.php": "./.wp-env/0-early-mu-plugin.php", "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", + "wp-content/mu-plugins/0-aaa-wp-env-constants.php": "./.wp-env/0-early-mu-plugin.php", "wp-content/plugins": "./public_html/wp-content/plugins", "wp-content/sunrise.php": "./public_html/wp-content/sunrise.php", "wp-content/sunrise-wordcamp.php": "./public_html/wp-content/sunrise-wordcamp.php", diff --git a/.wp-env/0-early-mu-plugin.php b/.wp-env/0-early-mu-plugin.php index 1a2581885d..b8a8eed6fe 100644 --- a/.wp-env/0-early-mu-plugin.php +++ b/.wp-env/0-early-mu-plugin.php @@ -1,8 +1,8 @@ Date: Tue, 17 Feb 2026 22:22:03 +1000 Subject: [PATCH 22/42] Fix mu-plugin loading during wp-env install - Change require_once to include_once for pub-sync/loader.php so missing private repo files emit warnings instead of fatals. - Add wp_installing() guard to wcorg_enforce_public_blog_option() to prevent calling get_site_meta() during initial install when multisite functions are not available yet. Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/load-other-mu-plugins.php | 4 +--- public_html/wp-content/mu-plugins/wcorg-misc.php | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index b70b7bbe1a..1285b2d0df 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -18,9 +18,7 @@ function wcorg_include_common_plugins() { // Include the public `wporg-mu-plugins` that are synced from Git to SVN. These are different than the // ones included in `wcorg_include_common_plugins()`. - if ( file_exists( dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php' ) ) { - require_once dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; - } + include_once dirname( __DIR__ ) . '/mu-plugins-private/wporg-mu-plugins/pub-sync/loader.php'; wcorg_include_individual_mu_plugins(); wcorg_include_mu_plugin_folders(); diff --git a/public_html/wp-content/mu-plugins/wcorg-misc.php b/public_html/wp-content/mu-plugins/wcorg-misc.php index c68c07f5b3..1fde2bcad7 100644 --- a/public_html/wp-content/mu-plugins/wcorg-misc.php +++ b/public_html/wp-content/mu-plugins/wcorg-misc.php @@ -57,6 +57,10 @@ function wcorg_close_comments_for_post_types( $post_types ) { * @return string */ function wcorg_enforce_public_blog_option() { + if ( wp_installing() ) { + return '1'; + } + if ( is_wordcamp_test_site() ) { $value = '0'; } else { From 374ded7d8746e210dad15cc22ec8af939ed70354 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:28:41 +1000 Subject: [PATCH 23/42] Skip jetpack-tweaks CSS module during WordPress install The Jetpack Custom CSS module references JETPACK__VERSION which is not available during wp core install since plugins load after mu-plugins. Co-Authored-By: Claude Opus 4.6 --- .../wp-content/mu-plugins/jetpack-tweaks/css-sanitization.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public_html/wp-content/mu-plugins/jetpack-tweaks/css-sanitization.php b/public_html/wp-content/mu-plugins/jetpack-tweaks/css-sanitization.php index 7c586e322d..90626035b2 100644 --- a/public_html/wp-content/mu-plugins/jetpack-tweaks/css-sanitization.php +++ b/public_html/wp-content/mu-plugins/jetpack-tweaks/css-sanitization.php @@ -7,6 +7,10 @@ defined( 'WPINC' ) || die(); +if ( wp_installing() ) { + return; +} + // Load the Jetpack Custom CSS Module that was removed in Jetpack 13.8. include_once __DIR__ . '/module-custom-css/custom-css.php'; From d0d1f207c7579f8052081bd25dc3d4d53edff8fd Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:36:12 +1000 Subject: [PATCH 24/42] Skip loading sub-folder mu-plugins during WordPress install During wp core install, mu-plugins load but many multisite functions are not yet available. Skip loading all sub-folder mu-plugins during install to prevent cascading fatal errors from init hooks. Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/load-other-mu-plugins.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index 1285b2d0df..b0429e4546 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -2,6 +2,10 @@ defined( 'WPINC' ) || die(); +if ( wp_installing() ) { + return; +} + wcorg_include_common_plugins(); wcorg_include_network_only_plugins(); From 0e722f55239e6c12f25a24a8a4fb4745c206efa7 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:43:25 +1000 Subject: [PATCH 25/42] Fix tests env: override DOMAIN_CURRENT_SITE to localhost The tests wp-env instance installs WordPress at localhost, not wordcamp.test. Override DOMAIN_CURRENT_SITE and blog IDs to match the default multisite install state. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index 9d4594e423..9edefe029d 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -73,7 +73,10 @@ "env": { "tests": { "config": { - "WP_TESTS_MULTISITE": 1 + "WP_TESTS_MULTISITE": 1, + "DOMAIN_CURRENT_SITE": "localhost", + "BLOG_ID_CURRENT_SITE": 1, + "WORDCAMP_ROOT_BLOG_ID": 1 }, "mappings": { "wp-content/mu-plugins": "./public_html/wp-content/mu-plugins", From 9d54bd1f8e1095498eeefc7fde61b6cc8d92b49b Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:49:26 +1000 Subject: [PATCH 26/42] Fix tests env: disable subdomain install for localhost SUBDOMAIN_INSTALL=true is incompatible with localhost domain. Override to false for the tests environment. Also fix retry logic to destroy the wp-env instance before retrying to avoid EACCES permission errors. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 2 +- .wp-env.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 1d4e646677..0a48af3bb0 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -90,7 +90,7 @@ jobs: run: echo '{ "phpVersion":"${{ matrix.php-version }}" }' > .wp-env.override.json - name: Start wp-env - run: npx wp-env start || (sleep 30 && npx wp-env start) + run: npx wp-env start || (npx wp-env destroy --yes && sleep 10 && npx wp-env start) - name: Running unit tests run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/.wp-env.json b/.wp-env.json index 9edefe029d..44cf163947 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -74,6 +74,7 @@ "tests": { "config": { "WP_TESTS_MULTISITE": 1, + "SUBDOMAIN_INSTALL": false, "DOMAIN_CURRENT_SITE": "localhost", "BLOG_ID_CURRENT_SITE": 1, "WORDCAMP_ROOT_BLOG_ID": 1 From 28b7d2f4b383919782c4eedc2608d16b19f88873 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 22:55:48 +1000 Subject: [PATCH 27/42] Move SUBDOMAIN_INSTALL to dev-only config The tests environment uses localhost which does not support subdomain multisite. Move SUBDOMAIN_INSTALL=true to the development env config so it only applies to dev, not tests. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 44cf163947..590860a6a8 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -31,7 +31,6 @@ "IS_WORDCAMP_NETWORK": true, "WP_ENVIRONMENT_TYPE": "local", "SUNRISE": true, - "SUBDOMAIN_INSTALL": true, "DOMAIN_CURRENT_SITE": "wordcamp.test", "PATH_CURRENT_SITE": "/", "SITE_ID_CURRENT_SITE": 1, @@ -71,10 +70,14 @@ "afterStart": "node .wp-env/setup.mjs" }, "env": { + "development": { + "config": { + "SUBDOMAIN_INSTALL": true + } + }, "tests": { "config": { "WP_TESTS_MULTISITE": 1, - "SUBDOMAIN_INSTALL": false, "DOMAIN_CURRENT_SITE": "localhost", "BLOG_ID_CURRENT_SITE": 1, "WORDCAMP_ROOT_BLOG_ID": 1 From f44a88910d5ffc326634b401c7c2bb1b30ce007e Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 17 Feb 2026 23:01:27 +1000 Subject: [PATCH 28/42] Explicitly set SUBDOMAIN_INSTALL=false at top level Ensure both environments default to path-based multisite. The dev environment overrides this to true for subdomain support. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.wp-env.json b/.wp-env.json index 590860a6a8..dd68200e58 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -31,6 +31,7 @@ "IS_WORDCAMP_NETWORK": true, "WP_ENVIRONMENT_TYPE": "local", "SUNRISE": true, + "SUBDOMAIN_INSTALL": false, "DOMAIN_CURRENT_SITE": "wordcamp.test", "PATH_CURRENT_SITE": "/", "SITE_ID_CURRENT_SITE": 1, @@ -78,6 +79,7 @@ "tests": { "config": { "WP_TESTS_MULTISITE": 1, + "SUBDOMAIN_INSTALL": false, "DOMAIN_CURRENT_SITE": "localhost", "BLOG_ID_CURRENT_SITE": 1, "WORDCAMP_ROOT_BLOG_ID": 1 From 01efebadfa3438b3f088d74dcef843e8878715fd Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 09:33:07 +1000 Subject: [PATCH 29/42] Disable multisite for tests env, use WP_TESTS_MULTISITE instead wp-env multisite install is incompatible with localhost domain when SUBDOMAIN_INSTALL is true. Since the WordPress PHPUnit test suite handles multisite via WP_TESTS_MULTISITE, disable wp-env multisite for the tests environment entirely. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index dd68200e58..f4ca87fa8e 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -31,7 +31,7 @@ "IS_WORDCAMP_NETWORK": true, "WP_ENVIRONMENT_TYPE": "local", "SUNRISE": true, - "SUBDOMAIN_INSTALL": false, + "SUBDOMAIN_INSTALL": true, "DOMAIN_CURRENT_SITE": "wordcamp.test", "PATH_CURRENT_SITE": "/", "SITE_ID_CURRENT_SITE": 1, @@ -71,16 +71,10 @@ "afterStart": "node .wp-env/setup.mjs" }, "env": { - "development": { - "config": { - "SUBDOMAIN_INSTALL": true - } - }, "tests": { + "multisite": false, "config": { "WP_TESTS_MULTISITE": 1, - "SUBDOMAIN_INSTALL": false, - "DOMAIN_CURRENT_SITE": "localhost", "BLOG_ID_CURRENT_SITE": 1, "WORDCAMP_ROOT_BLOG_ID": 1 }, From c1a84f4295b9f5231f07bf7dead8fc1c95fdab53 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 12:55:20 +1000 Subject: [PATCH 30/42] Move multisite config to env.development to fix tests env The top-level `multisite: true` was bleeding MULTISITE constant into the tests environment, causing database table errors during wp-env start. Moving multisite-specific settings (SUNRISE, SUBDOMAIN_INSTALL, DOMAIN_CURRENT_SITE, etc.) to env.development ensures the tests env gets a clean single-site install. Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index f4ca87fa8e..11f7a765f0 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,7 +1,7 @@ { "core": null, "phpVersion": "8.5", - "multisite": true, + "multisite": false, "port": 80, "testsPort": 8889, "plugins": [ @@ -30,19 +30,13 @@ "config": { "IS_WORDCAMP_NETWORK": true, "WP_ENVIRONMENT_TYPE": "local", - "SUNRISE": true, - "SUBDOMAIN_INSTALL": true, - "DOMAIN_CURRENT_SITE": "wordcamp.test", - "PATH_CURRENT_SITE": "/", "SITE_ID_CURRENT_SITE": 1, - "BLOG_ID_CURRENT_SITE": 5, "WORDCAMP_NETWORK_ID": 1, "WORDCAMP_ROOT_BLOG_ID": 5, "EVENTS_NETWORK_ID": 2, "EVENTS_ROOT_BLOG_ID": 47, "CAMPUS_NETWORK_ID": 3, "CAMPUS_ROOT_BLOG_ID": 47, - "NOBLOGREDIRECT": "http://central.wordcamp.test", "WP_DEBUG": true, "WP_DEBUG_LOG": true, "WP_DEBUG_DISPLAY": true, @@ -71,8 +65,18 @@ "afterStart": "node .wp-env/setup.mjs" }, "env": { + "development": { + "multisite": true, + "config": { + "SUNRISE": true, + "SUBDOMAIN_INSTALL": true, + "DOMAIN_CURRENT_SITE": "wordcamp.test", + "PATH_CURRENT_SITE": "/", + "BLOG_ID_CURRENT_SITE": 5, + "NOBLOGREDIRECT": "http://central.wordcamp.test" + } + }, "tests": { - "multisite": false, "config": { "WP_TESTS_MULTISITE": 1, "BLOG_ID_CURRENT_SITE": 1, From 8fe08f90ed7ad5f10ca96b3715e9a749cfb30feb Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 13:36:11 +1000 Subject: [PATCH 31/42] Guard mu-plugins against single-site test environment The tests env runs as single-site (with WP_TESTS_MULTISITE for PHPUnit). During wp-env wp config set commands, WordPress loads in single-site mode where multisite functions like get_site_meta() are unavailable. - Add is_multisite() check to load-other-mu-plugins.php - Add is_multisite() check to wcorg_enforce_public_blog_option() Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/load-other-mu-plugins.php | 2 +- public_html/wp-content/mu-plugins/wcorg-misc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php index b0429e4546..333ed574e4 100644 --- a/public_html/wp-content/mu-plugins/load-other-mu-plugins.php +++ b/public_html/wp-content/mu-plugins/load-other-mu-plugins.php @@ -2,7 +2,7 @@ defined( 'WPINC' ) || die(); -if ( wp_installing() ) { +if ( wp_installing() || ! is_multisite() ) { return; } diff --git a/public_html/wp-content/mu-plugins/wcorg-misc.php b/public_html/wp-content/mu-plugins/wcorg-misc.php index 1fde2bcad7..4dad2a66a0 100644 --- a/public_html/wp-content/mu-plugins/wcorg-misc.php +++ b/public_html/wp-content/mu-plugins/wcorg-misc.php @@ -57,7 +57,7 @@ function wcorg_close_comments_for_post_types( $post_types ) { * @return string */ function wcorg_enforce_public_blog_option() { - if ( wp_installing() ) { + if ( wp_installing() || ! is_multisite() ) { return '1'; } From a3e50160aa047d474c67a2d154f8cdcc07fc22da Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 13:43:07 +1000 Subject: [PATCH 32/42] Disable dev env multisite in CI to avoid localhost subdomain error Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 0a48af3bb0..99fd585e1a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -86,8 +86,8 @@ jobs: - name: Build JS assets run: yarn workspaces run build - - name: Configure wp-env PHP version - run: echo '{ "phpVersion":"${{ matrix.php-version }}" }' > .wp-env.override.json + - name: Configure wp-env for CI + run: echo '{ "phpVersion":"${{ matrix.php-version }}", "env":{ "development":{ "multisite":false } } }' > .wp-env.override.json - name: Start wp-env run: npx wp-env start || (npx wp-env destroy --yes && sleep 10 && npx wp-env start) From 7deccb2fe4c7dd88682e0aa21c094f5edf12ee38 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 13:50:41 +1000 Subject: [PATCH 33/42] Handle single-site gracefully in setup script and fix CI retry - setup.mjs: catch wp site list error in non-multisite environments - CI: fix wp-env destroy confirmation (pipe yes instead of --yes flag) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 2 +- .wp-env/setup.mjs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 99fd585e1a..05d3a6860e 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -90,7 +90,7 @@ jobs: run: echo '{ "phpVersion":"${{ matrix.php-version }}", "env":{ "development":{ "multisite":false } } }' > .wp-env.override.json - name: Start wp-env - run: npx wp-env start || (npx wp-env destroy --yes && sleep 10 && npx wp-env start) + run: npx wp-env start || (echo y | npx wp-env destroy && sleep 10 && npx wp-env start) - name: Running unit tests run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/.wp-env/setup.mjs b/.wp-env/setup.mjs index e722d001c1..c673ccc0ed 100644 --- a/.wp-env/setup.mjs +++ b/.wp-env/setup.mjs @@ -34,8 +34,14 @@ function sql( sql ) { return wp( `db query "${ sql.replace( /"/g, '\\"' ) }"` ); } -// Check if setup has already run by counting sites. -const siteCount = parseInt( wp( 'site list --format=count' ), 10 ); +// Check if multisite is available and if setup has already run. +let siteCount; +try { + siteCount = parseInt( wp( 'site list --format=count' ), 10 ); +} catch { + console.log( 'Multisite is not available. Skipping setup.' ); + process.exit( 0 ); +} if ( siteCount > 2 ) { console.log( `Setup already complete (${ siteCount } sites exist). Skipping.` ); From cf581e60036c763168c1df92e65521d4e4e6ec37 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 14:00:29 +1000 Subject: [PATCH 34/42] Simplify: top-level multisite with CI override Keep multisite:true and all config at top level for local dev. CI overrides multisite:false at top level - the multisite config constants in wp-config.php are harmless without MULTISITE defined. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 2 +- .wp-env.json | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 05d3a6860e..d199b91880 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -87,7 +87,7 @@ jobs: run: yarn workspaces run build - name: Configure wp-env for CI - run: echo '{ "phpVersion":"${{ matrix.php-version }}", "env":{ "development":{ "multisite":false } } }' > .wp-env.override.json + run: echo '{ "phpVersion":"${{ matrix.php-version }}", "multisite":false }' > .wp-env.override.json - name: Start wp-env run: npx wp-env start || (echo y | npx wp-env destroy && sleep 10 && npx wp-env start) diff --git a/.wp-env.json b/.wp-env.json index 11f7a765f0..341db20f55 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,7 +1,7 @@ { "core": null, "phpVersion": "8.5", - "multisite": false, + "multisite": true, "port": 80, "testsPort": 8889, "plugins": [ @@ -30,13 +30,19 @@ "config": { "IS_WORDCAMP_NETWORK": true, "WP_ENVIRONMENT_TYPE": "local", + "SUNRISE": true, + "SUBDOMAIN_INSTALL": true, + "DOMAIN_CURRENT_SITE": "wordcamp.test", + "PATH_CURRENT_SITE": "/", "SITE_ID_CURRENT_SITE": 1, + "BLOG_ID_CURRENT_SITE": 5, "WORDCAMP_NETWORK_ID": 1, "WORDCAMP_ROOT_BLOG_ID": 5, "EVENTS_NETWORK_ID": 2, "EVENTS_ROOT_BLOG_ID": 47, "CAMPUS_NETWORK_ID": 3, "CAMPUS_ROOT_BLOG_ID": 47, + "NOBLOGREDIRECT": "http://central.wordcamp.test", "WP_DEBUG": true, "WP_DEBUG_LOG": true, "WP_DEBUG_DISPLAY": true, @@ -65,17 +71,6 @@ "afterStart": "node .wp-env/setup.mjs" }, "env": { - "development": { - "multisite": true, - "config": { - "SUNRISE": true, - "SUBDOMAIN_INSTALL": true, - "DOMAIN_CURRENT_SITE": "wordcamp.test", - "PATH_CURRENT_SITE": "/", - "BLOG_ID_CURRENT_SITE": 5, - "NOBLOGREDIRECT": "http://central.wordcamp.test" - } - }, "tests": { "config": { "WP_TESTS_MULTISITE": 1, From 1022c422a0741b13cc0b3f4dbb268b0013f7977f Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 14:25:08 +1000 Subject: [PATCH 35/42] Move SUNRISE/SUBDOMAIN_INSTALL to lifecycle script WordPress's is_multisite() returns true if SUNRISE or SUBDOMAIN_INSTALL are defined (even as false), regardless of the MULTISITE constant. Having these in .wp-env.json config means they get added to wp-config.php for ALL environments via wp config set, making both dev and tests think they're multisite even when multisite:false is set via override. Moving these to the lifecycle script means they're only set when multisite is actually available (the script exits early for single-site). Co-Authored-By: Claude Opus 4.6 --- .wp-env.json | 5 ----- .wp-env/setup.mjs | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 341db20f55..c7c2fc307d 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -30,10 +30,6 @@ "config": { "IS_WORDCAMP_NETWORK": true, "WP_ENVIRONMENT_TYPE": "local", - "SUNRISE": true, - "SUBDOMAIN_INSTALL": true, - "DOMAIN_CURRENT_SITE": "wordcamp.test", - "PATH_CURRENT_SITE": "/", "SITE_ID_CURRENT_SITE": 1, "BLOG_ID_CURRENT_SITE": 5, "WORDCAMP_NETWORK_ID": 1, @@ -42,7 +38,6 @@ "EVENTS_ROOT_BLOG_ID": 47, "CAMPUS_NETWORK_ID": 3, "CAMPUS_ROOT_BLOG_ID": 47, - "NOBLOGREDIRECT": "http://central.wordcamp.test", "WP_DEBUG": true, "WP_DEBUG_LOG": true, "WP_DEBUG_DISPLAY": true, diff --git a/.wp-env/setup.mjs b/.wp-env/setup.mjs index c673ccc0ed..248f1a3006 100644 --- a/.wp-env/setup.mjs +++ b/.wp-env/setup.mjs @@ -50,6 +50,31 @@ if ( siteCount > 2 ) { console.log( 'Setting up WordCamp.org multi-network environment...' ); +// ────────────────────────────────────────────── +// 0. Set multisite constants that can't be in +// .wp-env.json because defined(SUNRISE) and +// defined(SUBDOMAIN_INSTALL) make WordPress's +// is_multisite() return true even without +// MULTISITE defined, breaking non-multisite envs. +// ────────────────────────────────────────────── + +console.log( 'Setting multisite constants...' ); +const constants = { + SUNRISE: 'true', + SUBDOMAIN_INSTALL: 'true', + DOMAIN_CURRENT_SITE: "'wordcamp.test'", + PATH_CURRENT_SITE: "'/'", + NOBLOGREDIRECT: "'http://central.wordcamp.test'", +}; + +for ( const [ name, value ] of Object.entries( constants ) ) { + try { + wp( `config set ${ name } ${ value } --raw --type=constant` ); + } catch { + // Constant may already exist. + } +} + // ────────────────────────────────────────────── // 1. Create additional networks via raw SQL. // wp-env creates network 1 automatically. From 81c1c26c594eb9c2ae1c636103b210872bdd545d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 14:31:54 +1000 Subject: [PATCH 36/42] Fix duplicate autoloader: use mu-plugins vendor path in bootstrap The vendor/ mapping and mu-plugins/vendor/ point to the same Docker mount. Using different paths causes require_once to load the autoloader twice. Also guard constant definitions to avoid warnings when wp-env has already set them via wp-config.php. Co-Authored-By: Claude Opus 4.6 --- phpunit-bootstrap.php | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php index 15f3e91912..cdd707bf50 100644 --- a/phpunit-bootstrap.php +++ b/phpunit-bootstrap.php @@ -4,23 +4,34 @@ $is_wp_env = str_starts_with( __DIR__, '/var/www/html' ); // Require composer dependencies. +// In wp-env, use the mu-plugins vendor path directly to avoid double-loading +// (the vendor/ mapping and mu-plugins/vendor/ are the same Docker mount). $vendor_autoload = $is_wp_env - ? '/var/www/html/vendor/autoload.php' + ? '/var/www/html/wp-content/mu-plugins/vendor/autoload.php' : __DIR__ . '/vendor/autoload.php'; if ( file_exists( $vendor_autoload ) ) { require_once $vendor_autoload; } -const WORDCAMP_ENVIRONMENT = 'local'; -const WORDCAMP_NETWORK_ID = 1; -const WORDCAMP_ROOT_BLOG_ID = 5; -const EVENTS_NETWORK_ID = 2; -const EVENTS_ROOT_BLOG_ID = 47; -const CAMPUS_NETWORK_ID = 3; -const CAMPUS_ROOT_BLOG_ID = 47; -const SITE_ID_CURRENT_SITE = WORDCAMP_NETWORK_ID; -const BLOG_ID_CURRENT_SITE = WORDCAMP_ROOT_BLOG_ID; +// Define constants only if not already set (wp-env sets these via wp-config.php). +$bootstrap_constants = array( + 'WORDCAMP_ENVIRONMENT' => 'local', + 'WORDCAMP_NETWORK_ID' => 1, + 'WORDCAMP_ROOT_BLOG_ID' => 5, + 'EVENTS_NETWORK_ID' => 2, + 'EVENTS_ROOT_BLOG_ID' => 47, + 'CAMPUS_NETWORK_ID' => 3, + 'CAMPUS_ROOT_BLOG_ID' => 47, + 'SITE_ID_CURRENT_SITE' => 1, + 'BLOG_ID_CURRENT_SITE' => 5, +); + +foreach ( $bootstrap_constants as $name => $value ) { + if ( ! defined( $name ) ) { + define( $name, $value ); + } +} if ( $is_wp_env ) { define( 'WP_PLUGIN_DIR', '/var/www/html/wp-content/plugins' ); From ca6f73e8bc8303df5a32d84881740c187541014d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 15:20:40 +1000 Subject: [PATCH 37/42] Fix autoloader conflict: remove vendor mapping, use mu-plugins path The vendor mapping created two paths to the same autoloader files, causing Composer's autoloader class to be declared twice. Instead of mapping vendor separately, invoke phpunit from its actual location in mu-plugins/vendor/bin/. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/unit-tests.yml | 2 +- .wp-env.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index d199b91880..72592002b0 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -93,4 +93,4 @@ jobs: run: npx wp-env start || (echo y | npx wp-env destroy && sleep 10 && npx wp-env start) - name: Running unit tests - run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/vendor/bin/phpunit -c phpunit.xml.dist + run: npx wp-env run tests-cli --env-cwd=/var/www/html /var/www/html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/.wp-env.json b/.wp-env.json index c7c2fc307d..e1c1167723 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -81,8 +81,7 @@ "wp-content/sunrise-events.php": "./public_html/wp-content/sunrise-events.php", "phpunit.xml.dist": "./phpunit.xml.dist", "phpunit-bootstrap.php": "./phpunit-bootstrap.php", - "phpunit-database-testcase.php": "./phpunit-database-testcase.php", - "vendor": "./public_html/wp-content/mu-plugins/vendor" + "phpunit-database-testcase.php": "./phpunit-database-testcase.php" } } } From 171075db98d2f95b9dc3a3eda35fa01c3c131066 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 15:27:11 +1000 Subject: [PATCH 38/42] Guard payments-network test bootstrap against missing private files The mu-plugins-private directory is not available in wp-env. Guard the require_once with file_exists to prevent fatal errors. Co-Authored-By: Claude Opus 4.6 --- .../plugins/wordcamp-payments-network/tests/bootstrap.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public_html/wp-content/plugins/wordcamp-payments-network/tests/bootstrap.php b/public_html/wp-content/plugins/wordcamp-payments-network/tests/bootstrap.php index 07e4df0b28..0d57a01e94 100755 --- a/public_html/wp-content/plugins/wordcamp-payments-network/tests/bootstrap.php +++ b/public_html/wp-content/plugins/wordcamp-payments-network/tests/bootstrap.php @@ -10,7 +10,10 @@ * Load the plugins that we'll need to be active for the tests. */ function manually_load_plugin() { - require_once SUT_WP_CONTENT_DIR . '/mu-plugins-private/wporg-mu-plugins/pub-sync/utilities/class-export-csv.php'; + $export_csv = SUT_WP_CONTENT_DIR . '/mu-plugins-private/wporg-mu-plugins/pub-sync/utilities/class-export-csv.php'; + if ( file_exists( $export_csv ) ) { + require_once $export_csv; + } require_once WP_PLUGIN_DIR . '/wordcamp-payments/includes/wordcamp-budgets.php'; require_once WP_PLUGIN_DIR . '/wordcamp-payments/includes/payment-request.php'; From 4ab3fa45d294e25d0a656e61b11edc7aa77b8568 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 15:33:04 +1000 Subject: [PATCH 39/42] Fix phpunit.xml.dist paths for wp-env container Inside the wp-env container, content is at /var/www/html/wp-content/ not /var/www/html/public_html/wp-content/. Remove the public_html/ prefix from all test directory paths. Co-Authored-By: Claude Opus 4.6 --- phpunit.xml.dist | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bdd212d86f..8f36919171 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,74 +16,74 @@ - ./public_html/wp-content/mu-plugins/tests/ + ./wp-content/mu-plugins/tests/ - ./public_html/wp-content/plugins/camptix/tests/ + ./wp-content/plugins/camptix/tests/ - ./public_html/wp-content/plugins/wordcamp-organizer-reminders/tests/ + ./wp-content/plugins/wordcamp-organizer-reminders/tests/ - ./public_html/wp-content/plugins/wordcamp-payments-network/tests/ + ./wp-content/plugins/wordcamp-payments-network/tests/ - ./public_html/wp-content/plugins/wc-post-types/tests/ + ./wp-content/plugins/wc-post-types/tests/ - ./public_html/wp-content/plugins/wcpt/tests/ + ./wp-content/plugins/wcpt/tests/ - ./public_html/wp-content/plugins/wordcamp-remote-css/tests/ + ./wp-content/plugins/wordcamp-remote-css/tests/ - ./public_html/wp-content/plugins/wordcamp-speaker-feedback/tests/ + ./wp-content/plugins/wordcamp-speaker-feedback/tests/ - ./public_html/wp-content/mu-plugins - ./public_html/wp-content/plugins/camptix - ./public_html/wp-content/plugins/wc-post-types - ./public_html/wp-content/plugins/wcpt - ./public_html/wp-content/plugins/wordcamp-organizer-reminders - ./public_html/wp-content/plugins/wordcamp-payments/ - ./public_html/wp-content/plugins/wordcamp-payments-network/ - ./public_html/wp-content/plugins/wordcamp-remote-css - ./public_html/wp-content/plugins/wordcamp-speaker-feedback - ./public_html/wp-content/sunrise.php - ./public_html/wp-content/sunrise-events.php - ./public_html/wp-content/sunrise-wordcamp.php + ./wp-content/mu-plugins + ./wp-content/plugins/camptix + ./wp-content/plugins/wc-post-types + ./wp-content/plugins/wcpt + ./wp-content/plugins/wordcamp-organizer-reminders + ./wp-content/plugins/wordcamp-payments/ + ./wp-content/plugins/wordcamp-payments-network/ + ./wp-content/plugins/wordcamp-remote-css + ./wp-content/plugins/wordcamp-speaker-feedback + ./wp-content/sunrise.php + ./wp-content/sunrise-events.php + ./wp-content/sunrise-wordcamp.php - ./public_html/wp-content/mu-plugins/tests - ./public_html/wp-content/mu-plugins/vendor - ./public_html/wp-content/plugins/*/tests - ./public_html/wp-content/plugins/*/node_modules + ./wp-content/mu-plugins/tests + ./wp-content/mu-plugins/vendor + ./wp-content/plugins/*/tests + ./wp-content/plugins/*/node_modules From f34694ef20270dfd0f0f18932dbf5c5821d12dac Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 16:14:46 +1000 Subject: [PATCH 40/42] Fix test failures: bootstrap constants, autoloader, domain, and test fixes - Skip bootstrap constants in wp-env (let wp-config.php set correct values) - Override WP_TESTS_DOMAIN/WP_SITEURL/WP_HOME to prevent wp-env port append - Add file_exists() guard in autoloader for missing private mu-plugins - Use dynamic table prefix in SubRoles test instead of hardcoded 'wptests_' - Set HTTP_HOST in sunrise-events test for get_redirect_url() Co-Authored-By: Claude Opus 4.6 --- phpunit-bootstrap.php | 43 ++++++++++++------- .../wp-content/mu-plugins/2-autoloader.php | 8 +++- .../mu-plugins/tests/test-sunrise-events.php | 2 + .../mu-plugins/tests/test-wcorg-subroles.php | 4 +- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php index cdd707bf50..165d61a8a9 100644 --- a/phpunit-bootstrap.php +++ b/phpunit-bootstrap.php @@ -14,26 +14,37 @@ require_once $vendor_autoload; } -// Define constants only if not already set (wp-env sets these via wp-config.php). -$bootstrap_constants = array( - 'WORDCAMP_ENVIRONMENT' => 'local', - 'WORDCAMP_NETWORK_ID' => 1, - 'WORDCAMP_ROOT_BLOG_ID' => 5, - 'EVENTS_NETWORK_ID' => 2, - 'EVENTS_ROOT_BLOG_ID' => 47, - 'CAMPUS_NETWORK_ID' => 3, - 'CAMPUS_ROOT_BLOG_ID' => 47, - 'SITE_ID_CURRENT_SITE' => 1, - 'BLOG_ID_CURRENT_SITE' => 5, -); - -foreach ( $bootstrap_constants as $name => $value ) { - if ( ! defined( $name ) ) { - define( $name, $value ); +// Define constants only if not already set. +// In wp-env, these come from wp-config.php (loaded later via wp-tests-config.php). +// Defining them here would override the wp-env values with wrong defaults. +if ( ! $is_wp_env ) { + $bootstrap_constants = array( + 'WORDCAMP_ENVIRONMENT' => 'local', + 'WORDCAMP_NETWORK_ID' => 1, + 'WORDCAMP_ROOT_BLOG_ID' => 5, + 'EVENTS_NETWORK_ID' => 2, + 'EVENTS_ROOT_BLOG_ID' => 47, + 'CAMPUS_NETWORK_ID' => 3, + 'CAMPUS_ROOT_BLOG_ID' => 47, + 'SITE_ID_CURRENT_SITE' => 1, + 'BLOG_ID_CURRENT_SITE' => 5, + ); + + foreach ( $bootstrap_constants as $name => $value ) { + if ( ! defined( $name ) ) { + define( $name, $value ); + } } } if ( $is_wp_env ) { + // Override wp-env defaults for the test domain. + // wp-env appends the tests port to WP_TESTS_DOMAIN (e.g. 'localhost:8889'), + // which causes the default test site to have an invalid domain. + define( 'WP_TESTS_DOMAIN', 'example.org' ); + define( 'WP_SITEURL', 'http://example.org' ); + define( 'WP_HOME', 'http://example.org' ); + define( 'WP_PLUGIN_DIR', '/var/www/html/wp-content/plugins' ); define( 'SUT_WP_CONTENT_DIR', '/var/www/html/wp-content/' ); } else { diff --git a/public_html/wp-content/mu-plugins/2-autoloader.php b/public_html/wp-content/mu-plugins/2-autoloader.php index 31c39d0ff3..4828ef8b0f 100644 --- a/public_html/wp-content/mu-plugins/2-autoloader.php +++ b/public_html/wp-content/mu-plugins/2-autoloader.php @@ -23,11 +23,15 @@ function autoload( $class_name ) { $file_name = str_replace( 'WordPressdotorg\MU_Plugins\Utilities\\', '', $class_name ); $file_name = str_replace( '_', '-', strtolower( $file_name ) ); - require_once( sprintf( + $file_path = sprintf( '%s/mu-plugins-private/wporg-mu-plugins/pub-sync/utilities/class-%s.php', WP_CONTENT_DIR, $file_name - ) ); + ); + + if ( file_exists( $file_path ) ) { + require_once( $file_path ); + } break; } } diff --git a/public_html/wp-content/mu-plugins/tests/test-sunrise-events.php b/public_html/wp-content/mu-plugins/tests/test-sunrise-events.php index 65e7c00b42..2f192eb06e 100644 --- a/public_html/wp-content/mu-plugins/tests/test-sunrise-events.php +++ b/public_html/wp-content/mu-plugins/tests/test-sunrise-events.php @@ -26,6 +26,8 @@ class Test_Sunrise_Events extends WP_UnitTestCase { * @dataProvider data_get_redirect_url */ public function test_get_redirect_url( $request_uri, $expected_url ) { + $_SERVER['HTTP_HOST'] = 'events.wordpress.test'; + $actual_url = get_redirect_url( $request_uri ); $this->assertSame( $expected_url, $actual_url ); diff --git a/public_html/wp-content/mu-plugins/tests/test-wcorg-subroles.php b/public_html/wp-content/mu-plugins/tests/test-wcorg-subroles.php index e8f80b36c6..30486ce129 100644 --- a/public_html/wp-content/mu-plugins/tests/test-wcorg-subroles.php +++ b/public_html/wp-content/mu-plugins/tests/test-wcorg-subroles.php @@ -51,7 +51,9 @@ public function test_user_with_additional_caps_cannot() { ) ); $user->add_cap( 'wordcamp_wrangle_wordcamps' ); - $usermeta = get_user_meta( $user->ID, 'wptests_capabilities', true ); + + global $wpdb; + $usermeta = get_user_meta( $user->ID, $wpdb->get_blog_prefix() . 'capabilities', true ); $this->assertTrue( $user->has_cap( 'read' ) ); $this->assertTrue( $usermeta['wordcamp_wrangle_wordcamps'] ); From d30e4d0b0f5520bf2c043d5208033c71f817fc81 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Wed, 18 Feb 2026 16:18:55 +1000 Subject: [PATCH 41/42] Fix PHPCS: remove parentheses from require_once statement Co-Authored-By: Claude Opus 4.6 --- public_html/wp-content/mu-plugins/2-autoloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public_html/wp-content/mu-plugins/2-autoloader.php b/public_html/wp-content/mu-plugins/2-autoloader.php index 4828ef8b0f..ff5a77e33c 100644 --- a/public_html/wp-content/mu-plugins/2-autoloader.php +++ b/public_html/wp-content/mu-plugins/2-autoloader.php @@ -30,7 +30,7 @@ function autoload( $class_name ) { ); if ( file_exists( $file_path ) ) { - require_once( $file_path ); + require_once $file_path; } break; } From b597fb35f21e07377c242e25c2ee777761a3b74a Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Thu, 12 Mar 2026 20:31:24 +1000 Subject: [PATCH 42/42] Resolve a bad merge. --- public_html/wp-content/sunrise.php | 1 + 1 file changed, 1 insertion(+) diff --git a/public_html/wp-content/sunrise.php b/public_html/wp-content/sunrise.php index 98573ead7c..813dbdfa6c 100644 --- a/public_html/wp-content/sunrise.php +++ b/public_html/wp-content/sunrise.php @@ -139,6 +139,7 @@ function get_domain_network_id( string $domain ): int { if ( ! defined( 'WORDCAMP_ENVIRONMENT' ) ) { define( 'WORDCAMP_ENVIRONMENT', 'local' ); return; +} /** * Look up the current URL for a site that was previously at the given domain/path.