Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/core/class-updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
/**
* Plugin update routines.
*
* Runs on every page load; cheap when nothing's changed (the version compare
* short-circuits). Each migration is gated on a version bump so it executes
* exactly once per upgrade path.
* Runs on every page load; cheap when nothing's changed because `run()` bails
* as soon as the recorded version matches this build. Each migration is gated
* on a version bump so it executes at most once per upgrade path.
*
* @package BeyondWords\Core
* @since 3.0.0
Expand All @@ -27,11 +27,24 @@ class Updater {
/**
* Run any pending migrations and update the recorded plugin version.
*
* Always runs — version checks inside skip the migrations on no-op upgrades.
* Bails immediately once the recorded version matches this build, so the
* migrations run at most once per version change instead of on every
* request. That guard — not the per-migration `version_compare` gates — is
* what makes the common path cheap: a pre-release build such as
* `7.0.0-beta.1` compares `< 7.0.0`, so those gates never close on it and
* would otherwise re-run the v7 migrations (~40 uncached queries plus a slow
* postmeta JOIN) on every page load, front end included.
*/
public static function run(): void {
$version = get_option( 'beyondwords_version', '1.0.0' );

// Already up to date: nothing to migrate, and the version write below
// would be a no-op. Bail before touching the database. This is the hot
// path — it runs on every request once the plugin has booted once.
if ( BEYONDWORDS__PLUGIN_VERSION === $version ) {
return;
}

if ( version_compare( $version, '3.0.0', '<' ) ) {
self::migrate_settings();
}
Expand Down Expand Up @@ -61,8 +74,9 @@ public static function run(): void {
* `[ 'mode' => 'all' ]` or `[ 'mode' => 'terms', 'terms' => [...] ]`.
*
* Reuses the tolerant readers on `Preselect`, so it is idempotent: already
* mode-based values pass through unchanged. The `-dev` plugin version keeps
* the `< 7.0.0` gate firing on every load, so the no-op path matters.
* mode-based values pass through unchanged. Pre-release builds compare
* `< 7.0.0`, so this can re-run once on each dev-version bump — idempotency
* keeps those re-runs safe.
*
* @since 7.0.0
*/
Expand Down
55 changes: 55 additions & 0 deletions tests/phpunit/core/test-updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,59 @@ public function migrate_disabled_to_embed_none()
wp_delete_post($embedPost, true);
wp_delete_post($untouchedPost, true);
}

/**
* The migration gate must close once the recorded version matches this
* build — otherwise the pre-release `< 7.0.0` comparison keeps the v7
* migrations firing on every request.
*
* @test
*/
public function run_is_a_noop_once_the_recorded_version_matches_this_build()
{
// Simulate an install that has already booted this exact build.
update_option('beyondwords_version', BEYONDWORDS__PLUGIN_VERSION);

// A post still carrying the legacy opt-out flag. If run() re-executed
// the v7 block it would strip this flag and set Embed = None; the
// version guard must leave it untouched.
$post = self::factory()->post->create();
update_post_meta($post, 'beyondwords_disabled', '1');

Updater::run();

// Migration did not run: legacy flag preserved, no Embed written.
$this->assertSame('1', get_post_meta($post, 'beyondwords_disabled', true));
$this->assertSame('', get_post_meta($post, 'beyondwords_embed', true));

wp_delete_post($post, true);
delete_option('beyondwords_version');
}

/**
* When the recorded version predates a migration, run() must execute it and
* normalise the stored version to this build.
*
* @test
*/
public function run_migrates_and_records_the_version_when_outdated()
{
update_option('beyondwords_version', '6.0.0');

$post = self::factory()->post->create();
update_post_meta($post, 'beyondwords_disabled', '1');

Updater::run();

// v7 migration ran: legacy flag converted to Embed = None and removed.
$this->assertSame('none', get_post_meta($post, 'beyondwords_embed', true));
$this->assertSame('', get_post_meta($post, 'beyondwords_disabled', true));

// Stored version normalised to this build, so the next run() is a no-op.
$this->assertSame(BEYONDWORDS__PLUGIN_VERSION, get_option('beyondwords_version'));

wp_delete_post($post, true);
delete_option('beyondwords_version');
delete_option('beyondwords_date_activated');
}
}
Loading