From b9ac1860b2283ae9a27b532fe225c2bb50483045 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Mon, 20 Jul 2026 13:23:52 -0500 Subject: [PATCH 1/3] Plugin Directory: Fix the test environment so DB and REST tests can run. The test environment was missing three things the development environment already had, which between them made whole classes of test impossible: - `update_source` was never created, so anything touching the update API's table failed. - `wp-content/env-bin` was not mapped, so `database-tables.sql` was not reachable from the test container to import. - `PLUGINS_TABLE_PREFIX` was undefined, so `API\Base::load_routes()` fatalled and no `plugins/v1` route was registered. Shared verbatim with #717; whichever of the two PRs lands second should drop this commit. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01UNnLdnE7etihGNvsWY1qGE --- environments/plugin-directory/.wp-env.test.json | 6 ++++++ environments/plugin-directory/bin/after-start-test.sh | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/environments/plugin-directory/.wp-env.test.json b/environments/plugin-directory/.wp-env.test.json index 6bffb7da9c..145db9cad1 100644 --- a/environments/plugin-directory/.wp-env.test.json +++ b/environments/plugin-directory/.wp-env.test.json @@ -4,7 +4,13 @@ "plugins": [ "../wordpress.org/public_html/wp-content/plugins/plugin-directory" ], + "mappings": { + "wp-content/env-bin": "./plugin-directory/bin" + }, "lifecycleScripts": { "afterStart": "bash plugin-directory/bin/after-start-test.sh" + }, + "config": { + "PLUGINS_TABLE_PREFIX": "wp_" } } diff --git a/environments/plugin-directory/bin/after-start-test.sh b/environments/plugin-directory/bin/after-start-test.sh index 13d416d04a..b68d50afcb 100755 --- a/environments/plugin-directory/bin/after-start-test.sh +++ b/environments/plugin-directory/bin/after-start-test.sh @@ -1,7 +1,7 @@ #!/bin/bash # # Runs after wp-env start for the test environment. -# Installs PHPUnit 11 and Yoast polyfills in the test container. +# Installs PHPUnit 11 and Yoast polyfills, and creates the stub tables tests read. # CONFIG="--config plugin-directory/.wp-env.test.json" @@ -10,3 +10,7 @@ RUN="npx wp-env $CONFIG run tests-cli" echo "Installing PHPUnit 11 and polyfills..." $RUN composer global require -W phpunit/phpunit:^11.0 2>&1 $RUN composer require --dev yoast/phpunit-polyfills:^4.0 --working-dir=/wordpress-phpunit 2>&1 + +# Create stub database tables that exist outside WordPress on production. +echo "Creating stub database tables..." +$RUN -- wp db import wp-content/env-bin/database-tables.sql From ee1dd058ee0ae6ce0fa773be3152e8b124d2a501 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 22 Jul 2026 13:46:10 -0500 Subject: [PATCH 2/3] Plugin Directory: Let reviewers block a release from being served. Reviewers could already skip the release cooldown, but had no way to hold a release back short of closing the whole plugin. Add a Block control to the Controls metabox, alongside the force-release one and sharing a single reason field, so a version still inside its cooldown can be held out of `update_source` until it's force-released. - API_Update_Updater::block_release() is the counterpart to force_release(): it records a `release_block` on the release row, and a single gate in update_single_plugin() honours it, so both the deferred cooldown cron and the backup reconciliation cron keep the version out of `update_source`. - A version that's already live can't be un-shipped, so a block on one is refused rather than recorded as a hold that never takes effect. - force_release() clears the block and notes the override in the audit log. Holding a version back means update_single_plugin() can't rewrite the row at all, since every version-specific column describes the held version. That was already true of the cooldown, and it means a status change never reached sites while one was in flight: closing a plugin whose next version was deferred left `update_source` serving the current version with `available = 1`. A block makes that unbounded rather than a few hours, so sync_availability() now writes the availability and closure fields on their own and leaves the served version alone. Covered by Reviewer_Release_Block_Test, which exercises the block through the same path the metabox uses, including closing and re-opening a plugin while a version is held. Its fixture lives in Release_Block_Test_Case, resolved by a small autoloader in the test bootstrap. Co-Authored-By: Claude Opus 4.8 (1M context) --- phpcs.xml.dist | 10 + .../admin/metabox/class-controls.php | 84 ++-- .../class-plugin-directory.php | 9 + .../jobs/class-api-update-updater.php | 221 +++++++++-- .../plugins/plugin-directory/phpunit.xml | 1 + .../tests/Release_Block_Test_Case.php | 224 +++++++++++ .../tests/Reviewer_Release_Block_Test.php | 362 ++++++++++++++++++ .../plugin-directory/tests/bootstrap.php | 17 + 8 files changed, 876 insertions(+), 52 deletions(-) create mode 100644 wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Release_Block_Test_Case.php create mode 100644 wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Reviewer_Release_Block_Test.php diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 447c8ac037..c4aeb02ab3 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -138,4 +138,14 @@ + + + + + + + + + diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-controls.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-controls.php index 3d85569f28..2126922bbc 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-controls.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-controls.php @@ -46,11 +46,12 @@ static function display() { } /** - * Display the release cooldown status and (for reviewers) a force-release control. + * Display the release hold status and (for reviewers) force-release and block controls. * - * Bails when there's no current release to gate, when the release has no cooldown - * delay (feature off at release-creation, or already force-released), or when the - * cooldown window has elapsed. + * Shows one of two messages: a countdown while a release is cooling down, or a block + * notice when the release is being held (which outlasts the cooldown window). Reviewers + * can force-release either way, and can block a version that's still cooling down. Bails + * when there's no current release, or when it's neither held nor still cooling down. */ protected static function display_release_cooldown() { $post = get_post(); @@ -65,13 +66,13 @@ protected static function display_release_cooldown() { return; } - $release_delay = (int) ( $release['release_delay'] ?? 0 ); - if ( ! $release_delay ) { - return; - } - + $blocked = API_Update_Updater::is_release_blocked( $release ); + $release_delay = (int) ( $release['release_delay'] ?? 0 ); $cooldown_until = API_Update_Updater::compute_release_time( $post, $release ) + $release_delay; - if ( $cooldown_until <= time() ) { + $in_cooldown = $release_delay && $cooldown_until > time(); + + // Nothing to surface unless the release is held or still cooling down. + if ( ! $blocked && ! $in_cooldown ) { return; } @@ -79,21 +80,29 @@ protected static function display_release_cooldown() {

- +