diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 6e8fcf95a..0e767ed6d 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -57,17 +57,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
@@ -76,26 +65,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 for CI
+ 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)
- 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/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist
diff --git a/.gitignore b/.gitignore
index ffbd5f950..01e410708 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,13 @@
docker-compose.override.yaml
+#
+# wp-env
+#
+.wp-env-home
+.wp-env.override.json
+
+
#
# Misc
#
diff --git a/.wp-env.json b/.wp-env.json
new file mode 100644
index 000000000..e1c116772
--- /dev/null
+++ b/.wp-env.json
@@ -0,0 +1,88 @@
+{
+ "core": null,
+ "phpVersion": "8.5",
+ "multisite": true,
+ "port": 80,
+ "testsPort": 8889,
+ "plugins": [
+ "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": [
+ "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,
+ "WP_ENVIRONMENT_TYPE": "local",
+ "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,
+ "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",
+ "WORDCAMP_ENVIRONMENT": "local"
+ },
+ "mappings": {
+ "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",
+ "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,
+ "BLOG_ID_CURRENT_SITE": 1,
+ "WORDCAMP_ROOT_BLOG_ID": 1
+ },
+ "mappings": {
+ "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",
+ "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"
+ }
+ }
+ }
+}
diff --git a/.wp-env/0-early-mu-plugin.php b/.wp-env/0-early-mu-plugin.php
new file mode 100644
index 000000000..b8a8eed6f
--- /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/.wp-env/setup.mjs b/.wp-env/setup.mjs
new file mode 100644
index 000000000..248f1a300
--- /dev/null
+++ b/.wp-env/setup.mjs
@@ -0,0 +1,214 @@
+/**
+ * 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 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.` );
+ process.exit( 0 );
+}
+
+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.
+// ──────────────────────────────────────────────
+
+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 72f243076..2e9e1b4fd 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": [
@@ -39,187 +49,6 @@
{
"type": "package",
"package": [
- {
- "name": "wordpress-plugin/akismet",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/akismet.latest-stable.zip"
- }
- },
- {
- "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-plugin/classic-editor",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/classic-editor.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/custom-content-width",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/custom-content-width.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/edit-flow",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/edit-flow.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/email-post-changes",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/email-post-changes.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/gutenberg",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/jetpack",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/jetpack.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/liveblog",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/liveblog.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/public-post-preview",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/public-post-preview.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/pwa",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/pwa.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/tagregator",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/tagregator.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/wordpress-importer",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wordpress-importer.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/wp-cldr",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wp-cldr.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-plugin/wp-super-cache",
- "type": "wordpress-plugin",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wp-super-cache.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-theme/p2",
- "type": "wordpress-theme",
- "version": "1.5.8",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/theme/p2.1.5.8.zip"
- }
- },
- {
- "name": "wordpress-theme/twentyten",
- "type": "wordpress-theme",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/theme/twentyten.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-theme/twentytwentytwo",
- "type": "wordpress-theme",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/theme/twentytwentytwo.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-theme/twentytwentythree",
- "type": "wordpress-theme",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/theme/twentytwentythree.latest-stable.zip"
- }
- },
- {
- "name": "wordpress-theme/twentytwentyfour",
- "type": "wordpress-theme",
- "version": "999",
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/theme/twentytwentyfour.latest-stable.zip"
- }
- },
{
"name": "wordpress-meta/wporg-profiles-wp-activity-notifier",
"type": "wordpress-plugin",
@@ -247,28 +76,8 @@
"spatie/phpunit-watcher": "^1.23",
"yoast/phpunit-polyfills": "^4.0",
"composer/installers": "^2.2",
- "wordpress-plugin/akismet": "*",
- "wordpress-plugin/bbpress": "*",
- "wordpress-plugin/classic-editor": "*",
- "wordpress-plugin/custom-content-width": "*",
- "wordpress-plugin/edit-flow": "*",
- "wordpress-plugin/email-post-changes": "*",
- "wordpress-plugin/gutenberg": "*",
- "wordpress-plugin/jetpack": "*",
- "wordpress-plugin/liveblog": "*",
- "wordpress-plugin/public-post-preview": "*",
- "wordpress-plugin/pwa": "*",
- "wordpress-plugin/tagregator": "*",
- "wordpress-plugin/wordpress-importer": "*",
- "wordpress-plugin/wp-cldr": "*",
- "wordpress-plugin/wp-super-cache": "*",
- "wordpress-meta/wporg-profiles-wp-activity-notifier": "*",
+ "wordpress-meta/wporg-profiles-wp-activity-notifier": "1.1",
"wporg/wporg-mu-plugins": "dev-build",
- "wordpress-theme/p2": "*",
- "wordpress-theme/twentyten": "*",
- "wordpress-theme/twentytwentytwo": "*",
- "wordpress-theme/twentytwentythree": "*",
- "wordpress-theme/twentytwentyfour": "*",
"quickbooks/v3-php-sdk": "*"
},
"scripts": {
@@ -280,7 +89,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",
diff --git a/package.json b/package.json
index bb700d330..90569290e 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 /var/www/html/vendor/bin/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 1e9de559c..165d61a8a 100644
--- a/phpunit-bootstrap.php
+++ b/phpunit-bootstrap.php
@@ -1,27 +1,67 @@
'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,
+ );
-define( 'WP_PLUGIN_DIR', __DIR__ . '/public_html/wp-content/plugins' );
-define( 'SUT_WP_CONTENT_DIR', __DIR__ . '/public_html/wp-content/' ); // WP_CONTENT_DIR will be in `WP_TESTS_DIR`.
-define( 'SUT_WPMU_PLUGIN_DIR', SUT_WP_CONTENT_DIR . '/mu-plugins' ); // WPMU_PLUGIN_DIR will be in `WP_TESTS_DIR`.
+ 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 {
+ define( 'WP_PLUGIN_DIR', __DIR__ . '/public_html/wp-content/plugins' );
+ define( 'SUT_WP_CONTENT_DIR', __DIR__ . '/public_html/wp-content/' ); // WP_CONTENT_DIR will be in `WP_TESTS_DIR`.
+}
+define( 'SUT_WPMU_PLUGIN_DIR', SUT_WP_CONTENT_DIR . 'mu-plugins' ); // WPMU_PLUGIN_DIR will be in `WP_TESTS_DIR`.
$core_tests_directory = getenv( 'WP_TESTS_DIR' );
if ( ! $core_tests_directory ) {
- $core_tests_directory = rtrim( sys_get_temp_dir(), '/\\' ) . '/wp/wordpress-tests-lib';
+ if ( $is_wp_env && is_dir( '/wordpress-phpunit' ) ) {
+ $core_tests_directory = '/wordpress-phpunit';
+ } else {
+ $core_tests_directory = rtrim( sys_get_temp_dir(), '/\\' ) . '/wp/wordpress-tests-lib';
+ }
+
// Necessary for the CampTix tests.
putenv( "WP_TESTS_DIR=$core_tests_directory" );
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index bdd212d86..8f3691917 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
diff --git a/public_html/wp-content/mu-plugins/2-autoloader.php b/public_html/wp-content/mu-plugins/2-autoloader.php
index 31c39d0ff..ff5a77e33 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/jetpack-tweaks/css-sanitization.php b/public_html/wp-content/mu-plugins/jetpack-tweaks/css-sanitization.php
index 7c586e322..90626035b 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';
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 14a5394f3..333ed574e 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() || ! is_multisite() ) {
+ return;
+}
+
wcorg_include_common_plugins();
wcorg_include_network_only_plugins();
@@ -18,7 +22,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()`.
- 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/tests/test-sunrise-events.php b/public_html/wp-content/mu-plugins/tests/test-sunrise-events.php
index 71550d165..4d967a9ce 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 Database_TestCase {
* @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 e8f80b36c..30486ce12 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'] );
diff --git a/public_html/wp-content/mu-plugins/wcorg-misc.php b/public_html/wp-content/mu-plugins/wcorg-misc.php
index 94a452203..cb15927a4 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() || ! is_multisite() ) {
+ return '1';
+ }
+
if ( is_wordcamp_test_site() ) {
$value = '0';
} else {
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 07e4df0b2..0d57a01e9 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';
diff --git a/public_html/wp-content/sunrise-events.php b/public_html/wp-content/sunrise-events.php
index 3548f302b..98b67e196 100644
--- a/public_html/wp-content/sunrise-events.php
+++ b/public_html/wp-content/sunrise-events.php
@@ -31,7 +31,7 @@ function main() {
* Get the URL to redirect to, if any.
*/
function get_redirect_url( string $request_uri ): string {
- $domain = 'events.wordpress.' . get_top_level_domain();
+ $domain = strtolower( strtok( $_SERVER['HTTP_HOST'] ?? '', ':' ) );
$old_full_url = sprintf(
'https://%s/%s',
$domain,
@@ -68,8 +68,11 @@ function get_redirect_url( string $request_uri ): string {
function set_network_and_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( SITE_ID_CURRENT_SITE );
+ $current_site = WP_Network::get_instance( $network_id );
$site_id = $current_site->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 );
if ( $current_blog && '/' === $current_blog->path ) {
// We found the root site, not a matching site.
@@ -96,9 +99,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 f2dae1db9..64b008770 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 b67316c2e..813dbdfa6 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:
@@ -131,6 +134,13 @@ 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. Return early until it's set.
+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.
*