EpisodeMeta
\Benecaster\Episode\EpisodeMeta
Accessor class for episode-level post meta. Wraps get_post_meta / update_post_meta with typed getters and setters, keeping meta-key strings in constants so call-sites never use raw strings.
Resolve or override an episode's Explicit flag
The episode-level Explicit flag is a three-value override — 'inherit', 'yes', or 'no' — never a bool. 'inherit' is the default and falls back to the show’s Explicit setting; 'yes' and 'no' override it for that one episode.
Feed emission resolves the enum against the show default through EpisodeMeta::get_effective_explicit(), so the enum-to-bool mapping lives in exactly one place. Anything else that needs the effective value — a custom feed, a bulk operation, a migration importer, a front-end badge — should call the same resolver rather than reading the post meta and interpreting it independently.
<?php
use Benecaster\Episode\EpisodeMeta;
use Benecaster\Show\ShowMeta;
// Read the effective flag exactly as the RSS compiler does.
$ep_meta = new EpisodeMeta( $episode_id );
$show_meta = new ShowMeta( wp_get_post_parent_id( $episode_id ) );
$is_explicit = $ep_meta->get_effective_explicit( $show_meta ); // bool
// Write a per-episode override — never pass a raw bool.
$ep_meta->set_explicit( 'no' ); // force clean regardless of show default
$ep_meta->set_explicit( 'yes' ); // force explicit
$ep_meta->set_explicit( 'inherit' ); // clear the override
Methods
| Method | Visibility | Since | Description |
|---|---|---|---|
get_chapters(): list<array{timestamp: string, title: string, url?: string, image_id?: int}> |
Public | — | Returns the episode's authored chapter list from _benecaster_chapters post meta. Empty array when no chapters are set. |
set_chapters( array $value ): static |
Public | — | Sanitises and persists the chapter list. Timestamps validated via is_valid_timestamp(); titles via sanitize_text_field(); URLs via esc_url_raw(); image_ids via absint(). Malformed rows are silently dropped. Passing an empty array deletes the post_meta row. |
is_valid_timestamp( string $value ): bool |
Public | — | Static. Returns true when $value matches /^\d{1,2}:[0-5]\d:[0-5]\d$/. Used by set_chapters() and EpisodeChaptersController::auto_suggest_chapters() for defense-in-depth validation. |
get_explicit(): string |
Public | — | Returns the episode's raw Explicit override — one of 'inherit', 'yes', or 'no'. Legacy values coerce on read: '1' and true become 'yes'; '', '0', and false become 'inherit'. This is the stored override, not the resolved value — use get_effective_explicit() to know what the feed will emit. |
set_explicit( string $value ): static |
Public | — | Persists the Explicit override. Accepts only the EXPLICIT_MODES enum; anything else — including a bool — coerces silently to 'inherit'. Setting 'inherit' clears the override and returns the episode to the show default. |
get_effective_explicit( ShowMeta $show_meta ): bool |
Public | — | Resolves the three-value override against the show's Explicit setting and returns the boolean the feed emits. 'yes' and 'no' win outright; 'inherit' defers to the show. This is the canonical resolver — the RSS compiler and the front-end badge both call it. |
get_download_tracking(): string |
Public | — | Returns the episode's own three-state download-tracking override — one of '' (inherit), 'force-on', or 'force-off'. An unrecognized stored value coerces to '' on read rather than erroring. |
set_download_tracking( string $value ): static |
Public | — | Persists the override. A value outside DOWNLOAD_TRACKING_MODES coerces silently to '' (inherit). Fluent. |
get_preview_url(): string |
Public | — | Returns the episode's preview clip URL from PREVIEW_URL post meta. '' means no clip is set. |
set_preview_url( string $value ): static |
Public | — | Persists the preview clip URL. Callers (EpisodesController, EpisodeMetaSaver) sanitise with esc_url_raw before calling this; the setter itself does not. An empty string is written as '' rather than deleting the row — both read back as "no clip", so callers need not distinguish them. Web player only: Episode\PreviewClipPlayer reads this only on a locked web render, and only when the episode also has a base audio URL (AUDIO_URL) set — it must never reach an . |
Constants
| Name | Value | Description |
|---|---|---|
CHAPTERS |
_benecaster_chapters |
Post meta key for the episode's authored chapter list. |
EXPLICIT_MODES |
[ 'inherit', 'yes', 'no' ] |
The complete set of legal episode-level Explicit values. Validate against this rather than hardcoding the strings. |
REQUIRED_BUYUPS |
_benecaster_required_buyups |
Post meta key, array of integer buy-up IDs. An active grant for any listed ID unlocks the episode (OR logic), on top of whatever the tier schedule already gives it. Read via benecaster_user_can_access_episode(). A new episode can arrive with this value already populated, copied once at creation from the show's ShowMeta::DEFAULT_REQUIRED_BUYUPS — an explicit value on the create request always wins over the show default, and no later save ever re-seeds it. |
DOWNLOAD_TRACKING |
_benecaster_episode_download_tracking |
Post meta key for the episode's three-state download-tracking override. A brand-new field, not a schema change to an existing one — no prior boolean value existed anywhere to migrate from. '' (inherit) falls back to ShowMeta::has_download_tracking_enabled(); force-on/force-off win outright. Read/write via REST PUT /episodes/{id} (download_tracking param). |
DOWNLOAD_TRACKING_MODES |
[ '', 'force-on', 'force-off' ] |
The complete set of legal episode-level download-tracking override values. |
PREVIEW_URL |
_benecaster_preview_url |
Post meta key for the episode's preview clip URL — a short clip played on the website to visitors who can't hear the full episode. '' = no clip. Registered with show_in_rest (wp/v2) like the other episode URL fields; a change appears in benecaster_episode_meta_updated's $changed array. Web player only: Episode\PreviewClipPlayer reads it only for a locked web render (episode page, archive card, [benecaster_player]) and only when the episode also has a base audio URL — it must never reach an or any other feed element. |
Notes
There is no boolean accessor. is_explicit() and set_explicit() do not exist — use the string API below.
The bool API could not represent the full state space. WordPress serialises bool false as '', which is byte-identical to "meta was never set" — so "this episode is deliberately clean even though the show is explicit" was unreachable. That was the defect; the three-value enum is the fix.
Migration. Replace is_explicit() with get_effective_explicit( $show_meta ) — note it takes the show's meta object, because resolving an override requires knowing what it is overriding. Replace set_explicit( true ) with set_explicit( 'yes' ) and set_explicit( false ) with set_explicit( 'no' ) — but check the intent first: code that passed false to mean "clear the override" wants 'inherit', not 'no'.
Do not read _benecaster_episode_explicit directly. The legacy coercion rules live in get_explicit(), so raw reads will misinterpret any episode written before the enum landed.