Skip to content

Themes: share one WordPress.com themes data layer between the Marketplace tab and wpcomsh - #53511

Merged
dsas merged 13 commits into
trunkfrom
update/dotcom-18877-shared-themes-data-layer
Oct 13, 2026
Merged

dsas merged 13 commits into
trunkfrom
update/dotcom-18877-shared-themes-data-layer

Conversation

@dsas

@dsas dsas commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes DOTCOM-18877

wpcomsh and the Marketplace tab each fetched, cached and mapped the WordPress.com theme catalog — one job, two implementations, two answers about which themes exist. The shared parts move to jetpack-mu-wpcom rather than wpcomsh, because the tab is expected to reach WordPress.com Simple and wpcomsh is Atomic only.

Moved to the package Still wpcomsh
WPCom_Themes_Api, WPCom_Themes_Cache, WPCom_Themes_Mapper, and a new WPCom_Themes_Tiers WPCom_Themes_Merger, WPCom_Themes_Service, merging into core's tabs, symlink install, removing WordPress.org themes by plan

They are classmap-autoloaded, so wpcomsh needs no require_once. The tier reader is a class rather than functions because a package feature file is only required for WordPress.com users, while this runs on themes_api_result.

Proposed changes

  • The tab reads the shared layer. Its own fetch, cache and mapping are gone, 328 lines down to 214. The shared mapper fills in more than the tab's did: download_link, which is what the symlink installer matches on, whether a theme is installed or active, its tags, its tier, and the demo preview args. The whole catalog arrives in one request, so paging is a slice, and only the requested page is mapped because mapping reads each theme from disk.
  • Removing WordPress.org themes by plan no longer empties the result. Emptying the whole result was the same as removing them only while everything came from WordPress.org, which stopped being true once the Marketplace tab answered with its own — and is why the tab listed nothing below the Business plan. It now drops only the themes the mapper did not mark as ours. A result made only of ours keeps its total, which counts every page rather than this one, and a theme_information result is a single theme, so it is left alone.
  • Tier entitlement is answered in one place. has_valid_theme_tier() mixed entitlement with a display choice: core's tabs hide partner themes because each needs its own subscription, not because the plan excludes them, so a caller wanting the first got the second with it. WPCom_Themes_Tiers answers entitlement alone and the tabs compose their rule on top. The tier-to-capability convention and the premium default for a theme reporting no tier live there too.
  • A failed read is no longer cached for a day. A non-200 or unparseable response became an empty array that the cache kept for DAY_IN_SECONDS, so brief trouble reaching WordPress.com emptied the Marketplace tab, and core's own tabs, until the entry expired. Failure now stays null all the way to the cache, which does not store it, and reading the body with isset() stops the warning each failure used to log.

Behaviour is otherwise unchanged, and the catalog TTL stays at a day, which is what every other cache in this area uses.

Tests: 1360 in the package, 308 in wpcomsh. New coverage for the tier reader, for the plan-based removal including the total that a page-count regression broke, and for a failed read not being cached. Both bugs above were found by clicking rather than by tests, so both have regression tests.

Related product discussion/links

DOTCOM-18635 added the Marketplace tab, with its own copy of this data layer; this PR is the follow-up that shares one.

Does this pull request change what data or activity we track or use?

No.

Testing instructions

Needs an Atomic site. The Marketplace tab also needs the wpcom-themes-marketplace-tab flag on, in Tools → Feature Flags, which is visible to a proxied Automattician with manage_options.

  • Build and sync: jetpack build --deps plugins/wpcomsh, then jetpack rsync wpcomsh <your-site>@sftp.wp.com:htdocs/wp-content/mu-plugins/wpcomsh.
  • Popular, Latest, Block Themes and Search should look exactly as they do on trunk. They are the regression risk: they go through the filter this PR rewrites, and they rebuild their own list after it.
  • On a plan below Business, those tabs should still list WordPress.com themes and no WordPress.org ones, as before.
  • With the flag on, the Marketplace tab should list the whole catalog — 335 themes at the time of writing, rather than the nothing it showed below Business before this PR. The count reads 335 on the first page and then tracks what is loaded as you scroll, which is core's own behaviour for every tab.
  • Open a theme's preview. It should open promo-free, and ?theme=<slug> loaded directly should still work.
  • Install and activate a theme the plan includes. The grid should show themes already installed as installed rather than offering Install, which the tab did not manage before.
  • jetpack test php packages/jetpack-mu-wpcom and jetpack docker phpunit wpcomsh.

dsas added 7 commits October 12, 2026 21:55
…omsh

wpcomsh and the Marketplace tab each fetched, cached and mapped the WordPress.com
theme catalog, so there were two implementations of one job and two sources of
truth about which themes exist. Move the three platform-neutral classes into
jetpack-mu-wpcom, which wpcomsh bundles, leaving the merger and the service in
wpcomsh because merging with WordPress.org results is Atomic's business.

They are classmap-autoloaded from the package, so wpcomsh needs no requires for
them. The tab still has its own fetch and mapping; that goes next.

No behaviour change.
The tab fetched, cached and mapped the catalog itself, in parallel with the
shared reader wpcomsh uses for core's own tabs. It now uses that reader, so
there is one fetch, one cache and one mapping.

The shared mapper fills in more than the tab's own did: download_link, which is
what makes install work, whether a theme is installed or active, its tags and
its tier. It also appends the demo site's preview args, so the tab's own copy of
those is gone.

The whole catalog arrives in one request rather than a page at a time, so paging
is a slice here. Only the page core asked for is mapped, because mapping reads
each theme from disk to say whether it is installed.
wpcomsh_remove_dot_org_themes_based_on_plan() emptied the whole themes API
result when a site's plan excludes WordPress.org themes. That was the same thing
as removing them only while every result came from WordPress.org, which stopped
being true once the Marketplace tab started answering with its own.

It now drops the themes WPCom_Themes_Mapper did not mark as ours, which is what
the name says and what the core tabs already end up with: they are emptied at
this priority and merged back at the next. A theme_information result is one
theme rather than a list, so it is left alone instead of having an empty themes
array bolted onto it.
has_valid_theme_tier() answered two questions at once: whether a site's plan
includes a tier, and whether core's Add Themes tabs should show it. The second
is that surface's choice -- partner themes are not unentitled, they need their
own subscription -- so a caller wanting the first got the second with it, which
is wrong for the Marketplace tab and will be wrong again for the install guard
in DOTCOM-18722.

WPCom_Themes_Tiers now answers entitlement alone, and the tabs compose their own
rule on top of it, where it can be read. The tier-to-capability convention and
the premium default for a theme reporting no tier live there too.

Behaviour is unchanged. A class rather than functions because a package feature
file is only required for WordPress.com users, while this runs on themes_api_result.
A non-200 or unparseable response made handle_request() return null, the caller
turned that into an empty array, and the cache kept it for a day. A moment's
trouble reaching WordPress.com therefore emptied the Marketplace tab, and core's
own tabs, until the entry expired.

A failed read is now null all the way to the cache, which does not store it, and
reading the body with isset() instead of a null property read stops the warning
that failure used to log. Preview URLs are built with add_query_arg so a demo_uri
carrying a query string does not end up with two question marks.
…answer

Every paid plan includes the partner tier, while each partner theme still needs
its own subscription that no plan feature reports. Without the note, an install
guard built on site_can_use() alone would let a partner theme through.
The total counts every page, so replacing it with the themes kept from this one
made the Marketplace tab report 100 rather than the whole catalog. A result made
only of our themes is now left alone, and a WordPress.org one still has its
total cleared, which is what the filters that run next expect.
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (WordPress.com Site Helper), and enable the update/dotcom-18877-shared-themes-data-layer branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack-mu-wpcom-plugin update/dotcom-18877-shared-themes-data-layer

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Wpcomsh plugin:

  • Next scheduled release: Atomic deploys happen twice daily on weekdays (p9o2xV-2EN-p2)

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Oct 12, 2026
@jp-launch-control

jp-launch-control Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 4 files.

File Coverage Δ% Δ Uncovered
projects/plugins/wpcomsh/wpcom-themes/includes/class-wpcom-themes-service.php 0/37 (0.00%) 0.00% -1 💚
projects/packages/jetpack-mu-wpcom/src/features/wpcom-themes/wpcom-themes-tab.php 63/79 (79.75%) -5.75% -3 💚
projects/plugins/wpcomsh/wpcom-themes/themes.php 16/98 (16.33%) 16.33% -4 💚
projects/packages/jetpack-mu-wpcom/src/features/launchpad/launchpad-task-definitions.php 722/1501 (48.10%) 0.47% -7 💚

4 files are newly checked for coverage.

File Coverage
projects/packages/jetpack-mu-wpcom/src/features/wpcom-themes/class-wpcom-themes-tiers.php 2/6 (33.33%) ❤️‍🩹
projects/packages/jetpack-mu-wpcom/src/features/wpcom-themes/class-wpcom-themes-api.php 24/35 (68.57%) 💚
projects/packages/jetpack-mu-wpcom/src/features/wpcom-themes/class-wpcom-themes-mapper.php 41/45 (91.11%) 💚
projects/packages/jetpack-mu-wpcom/src/features/wpcom-themes/class-wpcom-themes-cache.php 7/7 (100.00%) 💚

Full summary · PHP report · JS report

dsas added 3 commits October 12, 2026 22:37
… plan

The tests asserted that the suite's site lacks community themes, which holds on
the local test site and not on the WP Cloud one, where the same assertions
failed. Splitting the plan gate from the filtering lets the filtering be tested
on its own, which is the part with the logic in it and holds on any site.
The free short-circuit exists because there is no free-themes capability to ask about,
which a comment carries better than a test asserting one line of a three-line method.
Also folds the premium default in with the tier it defaults from.
Its own Add Themes tabs read through the same cache, so an Atomic site owner
notices this one even though the fix is in the package.
@dsas
dsas requested a balanced review from Copilot October 12, 2026 22:52
Moving shared code into the package and reading tiers from it change nothing a site
owner sees, and wpcomsh's two fixes already give that project an entry.

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.

🟡 Changes recommended

The changed entitlement and plan gates lack direct regression coverage, and two changelog entries overstate failed-read behavior.

6 open findings
What changed in this PR

Centralizes WordPress.com theme catalog handling in jetpack-mu-wpcom for reuse by wpcomsh and the Marketplace tab.

Changes:

  • Shares theme fetching, caching, mapping, and tier entitlement logic.
  • Preserves WordPress.com themes when filtering plan-restricted results.
  • Adds pagination, failure-retry, tier, and filtering coverage.
File Description
projects/​plugins/​wpcomsh/​wpcom-themes/​themes.php Uses shared classes and refines plan filtering.
projects/​plugins/​wpcomsh/​wpcom-themes/​includes/​class-wpcom-themes-service.php Delegates tier entitlement checks.
projects/​plugins/​wpcomsh/​tests/​WpcomThemesPlanFilterTest.php Tests source-based theme filtering.
projects/​plugins/​wpcomsh/​changelog/​dotcom-18877-tier-policy Records shared tier policy.
projects/​plugins/​wpcomsh/​changelog/​dotcom-18877-shared-themes-data-layer Records data-layer consolidation.
projects/​plugins/​wpcomsh/​changelog/​dotcom-18877-honest-dot-org-removal Records corrected plan filtering.
projects/​plugins/​wpcomsh/​changelog/​dotcom-18877-dont-cache-failures Records failed-read cache behavior.
projects/​packages/​jetpack-mu-wpcom/​tests/​php/​features/​wpcom-themes/​Wpcom_Themes_Tiers_Test.php Tests tier extraction and feature naming.
projects/​packages/​jetpack-mu-wpcom/​tests/​php/​features/​wpcom-themes/​Wpcom_Themes_Tab_Test.php Updates Marketplace integration coverage.
projects/​packages/​jetpack-mu-wpcom/​src/​features/​wpcom-themes/​wpcom-themes-tab.php Uses the shared catalog and mapper.
projects/​packages/​jetpack-mu-wpcom/​src/​features/​wpcom-themes/​class-wpcom-themes-tiers.php Introduces shared tier entitlement logic.
projects/​packages/​jetpack-mu-wpcom/​src/​features/​wpcom-themes/​class-wpcom-themes-mapper.php Moves and improves theme mapping.
projects/​packages/​jetpack-mu-wpcom/​src/​features/​wpcom-themes/​class-wpcom-themes-cache.php Avoids caching failed reads.
projects/​packages/​jetpack-mu-wpcom/​src/​features/​wpcom-themes/​class-wpcom-themes-api.php Preserves failures as uncached null results.
projects/​packages/​jetpack-mu-wpcom/​changelog/​dotcom-18877-tier-policy Records the shared tier reader.
projects/​packages/​jetpack-mu-wpcom/​changelog/​dotcom-18877-shared-themes-data-layer Records shared catalog infrastructure.
projects/​packages/​jetpack-mu-wpcom/​changelog/​dotcom-18877-dont-cache-failures Records failure retry behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread projects/plugins/wpcomsh/wpcom-themes/themes.php
Comment thread projects/packages/jetpack-mu-wpcom/changelog/dotcom-18877-dont-cache-failures Outdated
Comment thread projects/plugins/wpcomsh/changelog/dotcom-18877-dont-cache-failures Outdated
dsas added 2 commits October 13, 2026 08:52
A failed read still answers that one request with an empty list; only the next one
retries, which the changelog entries claimed away. The entitlement docblock said
an unknown host offers nothing, which is not true of the free tier.
add_query_arg's own name says why it beats concatenation, and the require block
does not need an account of where the other classes come from. Also drops a
before-this-PR aside in the tab tests, and leaves the no-caching rationale in the
cache that owns it rather than in both files.
@dsas
dsas marked this pull request as ready for review October 13, 2026 13:22
@dsas dsas self-assigned this Oct 13, 2026
@dsas
dsas merged commit dfe5a24 into trunk Oct 13, 2026
78 checks passed
@dsas
dsas deleted the update/dotcom-18877-shared-themes-data-layer branch October 13, 2026 14:15
@github-actions github-actions Bot removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants