Skip to content

SCON-460: Expose update_version via Feature_Resource#32

Open
pauloiankoski wants to merge 7 commits intomainfrom
SCON-460/update-version-transient
Open

SCON-460: Expose update_version via Feature_Resource#32
pauloiankoski wants to merge 7 commits intomainfrom
SCON-460/update-version-transient

Conversation

@pauloiankoski
Copy link
Copy Markdown
Contributor

@pauloiankoski pauloiankoski commented Mar 28, 2026

Resolves SCON-460

Summary

  • Removes has_update from feature types and replaces it with update_version — a nullable string read from the WordPress update transient after resolution is complete
  • Introduces Feature_Resource to decorate resolved features with transient-sourced update data before serialization in the REST API
  • Fixes a re-entrancy bug in Collection: calling get_site_transient() mid-loop fired the update filter, which iterated the same cached collection and corrupted the outer cursor; switched Collection from Iterator to IteratorAggregate so each foreach gets its own independent ArrayIterator

has_update was a naive catalog vs installed version comparison stored on
the feature object. It is now computed inline inside get_update_data()
using the Catalog_Feature parameter, keeping it as an internal transient
injection detail without leaking it through the feature's attribute bag.

Removes has_update() from the Installable interface and both Plugin and
Theme types. Updates tests to reflect that to_array() no longer includes
the key.
Feature_Resource decorates a resolved Feature and reads the WordPress
update transient (update_plugins or update_themes) to expose an
update_version field. This is the authoritative signal for whether an
update is available, because Plugin_Handler and Theme_Handler gate
transient injection on license availability and dot-org exclusion —
conditions that the naive catalog vs installed comparison in has_update
did not account for.

Safe to call after Feature_Repository has cached its results: the
transient filter chain re-enters the repository through the warm cache,
so there is no circular resolution.
All Feature_Controller response sites now wrap the resolved feature in
Feature_Resource before serializing, merging the transient-sourced
update_version into the response payload.

Replaces has_update (naive catalog comparison) with update_version
(authoritative WP transient value) in the REST schema. update_version
is null when the feature is not in the transient — which covers dot-org
plugins, unlicensed features, and features that are not installed.
Replaces has_update (naive boolean) with update_version (version string
or null) in the TS types and VersionDisplay component.

VersionDisplay now shows the update version from the WP transient rather
than the catalog version, so the displayed target version matches what
WordPress will actually install.
Collection previously implemented Iterator using PHP's internal array
pointer, which is shared mutable state. Re-entrant foreach loops over
the same cached instance (e.g. triggered by get_site_transient firing
the update filter mid-loop) would corrupt the outer cursor, causing
get_items() to return only the first feature.

Switch to IteratorAggregate so getIterator() returns a fresh
ArrayIterator on each foreach, giving every loop its own independent
cursor. Also reverts the defensive foreach ($this->items) workaround
in Feature_Collection now that the root cause is fixed.
- Remove unused Cast import from Feature_Resource
- Add missing generic type annotations to Collection constructor
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