Skip to main content

TierAwareEpisodeNamespaceInterface

The episode-level counterpart of TierAwareFeedNamespaceInterface. Extends FeedNamespaceInterface with one extra method. Opt-in and additive: FeedCompiler calls episode_tags_for_tier() on a handler that implements it and episode_tags() on every other handler, so existing handlers need no change.

Add a channel-level feed tag that names an episode, safely

Free Advanced

Most channel-level feed tags are made of show metadata — a licence, a GUID, a funding link — and need nothing but the show ID to build. A channel tag that names an episode is a different problem, and getting it wrong hands out media people paid for.

The <channel> block is compiled once per tier and sent to every subscriber on that tier. Put an episode’s enclosure URL there and you have published it to all of them at once — including, if you built the list from your own query, episodes the availability rules are still withholding. That routes around the gate the whole plugin exists to enforce, and it does it in the one part of the feed every recipient reads.

Implement TierAwareFeedNamespaceInterface instead of FeedNamespaceInterface. Benecaster then hands your handler the tier slug plus every episode ID that tier may receive — the whole feed, not just the page being compiled, so a trailer published years ago still appears on page 1.

Two rules the interface cannot enforce for you. Never widen the search beyond $available_episode_ids, and treat an empty array as “advertise nothing” rather than “no restriction”. The second is the one that looks like working code: a handler that skips its filter when the list is empty advertises everything to the tier entitled to least. Core’s own <podcast:trailer> follows both rules.

Pass the media URL through the enclosure filters, as core does. A site running the download proxy has those rewriting every <enclosure>; a channel tag that skips them republishes the raw host URL the proxy exists to hide.

Handlers implementing only FeedNamespaceInterface keep working untouched — Benecaster falls back to channel_tags() for them, so nothing you have already shipped needs changing.

<?php
use Benecaster\Episode\EpisodeMeta;
use Benecaster\Feed\Namespaces\TierAwareFeedNamespaceInterface;

class MyHighlightsNamespace implements TierAwareFeedNamespaceInterface {

    public function get_xmlns(): array {
        return [ 'myns' => 'https://example.com/ns/1.0' ];
    }

    // No tier context here, so advertise no episodes at all rather than guess.
    public function channel_tags( int $show_id ): array {
        return $this->channel_tags_for_tier( $show_id, '', [] );
    }

    public function channel_tags_for_tier( int $show_id, string $tier_slug, array $available_episode_ids ): array {
        // Empty means "this tier can reach nothing", NOT "no filter applied".
        if ( empty( $available_episode_ids ) ) {
            return [];
        }

        $frags = [];

        foreach ( $available_episode_ids as $episode_id ) {
            $episode_id = (int) $episode_id;

            if ( ! get_post_meta( $episode_id, '_my_highlight', true ) ) {
                continue;
            }

            $ep_meta = new EpisodeMeta( $episode_id );

            // Pass the URL through the same filters the <enclosure> uses, or a
            // site running the download proxy gets its raw host URL republished
            // in the channel — the one place every recipient sees it.
            $url = (string) apply_filters(
                'benecaster_feed_enclosure_url',
                $ep_meta->get_audio_url(),
                $episode_id,
                $show_id,
                $tier_slug
            );

            if ( '' === $url ) {
                continue;
            }

            $frags[] = '<myns:highlight url="' . esc_url( $url ) . '"/>' . "\n";
        }

        return $frags;
    }

    public function episode_tags( int $episode_id, int $show_id ): array {
        return [];
    }
}

add_filter( 'benecaster_feed_namespaces', function ( array $handlers ): array {
    $handlers[] = new MyHighlightsNamespace();
    return $handlers;
} );

View on GitHub →