Skip to content

Plan-side product picker hides private and draft products #153

Description

@bergy10

FCHub Memberships — plan-side product picker hides private/draft products
Plugin: FCHub Memberships 1.4.6
Environment: WordPress 7.0.x, FluentCart, single site
Type: UX inconsistency rather than a hard bug — the plan side and the product side disagree about which products can be linked.

Summary
In the plan editor, the "Link product" picker only ever offers products with post\\\_status = publish. Products saved as private (or draft) never appear, so from the plan side they look un-linkable.
They aren't. Adding the same link from the other direction — FluentCart → product → Integrations → Memberships feed — works perfectly on a private product, and produces exactly the same wp\\\_fct\\\_product\\\_meta row. PlanProductLinkService::linkProduct() itself performs no status check at all; it will link any product ID it is given.
So the restriction exists only in the picker's query, and only on one of the two routes to the same outcome.
Where it comes from
app/Domain/Plan/PlanProductLinkService.phpsearchProducts():

$where  = 'p.post\\\_type = %s AND p.post\\\_status = %s';
$params = \\\['fluent-products', 'publish'];

linkProduct(), a few methods above, looks the product up with no status condition:

"SELECT ID as id, post\\\_title as title FROM {$postsTable} WHERE ID = %d"

Why it matters
Private is a legitimate, well-supported FluentCart product state — an unlisted product that is fully purchasable by direct link. It's the natural shape for a back-catalogue: sold from a curated page or an email, deliberately kept out of the public shop index.
On our site that's most of the catalogue — 30+ private products, each with its own membership plan. Every one of them is invisible in the plan editor's picker, which reads as "you cannot link this", when in fact the product-side route links it without complaint. The disconnect costs more time than the limitation would.
Suggested fix
Widen the query to include private (and arguably draft, since a draft product can be linked ahead of launch):

$where  = 'p.post\\\_type = %s AND p.post\\\_status IN (%s, %s)';
$params = \\\['fluent-products', 'publish', 'private'];

Optionally return post\\\_status in the payload so the picker can render a small "Private" / "Draft" badge — the admin then sees the state rather than an absence, which is the part that currently misleads.
A filter on the status list would also do it, and would let sites decide for themselves:

$statuses = apply\\\_filters('fchub\\\_memberships/linkable\\\_product\\\_statuses', \\\['publish', 'private']);

Verification on our install
30+ fluent-products at post\\\_status = private; none appear in the plan-side picker.
Products 302, 304, 306, 308 (private) and 310, 346 (draft) each carry a working memberships integration feed, added from the product side — proof that private products link and grant correctly.
Nothing in linkProduct(), the feed writer, or MembershipAccessIntegration consults post status at any point.

Reported by Brett Chitty

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions