Skip to content

fix: flush the update cache on every license removal path - #1684

Merged
jakejackson1 merged 1 commit into
hot-patch-6.16.0from
fix/license-flush-update-cache
Jul 24, 2026
Merged

fix: flush the update cache on every license removal path#1684
jakejackson1 merged 1 commit into
hot-patch-6.16.0from
fix/license-flush-update-cache

Conversation

@jakejackson1

@jakejackson1 jakejackson1 commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

License activation and deactivation already flushed the cached update info, but three paths that change license state did not.

1. Access Pass siblings. maybe_auto_activate_license() / maybe_auto_deactivate_license() called update_license_info() alone. Every add-on covered by the pass kept version info fetched under the old key (so, no package) for up to the 3-hour TTL, plus its stale update_plugins entry.

2. Clearing the key in the settings form. Unlike the Deactivate button, the empty-key branch of maybe_active_licenses() left the cached package in place.

3. The Multisite network cache. delete_version_info_cache() only removes the per-site option, so the gpdf_sl_net_* package a site promoted stayed borrowable via maybe_apply_network_package() for its 3-day TTL — a site that had lost its license could still hand a download URL to the network.

What ends the network entitlement

Withdrawal needs a lost license and ownership of the shared package.

  • Lost license means the key was removed or the store rejected it: expired, revoked, disabled, missing, invalid, site_inactive, item_name_mismatch, invalid_item_id, no_activations_left. These all arrive with the key still populated, so the status is the only signal.
  • error and rate_limit are deliberately excluded. They are written on a WP_Error, timeout or 429, and treating a failed request as a verdict would strip a package every subsite depends on whenever the licensing server is briefly unreachable.
  • Ownership: the promoting blog id is stored alongside the package, so a promotion made by another site — which is therefore still licensed — outlives this site's deactivation.

flush_update_cache() takes no flag; the withdrawal derives from the updater's own key and status, so no caller has to remember to ask for it.

Guards against no-op work

  • The sibling flush only fires when the key or status actually moved — a hardcoded GPDF_LICENSE_KEY re-fires the activation action every few hours and would otherwise force a fresh remote request per add-on each cycle.
  • The settings-form flush only fires when a key was previously stored — the licensing tab posts an empty field for every registered add-on on an unrelated save.

Testing

16 new tests. Full PHPUnit suite green in both modes: 1149 tests / 3578 assertions single-site, 1149 / 3677 multisite. phpcs clean on the changed files.

The rejection-status tests were verified non-vacuous: with the status gate reverted to the key-only check, all 10 fail.

Follow-up (not in this patch)

All five update_license_info() call sites in src/ are now immediately followed by a flush — that 100% correlation says the invariant belongs inside update_license_info() itself. Deliberately left for 6.17 rather than relocating side effects of a public @since 4.2 method in a hot patch. Same for the settings-clear branch being a second, partial definition of "license removed" alongside delete_license_info().

🤖 Generated with Claude Code

@jakejackson1
jakejackson1 force-pushed the fix/license-flush-update-cache branch 2 times, most recently from 9198e17 to 2dd3b55 Compare July 24, 2026 02:28
Activation and deactivation already flushed the cached update info, but three
paths that change license state did not:

1. Access Pass siblings. maybe_auto_activate_license()/maybe_auto_deactivate_license()
   called update_license_info() alone, so every add-on covered by the pass kept
   version info fetched under the old key (no package) for up to the 3 hour TTL,
   plus its stale update_plugins entry.

2. Clearing the key in the settings form. Unlike the Deactivate button, the
   empty-key branch of maybe_active_licenses() left the cached package in place.

3. The Multisite network cache. delete_version_info_cache() only removes the
   per-site option, so the gpdf_sl_net_* package a site promoted stayed
   borrowable through maybe_apply_network_package() for its 3 day TTL — a site
   that had lost its license could still hand out a download URL to the network.

Withdrawing the network package needs a lost license and ownership of the cached
copy. Lost means the key was removed or the store rejected it outright: expired,
revoked, disabled, missing, invalid, site_inactive, item_name_mismatch,
invalid_item_id, no_activations_left — all of which arrive with the key still
populated, so the status is the only signal. `error` and `rate_limit` are
excluded, because they are written on a WP_Error, timeout or 429, and treating a
failed request as a verdict would strip a package every subsite depends on
whenever the licensing server is briefly unreachable. Ownership is tracked by
recording the promoting blog id alongside the package: a promotion made by
another site belongs to a site that is still licensed, and must outlive this one.

Two guards keep the new flushes off no-op paths. The sibling flush only fires
when the key or status actually moved, since a hardcoded GPDF_LICENSE_KEY
re-fires the activation action every few hours and would otherwise force a fresh
remote request per add-on each cycle. The settings-form flush only fires when a
key was previously stored, since the licensing tab posts an empty field for every
registered add-on on an unrelated save.

Also corrects Model_Uninstall::remove_plugin_network_options()'s docblock, which
described the network cache as a single option. There is one per extension; the
wildcard delete is what makes the cleanup complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jakejackson1
jakejackson1 force-pushed the fix/license-flush-update-cache branch from 2dd3b55 to e7dbf0c Compare July 24, 2026 02:36
@jakejackson1
jakejackson1 merged commit c5f7df6 into hot-patch-6.16.0 Jul 24, 2026
13 of 14 checks passed
@jakejackson1
jakejackson1 deleted the fix/license-flush-update-cache branch July 24, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant