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. *