Skip to main content

EpisodeMeta

\Benecaster\Episode\EpisodeMeta

Class Free

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

Free Beginner

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

View on GitHub →

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.