DashboardController
\Benecaster\REST\DashboardController
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.