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.
Recipe
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_podcast2_transcript_owner(): string |
Public | — | The entitlement slug of the add-on that wrote the episode's transcript URL, or '' when the URL is the podcaster's own. While the named add-on is not loaded, Benecaster withholds the URL from feeds, the episode page and wp/v2. See the transcript-url-owner recipe. |
set_podcast2_transcript_owner( string $addon_slug ): static |
Public | — | Records which add-on owns the transcript URL. Write it every time you write the URL; an empty string deletes it, handing the URL to the podcaster. An episode-editor save that changes the URL clears it. |
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' returns the episode to the show default, but it stores the value 'inherit' rather than deleting the meta. So 'set to inherit' and 'never set' read the same through get_explicit() and differ only to code that checks whether the key exists. An importer whose source carries no explicit field should not call this at all. |
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.