diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 0000000000..8ccd9b9c76 --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,20 @@ +name: wordcamp +type: php +docroot: public_html/mu +php_version: "8.5" +webserver_type: nginx-fpm +database: + type: mariadb + version: "10.11" +disable_settings_management: true +additional_fqdns: + - wordcamp.test + - central.wordcamp.test + - seattle.wordcamp.test + - shinynew.wordcamp.test + - narnia.wordcamp.test + - events.wordpress.test + - campus.wordpress.test +hooks: + post-start: + - exec: bash /var/www/html/.ddev/setup.sh diff --git a/.ddev/nginx/multisite.conf b/.ddev/nginx/multisite.conf new file mode 100644 index 0000000000..1794c15846 --- /dev/null +++ b/.ddev/nginx/multisite.conf @@ -0,0 +1,28 @@ +# Multisite rewrite rules for WordCamp.org +# Adapted from .docker/config/nginx.conf + +# Rewrite ms-files for uploaded media. +rewrite ^/files/(.*)$ /wp-includes/ms-files.php?file=$1; + +# Rewrite multisite subdirectory paths (e.g., /2023/wp-admin/). +if (!-e $request_filename) { + rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last; + rewrite ^/[_0-9a-zA-Z-]+.*(/wp-admin/.*\.php)$ $1 last; + rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last; + rewrite "/\d{4}/files/(.+)$" /wp-includes/ms-files.php?file=$1 last; +} + +# Events network 3-level path rewrites (e.g., /rome/2024/training/wp-admin/). +if ( $host = "events.wordpress.test" ) { + rewrite "/[\w-]+/\d{4}/[\w-]+/wp-(admin|content|includes)(.*)" /wp-$1$2 last; + rewrite "/[\w-]+/\d{4}/[\w-]+/wp-(.*).php(.*)" /wp-$1.php$2 last; + rewrite "/[\w-]+/\d{4}/[\w-]+/xmlrpc\.php" /xmlrpc.php last; + rewrite "/[\w-]+/\d{4}/[\w-]+/files/(.+)$" /wp-includes/ms-files.php?file=$1 last; +} + +# Serve wp-content from the parent directory (public_html/, not public_html/mu/). +location ~* /wp-content/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ { + root /var/www/html/public_html; + expires max; + log_not_found off; +} diff --git a/.ddev/setup.sh b/.ddev/setup.sh new file mode 100755 index 0000000000..903a1212e2 --- /dev/null +++ b/.ddev/setup.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Idempotent DDEV post-start setup script for WordCamp.org. +# Runs inside the web container after `ddev start`. +# + +set -e + +PROJECT_ROOT="/var/www/html" +WP_DIR="$PROJECT_ROOT/public_html/mu" +WP_CONFIG="$PROJECT_ROOT/public_html/wp-config.php" +SEED_SQL="$PROJECT_ROOT/.docker/data/wordcamp_dev.sql" + +# 1. Download WordPress core if not present. +if [ ! -f "$WP_DIR/wp-includes/version.php" ]; then + echo "Downloading WordPress core..." + mkdir -p "$WP_DIR" + wp core download --path="$WP_DIR" --skip-content +fi + +# 2. Copy wp-config.php if not present. +if [ ! -f "$WP_CONFIG" ]; then + echo "Copying wp-config.php..." + cp "$PROJECT_ROOT/.ddev/wp-config-ddev.php" "$WP_CONFIG" +fi + +# 3. Import seed database if tables don't exist. +if ! wp db check --path="$WP_DIR" 2>/dev/null | grep -q "wc_options"; then + if [ -f "$SEED_SQL" ]; then + echo "Importing seed database..." + wp db import "$SEED_SQL" --path="$WP_DIR" + else + echo "Warning: Seed database not found at $SEED_SQL" + fi +fi + +# 4. Create placeholder build files for mu-plugins/blocks. +BLOCKS_DIR="$PROJECT_ROOT/public_html/wp-content/mu-plugins/blocks/source" +if [ -d "$BLOCKS_DIR" ]; then + for dir in "$BLOCKS_DIR"/*/; do + block_name=$(basename "$dir") + build_dir="$PROJECT_ROOT/public_html/wp-content/mu-plugins/blocks/build/$block_name" + if [ ! -d "$build_dir" ]; then + mkdir -p "$build_dir" + echo '(()=>{"use strict";})();' > "$build_dir/index.js" + echo '{"apiVersion":2}' > "$build_dir/block.json" + fi + done +fi + +# 5. Install WP test suite for PHPUnit. +WP_TESTS_DIR="/tmp/wp/wordpress-tests-lib" +if [ ! -d "$WP_TESTS_DIR/includes" ]; then + echo "Installing WordPress test suite..." + bash "$PROJECT_ROOT/.docker/bin/install-wp-tests.sh" wcorg_test db db db latest +fi + +echo "DDEV setup complete!" diff --git a/.ddev/web-build/Dockerfile b/.ddev/web-build/Dockerfile new file mode 100644 index 0000000000..208e9503a7 --- /dev/null +++ b/.ddev/web-build/Dockerfile @@ -0,0 +1 @@ +RUN apt-get update && apt-get install -y subversion && rm -rf /var/lib/apt/lists/* diff --git a/.ddev/wp-config-ddev.php b/.ddev/wp-config-ddev.php new file mode 100644 index 0000000000..881262c910 --- /dev/null +++ b/.ddev/wp-config-ddev.php @@ -0,0 +1,227 @@ + array( + 'wordcamp_wrangler', + 'meetup_wrangler', + 'mentor_manager', + 'report_viewer', + ), +); + +/* + * Third party services + */ +define( 'SLACK_ERROR_REPORT_URL', '(Optional) you can configure slack and add your webhook url. Errors will be posted to this slack url' ); +define( 'WORDCAMP_LOGS_SLACK_CHANNEL', '(Optional) @your_slack_username' ); +define( 'WORDCAMP_LOGS_JETPACK_SLACK_CHANNEL', '(Optional) @your_slack_username' ); +define( 'WORDCAMP_LOGS_GUTENBERG_SLACK_CHANNEL', '(Optional) @your_slack_username' ); + +define( 'TWITTER_CONSUMER_KEY_WORDCAMP_CENTRAL', '' ); +define( 'TWITTER_CONSUMER_SECRET_WORDCAMP_CENTRAL', '' ); +define( 'TWITTER_BEARER_TOKEN_WORDCAMP_CENTRAL', '' ); + +define( 'WORDCAMP_QBO_HMAC_KEY', 'localhmac' ); + +define( 'WORDCAMP_SANDBOX_QBO_CLIENT_ID', "(Optional) your app's development client ID key goes here" ); +define( 'WORDCAMP_SANDBOX_QBO_CLIENT_SECRET', "(Optional) your app's development client secret key goes here" ); +define( 'WORDCAMP_PRODUCTION_QBO_CLIENT_ID', '' ); +define( 'WORDCAMP_PRODUCTION_QBO_CLIENT_SECRET', '' ); + +define( 'REMOTE_CSS_GITHUB_ID', '' ); +define( 'REMOTE_CSS_GITHUB_SECRET', '' ); + +define( 'WORDCAMP_LE_HELPER_API_KEY', '' ); +define( 'GENDERIZE_IO_API_KEY', '' ); + +define( 'WC_SANDBOX_PAYPAL_NVP_USERNAME', '' ); +define( 'WC_SANDBOX_PAYPAL_NVP_PASSWORD', '' ); +define( 'WC_SANDBOX_PAYPAL_NVP_SIGNATURE', '' ); +define( 'WPCS_PAYPAL_NVP_USERNAME', '' ); +define( 'WPCS_PAYPAL_NVP_PASSWORD', '' ); +define( 'WPCS_PAYPAL_NVP_SIGNATURE', '' ); + +define( 'WORDCAMP_CAMPTIX_STRIPE_TEST_PUBLIC', '' ); +define( 'WORDCAMP_CAMPTIX_STRIPE_TEST_SECRET', '' ); +define( 'WORDCAMP_CAMPTIX_STRIPE_LIVE_PUBLIC', '' ); +define( 'WORDCAMP_CAMPTIX_STRIPE_LIVE_SECRET', '' ); + +define( 'WORDCAMP_PAYMENTS_ENCRYPTION_KEY', '' ); +define( 'WORDCAMP_PAYMENTS_HMAC_KEY', '' ); +define( 'WORDCAMP_PAYMENT_STRIPE_HMAC', '' ); +define( 'WORDCAMP_PAYMENT_STRIPE_PUBLISHABLE', '' ); +define( 'WORDCAMP_PAYMENT_STRIPE_SECRET', '' ); +define( 'WORDCAMP_PAYMENT_STRIPE_PUBLISHABLE_LIVE', '' ); +define( 'WORDCAMP_PAYMENT_STRIPE_SECRET_LIVE', '' ); + +define( 'WORDCAMP_PROD_GOOGLE_MAPS_API_KEY', '' ); +define( 'WORDCAMP_DEV_GOOGLE_MAPS_API_KEY', '' ); + +define( 'MEETUP_API_BASE_URL', 'https://api.meetup.com/' ); +define( 'MEETUP_MEMBER_ID', 72560962 ); +define( 'MEETUP_OAUTH_CONSUMER_KEY', '' ); +define( 'MEETUP_OAUTH_CONSUMER_SECRET', '' ); +define( 'MEETUP_OAUTH_CONSUMER_REDIRECT_URI', '' ); +define( 'MEETUP_USER_EMAIL', '' ); +define( 'MEETUP_USER_PASSWORD', '' ); + +define( 'WORDCAMP_FIXER_API_KEY', '' ); +define( 'WORDCAMP_OXR_API_KEY', '' ); + + +/* + * WP Super Cache + */ +define( 'WP_CACHE', true ); +define( 'WPCACHEHOME', WP_CONTENT_DIR . '/plugins/wp-super-cache/' ); +define( 'SUBMITDISABLED', false ); // Work around https://github.com/Automattic/wp-super-cache/issues/213. +define( 'WP_CACHE_PAGE_SECRET', '' ); +define( 'WP_CACHE_DEBUG_USERNAME', '' ); + + +/* + * Bootstrap WordPress + */ +if ( ! defined('ABSPATH') ) { + define( 'ABSPATH', __DIR__ . '/mu/' ); +} + +require_once ABSPATH . 'wp-settings.php'; diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index dbec3deb39..d71cce7e5c 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,33 @@ 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: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} 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 - - name: Install WordPress - run: | - bash .docker/bin/install-wp-tests.sh wcorg_test root root 127.0.0.1 latest + - name: Setup DDEV + uses: ddev/github-action-setup-ddev@v1 + with: + autostart: false + + - name: Set PHP version + run: ddev config --php-version=${{ matrix.php-version }} + + - name: Start DDEV + run: ddev start + + - name: Install WP test suite + run: ddev exec bash .docker/bin/install-wp-tests.sh wcorg_test root root db latest - name: Running unit tests - run: ./public_html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist + run: ddev exec ./public_html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist diff --git a/.gitignore b/.gitignore index ffbd5f9501..ece43d13b1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,16 @@ docker-compose.override.yaml +# +# DDEV +# +.ddev/.ddev-docker-compose-* +.ddev/db_snapshots +.ddev/.global_commands +.ddev/.homeadditions +.ddev/.sso-config + + # # Misc # diff --git a/composer.json b/composer.json index f5ad46ed73..e97ef87701 100644 --- a/composer.json +++ b/composer.json @@ -129,7 +129,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", @@ -143,14 +142,11 @@ "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": "*", @@ -165,6 +161,7 @@ "test:watch:group": "phpunit-watcher watch --group=", "_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:ddev": "ddev exec ./public_html/wp-content/mu-plugins/vendor/bin/phpunit -c phpunit.xml.dist", "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", "phpcs-changed": "BASE_REF=production php .github/bin/phpcs-branch.php ", diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php index 1e9de559c9..4f8a2d8074 100644 --- a/phpunit-bootstrap.php +++ b/phpunit-bootstrap.php @@ -1,10 +1,18 @@ '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, + + // 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 ) { + if ( ! defined( $name ) ) { + define( $name, $value ); + } +} 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/sunrise-events.php b/public_html/wp-content/sunrise-events.php index 53176932b5..eec8482ac5 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 ); } 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: