Skip to main content

DashboardController

\Benecaster\REST\DashboardController

Class Free

Dashboard REST endpoints — stat cards, the subscriber-growth sparkline, and the per-tier breakdown. All three routes are permission_callback_admin.

GET /shows/{id}/dashboard — active subscriber count, 30-day new/churn deltas, episode count, a revenue estimate, the site-wide paying-subscriber count and plan limit (from CapChecker), and an “attention items” list (feed-sync drafts stuck in draft, pending migration invites, and an upcoming license expiry or academic-grace deadline). GET /shows/{id}/dashboard/sparkline — 90-day daily subscriber-count series from benecaster_analytics_daily; returns an empty points array on installs with no snapshot data yet. GET /shows/{id}/analytics/tiers — per-tier active count plus 30-day new/churn deltas, ordered highest tier first. Hidden by the React component when the show has only one tier.

The constructor takes \Benecaster\Membership\NativeRevenueEstimator as an optional fifth argument; when provided and the show’s active bridge is Built-in Membership (NativeBridge::PLUGIN_SLUG), revenue_estimate and currency_symbol are resolved from it instead of the bridge-based estimate — a built-in membership show has no benecaster_tier_map rows, so the bridge-based estimate always returns null for it and the card would read “—”. currency_symbol is therefore not always $ for those shows — see 11-rest-api.md → GET /shows/{id}/dashboard.

Constructor Dependencies

Type Description
\Benecaster\Token\TokenRepository Active/new/churned subscriber counts and per-tier active counts for the bridge revenue estimate.
\Benecaster\Show\TierMapRepository Resolves a show's paying tiers for the bridge-based revenue estimate.
\Benecaster\Bridge\BridgeManager Resolves the show's active bridge (and its slug) to pick the revenue path and read bridge tier prices.
\Benecaster\License\CapChecker Site-wide paying_subscriber_count and subscriber_limit for the plan-limit stat.
\Benecaster\Membership\NativeRevenueEstimator Optional, defaults to null. Supplies revenue_estimate and currency_symbol for Built-in Membership shows, which the bridge-based estimate cannot answer.

Methods

Method Visibility Since Description
register_routes(): void Public — Registers the three GET routes above, each permission_callback_admin with a numeric-id validate_callback.
get_dashboard( WP_REST_Request $request ): WP_REST_Response|WP_Error Public — 404s when {id} is not a published show. Otherwise assembles the stat-card payload described above: active/new/churned subscriber counts over a trailing 30-day window (TokenRepository), episode_count (published episodes with this show as post_parent), revenue_estimate + currency_symbol (via revenue()), paying_subscriber_count and subscriber_limit (CapChecker), and attention_items (get_attention_items()).
get_sparkline( WP_REST_Request $request ): WP_REST_Response|WP_Error Public — 404s for an unpublished/missing show. Returns {points: [{date, count}, ...]} for the trailing 90 days, one point per day (zero-filled for days with no snapshot), from get_sparkline_points().
get_tier_breakdown( WP_REST_Request $request ): WP_REST_Response|WP_Error Public — 404s for an unpublished/missing show. Returns {period_days: 30, tiers: [...]} from build_tier_breakdown(), each tier carrying its latest active_count snapshot plus 30-day new_count/churn_count totals, sorted by tier_order descending.
build_tier_breakdown( int $show_id ): array — — Three queries against benecaster_analytics_daily and benecaster_tier_map: the latest subscriber_count snapshot per tier, the summed 30-day new/churn totals per tier, and tier name/order metadata. Merges them keyed by tier_slug (tiers with no metadata row fall back to the slug as their name and order 0), then sorts by tier_order descending.
get_sparkline_points( int $show_id ): array — — Sums subscriber_count across all tiers per day for the trailing 90 days from benecaster_analytics_daily, then fills in every day in the window (count 0 for days with no row) so the series has no gaps.
count_episodes( int $show_id ): int — — Counts published episode posts with post_parent = $show_id.
get_attention_items( int $show_id ): array — — Assembles the 'Needs attention' panel's item list from count_feed_sync_drafts(), get_migration_attention(), and get_license_expiry_item(), skipping any that return zero/null. Returns [] when nothing needs attention — callers must hide the panel entirely on an empty array rather than render an empty state.
count_feed_sync_drafts( int $show_id ): int — — Counts draft episodes for the show carrying a _benecaster_source_guid meta key — episodes pending review from a synced hosting feed.
get_migration_attention( int $show_id ): ?array — — Returns null when the migration_imports table does not exist (add-on not installed) or there are no patrons in 'imported'/'emailed' status for the show; otherwise a migration_pending item with the pending count and days since the oldest invite was sent.
get_license_expiry_item( int $show_id ): ?array — — Reads the license of the given show. When an academic grace period is active on it (AcademicGrace::cached()), always returns an academic_grace item — it overrides the license's cached expiry date, because that date is the wrong deadline during a grace period. Otherwise reads the license's cached expiry date and returns a license_expiry item only when it falls within the next 30 days (severity info); returns null when no expiry is set, it's unparseable, already past, or more than 30 days out.
revenue( int $show_id ): array — — Returns [amount, currency_symbol]. When a NativeRevenueEstimator was injected and the show's active bridge slug is NativeBridge::PLUGIN_SLUG, delegates to it and returns its amount/currency (or [null, '$'] if it can't resolve an estimate). Otherwise falls back to estimate_revenue() (the external-bridge path) with currency hard-coded to '$'.
estimate_revenue( int $show_id ): ?float — — For shows on an external membership bridge only. Filters the show's tiers to paying, non-public tiers with an external_tier_id; returns null immediately if there are none. Indexes the active bridge's get_all_tiers() prices by external ID, and returns null (rather than a misleading partial figure) if ANY paying tier's price can't be resolved. Otherwise sums each tier's active subscriber count times its price.

Notes

Extends the plugin's internal RestController base — not part of the public API; its own docblock tells add-on developers not to extend it, and to register independent routes on rest_api_init with the public benecaster_rest_permission_admin() function instead. Inherits register() (hooks register_routes() onto rest_api_init), the permission_callback_admin / permission_callback_editor / permission_callback_subscriber callbacks, rest_error(), and not_found().

Two tier systems, two revenue paths. A Built-in Membership show has no benecaster_tier_map rows, so estimate_revenue() always returns null for it — revenue() must route those shows through NativeRevenueEstimator instead, which is why that dependency exists and why its absence (an older container wiring, or a test double) silently degrades those shows back to a "—" revenue card rather than erroring.