Skip to content

feat: hold back the Premium update until matching Free is available - #23373

Open
enricobattocchi wants to merge 1 commit into
trunkfrom
hold-back-premium-update-until-free-available
Open

feat: hold back the Premium update until matching Free is available#23373
enricobattocchi wants to merge 1 commit into
trunkfrom
hold-back-premium-update-until-free-available

Conversation

@enricobattocchi

@enricobattocchi enricobattocchi commented Jun 18, 2026

Copy link
Copy Markdown
Member

Context

Yoast SEO Premium and Yoast SEO (Free) are released together and are built to match: Premium x.y expects Free x.y alongside it. They reach sites through different channels at different speeds. Free is distributed by the WordPress.org plugin directory, which rolls a new version out gradually over hours; Premium is downloaded immediately from My Yoast through the customer license. In that gap a site can be offered the Premium x.y update before Free x.y is available to it, and updating Premium then breaks the pairing. This happened with Premium 27.8 and required manually pulling Premium from distribution until Free 27.8 had spread.

This PR holds the Premium update back until the matching Free version is available to that specific site. It keys off the real Free availability each site sees, so it self-corrects per site with no fixed timer and no server-side coordination, mirroring how WordPress core silently holds back updates.

Summary

This PR can be summarized in the following changelog entry:

  • Hides the Yoast SEO Premium update until a compatible Yoast SEO version is available.

Relevant technical choices:

  • The decision lives in WPSEO_Addon_Manager::check_for_updates(), the method that already chooses, per add-on, between the response bucket (an update is shown and can auto-install) and the no_update bucket (nothing shown). The new guard keeps a held-back Premium update in no_update.
  • Placing it in no_update rather than failing the install mirrors how WordPress core holds back updates: nothing appears on the Updates screen, and the auto-updater skips it too, since it only acts on entries in response.
  • Only the major and minor version parts are compared, so patch releases are not held back: Premium x.y.z only needs Free x.y to be available.
  • The latest available Free version is read from Free's own entry in WordPress's update list (new_version); when it is absent the guard fails open, so updates are never hidden on incomplete data.
  • Limited to Premium for now via a single helper. Yoast WooCommerce SEO will need the same later, but its version line does not track Free's x.y, so it stays out until the required Free version can be provided per product (ideally from the licensing API).
  • This complements the existing YOAST_SEO_CHECK_REQUIRED_VERSION header check, which blocks a manually uploaded incompatible add-on. That handles manual installs; this hides the offered and automatic update.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

The behaviour only occurs when a newer Premium is available while the matching Free version is not yet available to the site, so the scenario has to be simulated. On a site with Free and Premium installed at the same version (for example both 27.7):

  • Drop this temporary must-use plugin in wp-content/mu-plugins/holdback-test.php. It makes the license report Premium 27.8 and pins the latest Free the site can see to 27.7. Make sure the installed Premium version is lower than the offered one (here, set the Premium plugin's installed version to 27.7).
<?php
// TEST ONLY. Simulate "Premium 27.8 released, Free 27.8 not yet available to this site".
add_action( 'admin_init', function () {
	$info = (object) [
		'subscriptions' => [
			(object) [
				'renewal_url' => '',
				'expiry_date' => '2037-01-01T00:00:00.000Z',
				'product'     => (object) [
					'version'      => '27.8',
					'name'         => 'Yoast SEO Premium',
					'slug'         => 'yoast-seo-wordpress-premium',
					'last_updated' => '2037-01-01T00:00:00.000Z',
					'store_url'    => 'https://yoast.com/shop',
					'download'     => 'https://example.com/premium.zip',
					'changelog'    => '',
				],
			],
		],
	];
	set_transient( 'wpseo_site_information', $info, DAY_IN_SECONDS );
	set_transient( 'wpseo_site_information_quick', $info, 60 );
}, 1 );

add_filter( 'pre_set_site_transient_update_plugins', function ( $transient ) {
	if ( is_object( $transient ) ) {
		unset( $transient->response['wordpress-seo/wp-seo.php'] );
		$transient->no_update['wordpress-seo/wp-seo.php'] = (object) [
			'plugin'      => 'wordpress-seo/wp-seo.php',
			'slug'        => 'wordpress-seo',
			'new_version' => '27.7',
			'requires'    => '6.5',
		];
	}
	return $transient;
}, 9 );
  • Go to Dashboard > Updates and click "Check again", then open the Plugins screen.
  • Confirm that no update is offered for Yoast SEO Premium on either screen. It is held back, because the site only sees Free 27.7.
  • Now change new_version in the snippet from 27.7 to 27.8 (the matching Free is now available), click "Check again", and reload the Plugins screen.
  • Confirm the Premium 27.8 update now appears normally.
  • Remove the temporary must-use plugin when done.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

  • Same as the acceptance test steps above.

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • The plugin update flow for Yoast SEO Premium: the Updates screen, the Plugins screen, and automatic background updates. Worth confirming that a normal Premium update still appears when the matching Free version is available, and that the other add-ons (News, Video, WooCommerce, Local) are unaffected.

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes #23374

…Free version is available

Yoast SEO Premium x.y requires Yoast SEO x.y. When the Premium version offered by the
My Yoast licensing API is ahead of the latest Free version the site can currently see,
the update is now placed in the no_update bucket instead of response. That hides it from
the Updates screen and skips it in the auto-updater, mirroring how WordPress core holds
back updates, and it self-corrects per site once the matching Free version is available.

Only the major and minor version components are compared, so patch releases are not
affected. The check is limited to Premium for now; the seam is isolated in a single
helper so other add-ons can be added once their required Free version is known.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqP5w8YtJBwAPhutDCsPUa
@enricobattocchi enricobattocchi added the changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog label Jun 18, 2026
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 2

Coverage decreased (-0.5%) to 53.25%

Details

  • Coverage decreased (-0.5%) from the base build.
  • Patch coverage: 3 uncovered changes across 1 file (13 of 16 lines covered, 81.25%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
inc/class-addon-manager.php 16 13 81.25%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 67725
Covered Lines: 35845
Line Coverage: 52.93%
Relevant Branches: 16577
Covered Branches: 9046
Branch Coverage: 54.57%
Branches in Coverage %: Yes
Coverage Strength: 44432.6 hits per line

💛 - Coveralls

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents Yoast SEO Premium updates from being offered to a site until the matching Yoast SEO (Free) major.minor version is actually available to that site via WordPress.org’s update data, avoiding mismatched Free/Premium pairs during phased rollouts.

Changes:

  • Adds a hold-back guard in WPSEO_Addon_Manager::check_for_updates() to keep Premium updates in no_update when Free’s latest available new_version is behind Premium’s offered major.minor.
  • Introduces helper methods to compare major.minor versions while ignoring patch/pre-release suffixes.
  • Adds unit tests covering the hold-back behavior for Premium vs non-Premium add-ons and patch releases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
inc/class-addon-manager.php Adds the Premium update hold-back decision and version-parsing helpers inside the add-on update flow.
tests/Unit/Inc/Addon_Manager_Test.php Adds unit coverage to ensure Premium updates are hidden/shown based on Free availability and that other add-ons remain unaffected.

Comment on lines +428 to +434
/**
* Extracts the major.minor part of a version string, ignoring any patch or pre-release suffix.
*
* @param string $version The version string.
*
* @return string|null The major.minor version, or null when it cannot be determined.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Hold back the Yoast SEO Premium update until a compatible Yoast SEO version is available

3 participants