$attrs Validated attributes. */ public static function path( string $type, array $attrs ): string { if ( 'episodes' !== $type && ! empty( $attrs['episode'] ) ) { return '/v1/episodes/' . $attrs['episode']; } if ( empty( $attrs['podcast'] ) ) { return ''; } $path = '/v1/podcasts/' . $attrs['podcast'] . '/episodes'; if ( 'episodes' !== $type ) { return $path . '/latest'; } $query = array( 'limit' => $attrs['count'] ?? '10' ); if ( isset( $attrs['season'] ) ) { $query['season'] = $attrs['season']; } if ( ! empty( $attrs['hide'] ) ) { $query['type'] = implode( ',', array_diff( array( 'full', 'trailer', 'bonus' ), explode( ',', $attrs['hide'] ) ) ); } return $path . '?' . http_build_query( $query, '', '&', PHP_QUERY_RFC3986 ); } /** * Render without ever fetching. Null/failed cache uses the insertion snapshot. * * @param string $type Element type. * @param array $input Block or shortcode attributes. * @param bool $block Whether this is a block. */ public static function render( string $type, array $input, bool $block = false ): string { if ( ! in_array( $type, array( 'player', 'episodes', 'play', 'transcript' ), true ) ) { return ''; } $attrs = Attributes::clean( $type, $input, $block ); $path = self::path( $type, $attrs ); if ( '' === $path && 'transcript' === $type && isset( $attrs['for'] ) ) { // Following a Latest-episode player: the element finds its episode when it plays. return self::element( $type, $attrs, $input, '', $block ); } if ( '' === $path ) { return ''; } $cache = Plugin::cache(); $data = $cache->get( $path ); if ( $cache->is_unavailable( $path ) ) { return ''; } $snapshot = isset( $input['snapshot'] ) && is_array( $input['snapshot'] ) ? Attributes::snapshot( $input['snapshot'] ) : array(); $fallback = array( 'title' => is_string( $snapshot['title'] ?? null ) ? $snapshot['title'] : '', 'links' => array( 'listen' => $snapshot['listenUrl'] ?? null ), 'audio' => array( 'url' => $snapshot['audioUrl'] ?? null ), ); $public = is_array( $data ) && isset( $data['data'] ) && is_array( $data['data'] ); // Nothing confirmed public and nothing public saved (a scheduled episode stores no // snapshot): no fallback content at all. The bare element shows nothing, and in the // browser it collapses on the public API's 404 until the episode is public. The cache // read above has scheduled the refresh. if ( ! $public && '' === $fallback['title'] ) { return self::element( $type, $attrs, $input, '', $block ); } // In click mode a transcript would reserve its full height behind a "Load transcript" // button, only to say there's none after the click. The cache already knows. if ( 'transcript' === $type && $public && Embed_Settings::enabled( Embed_Settings::LOAD_ON_CLICK ) && array_key_exists( 'transcript', $data['data'] ) && null === $data['data']['transcript'] ) { return ''; } $episode = $public ? $data['data'] : $fallback; $json = ''; if ( 'episodes' === $type ) { $episodes = $public ? $data['data'] : array(); // Refresh stale markers; only newer, fresh public data can supersede them. $episodes = array_values( array_filter( $episodes, static function ( $item ) use ( $cache, $path ) { return is_array( $item ) && ! $cache->is_episode_unavailable( $item['id'] ?? '', $path ); } ) ); $html = Fallback::episode_list( $data['podcast'] ?? $fallback, $episodes, array( 'limit' => (int) ( $attrs['count'] ?? 10 ) ) ); } else { if ( isset( $episode['id'] ) && $cache->is_episode_unavailable( $episode['id'], $path ) ) { return ''; } // A standalone transcript links to the listen page; it never fetches VTT in PHP. $html = 'transcript' === $type ? Fallback::episode_list( $episode, array() ) : Fallback::episode( $episode ); if ( $public && Embed_Settings::enabled( Embed_Settings::JSON_LD ) ) { $json = ''; } } return self::element( $type, $attrs, $input, $html, $block, $json ); } /** * The element around its fallback, inside the block wrapper for blocks. * * @param string $type Element type. * @param array $attrs Validated attributes. * @param array $input Block or shortcode attributes, for styles. * @param string $html Escaped fallback markup. * @param bool $block Whether this is a block. * @param string $json JSON-LD script, or ''. */ private static function element( string $type, array $attrs, array $input, string $html, bool $block, string $json = '' ): string { $element = ' $value ) { $element .= ' ' . $name . '="' . esc_attr( $value ) . '"'; } $style = Theme::block_style( $input ); if ( 'transcript' === $type && isset( $attrs['height'] ) ) { // The element is its 55px search row, the text area and a 1px border, 377px at the // default height. The package's click facade and fallback reserve --showfm-height, // so a shorter transcript doesn't sit in 377px before it loads. $style = ltrim( $style . ';--showfm-height:' . ( (int) $attrs['height'] + 57 ) . 'px', ';' ); } if ( '' !== $style ) { $element .= ' style="' . esc_attr( $style ) . '"'; } Assets::enqueue(); $element .= '>' . $html . '' . $json; return $block ? '
' . $element . '
' : $element; } }