get_defaults(), $atts, 'aiopod_episode' ); $attributes['shortcode'] = 'aiopod_episode'; $post_id = (int) $attributes['id']; if ( empty( $post_id ) ) { $is_singular = is_singular( 'aiopod_episodes' ); $in_the_loop = apply_filters( 'aiopod_the_content_in_the_loop', in_the_loop() ); $is_main_query = apply_filters( 'aiopod_the_content_is_main_query', is_main_query() ); // Define the query $args = array( 'post_type' => 'aiopod_episodes', 'post_status' => 'publish', 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false ); if ( ! empty( $attributes['podcast'] ) ) { // Podcast ID $args['post_parent'] = (int) $attributes['podcast']; $is_singular = false; } // Taxonomy Parameters $tax_queries = array(); if ( ! empty( $attributes['tags'] ) ) { // Tags $tags = array_map( 'intval', ( is_array( $attributes['tags'] ) ? $attributes['tags'] : explode( ',', $attributes['tags'] ) ) ); $tags = array_filter( $tags ); if ( ! empty( $tags ) ) { $tax_queries[] = array( 'taxonomy' => 'aiopod_tags', 'field' => 'term_id', 'terms' => $tags, 'include_children' => false ); $is_singular = false; } } if ( ! empty( $attributes['participants'] ) ) { // Participants $participants = array_map( 'intval', ( is_array( $attributes['participants'] ) ? $attributes['participants'] : explode( ',', $attributes['participants'] ) ) ); $participants = array_filter( $participants ); if ( ! empty( $participants ) ) { $tax_queries[] = array( 'taxonomy' => 'aiopod_participants', 'field' => 'term_id', 'terms' => $participants, 'include_children' => false ); $is_singular = false; } } $count_tax_queries = count( $tax_queries ); if ( $count_tax_queries ) { $args['tax_query'] = ( $count_tax_queries > 1 ) ? array_merge( array( 'relation' => 'AND' ), $tax_queries ) : $tax_queries; } // Custom Field (post meta) Parameters $meta_queries = array(); if ( ! empty( $attributes['type'] ) ) { // Episode Type $allowed_types = aiopod_get_episode_types(); $allowed_types = array_keys( $allowed_types ); $types = is_array( $attributes['type'] ) ? $attributes['type'] : explode( ',', $attributes['type'] ); $types = array_map( 'sanitize_key', array_map( 'trim', $types ) ); $types = array_intersect( $types, $allowed_types ); if ( ! empty( $types ) && count( $types ) !== count( $allowed_types ) ) { $meta_queries[] = array( 'key' => 'episode_type', 'value' => $types, 'compare' => 'IN' ); $is_singular = false; } } if ( ! empty( $attributes['featured'] ) ) { // Featured $meta_queries[] = array( 'key' => 'is_featured', 'value' => 1, 'compare' => '=' ); $is_singular = false; } if ( ! empty( $attributes['season'] ) ) { // Season Number $season = absint( $attributes['season'] ); if ( $season > 0 ) { $meta_queries[] = array( 'key' => 'season_number', 'value' => $season, 'compare' => '=', 'type' => 'NUMERIC' ); $is_singular = false; } } $count_meta_queries = count( $meta_queries ); if ( $count_meta_queries ) { $args['meta_query'] = ( $count_meta_queries > 1 ) ? array_merge( array( 'relation' => 'AND' ), $meta_queries ) : $meta_queries; } // Order & Orderby Parameters $orderby = sanitize_text_field( $attributes['orderby'] ); $order = sanitize_text_field( $attributes['order'] ); switch ( $orderby ) { case 'popularity': $args['orderby'] = 'meta_value_num'; $args['meta_key'] = 'total_downloads_plays'; $args['order'] = $order; break; case 'downloads': $args['orderby'] = 'meta_value_num'; $args['meta_key'] = 'total_downloads'; $args['order'] = $order; break; case 'plays': $args['orderby'] = 'meta_value_num'; $args['meta_key'] = 'total_plays'; $args['order'] = $order; break; case 'rand': $seed = aiopod_get_orderby_rand_seed(); $args['orderby'] = 'RAND(' . $seed . ')'; break; default: $args['orderby'] = $orderby; $args['order'] = $order; } if ( 'date' !== $orderby || 'desc' !== $order ) { // If overriding the default order $is_singular = false; } if ( $is_singular && $in_the_loop && $is_main_query ) { global $wp_the_query; $post_id = (int) $wp_the_query->get_queried_object_id(); } else { $args = apply_filters( 'aiopod_query_args', $args, $attributes ); $aiopod_query = new WP_Query( $args ); if ( $aiopod_query->have_posts() ) { $posts = $aiopod_query->posts; $post_id = (int) $posts[0]; } } } // Validate the post ID if ( empty( $post_id ) ) { return ''; } // Check post status and permissions $post_status = get_post_status( $post_id ); if ( 'publish' !== $post_status ) { return ''; } // Check for password protection if ( post_password_required( $post_id ) ) { return ''; } // Check user permissions if ( ! aiopod_current_user_can( 'view_aiopod_podcast', $post_id ) ) { return aiopod_get_access_denied_message( $post_id, 'episode' ); } // Enqueue dependencies wp_enqueue_style( AIOPOD_PLUGIN_SLUG . '-public' ); wp_enqueue_script( AIOPOD_PLUGIN_SLUG . '-public' ); // Generate the player card $post = get_post( $post_id ); ob_start(); echo '
'; aiopod_the_player_card( $post, $attributes ); echo '
'; $content = ob_get_clean(); return $content; } /** * Filter the post content. * * @since 1.0.0 * @param string $content Content of the current post. * @return string $content Modified Content. */ public function the_content( $content ) { $in_the_loop = apply_filters( 'aiopod_the_content_in_the_loop', in_the_loop() ); $is_main_query = apply_filters( 'aiopod_the_content_is_main_query', is_main_query() ); if ( is_singular( 'aiopod_episodes' ) && $in_the_loop && $is_main_query ) { global $wp_the_query, $post; $post_id = $post->ID; if ( $wp_the_query->get_queried_object_id() !== $post_id ) { return $content; } if ( post_password_required( $post_id ) ) { return $content; } if ( ! aiopod_current_user_can( 'view_aiopod_podcast', $post_id ) ) { return aiopod_get_access_denied_message( $post_id, 'episode', $content ); } // Vars $episode_settings = aiopod_get_option( 'aiopod_episode_settings' ); $attributes = array_merge( $this->get_defaults(), array( 'show_related_episodes' => $episode_settings['show_related_episodes'], 'related_episodes_title' => $episode_settings['related_episodes_title'], 'show_participants' => $episode_settings['show_participants'], 'participants_title' => $episode_settings['participants_title'] ) ); // Enqueue dependencies wp_enqueue_style( AIOPOD_PLUGIN_SLUG . '-public' ); wp_enqueue_script( AIOPOD_PLUGIN_SLUG . '-public' ); // Process output ob_start(); include apply_filters( 'aiopod_load_template', AIOPOD_PLUGIN_DIR . 'public/templates/single-episode.php' ); $content = ob_get_clean(); } return $content; } /** * Filters timestamps in the episode description and wraps them with links. * * @since 1.0.0 * @access private * @param string $content The original episode description. * @return string The modified description with timestamps wrapped in links. */ private function wrap_timestamps_with_links( $content ) { $player_settings = aiopod_get_option( 'aiopod_player_settings' ); if ( isset( $player_settings['controls']['chapters'] ) ) { $pattern = '/(\d{1,2}:)?\d{1,2}:\d{2}/'; // Replace timestamps with anchor tags $content = preg_replace_callback( $pattern, function( $matches ) { $timestamp = $matches[0]; return sprintf( '%s', absint( aiopod_convert_time_to_seconds( $timestamp ) ), esc_html( $timestamp ) ); }, $content ); } return $content; } /** * Get the transcripts the episode page shows, ready for its template. * * The usable transcripts that could be read, in editor order. A timed transcript comes as * passages of cues by the same speaker; an untimed one as HTML with paragraphs and clickable * timestamps. An HTML file keeps the markup allowed in posts; plain text is always escaped. * * The transcript in the podcast's own language is marked as the original: the language being * spoken. The others are translations of it, read along with the same audio. * * @since 1.0.5 * @access private * @param WP_Post $post Episode post object. * @return array Transcripts, each with `row` (the transcript row), `language` (code), * `label` (language name), `is_original`, `passages` (timed) and `html` * (untimed). */ private function get_transcripts( $post ) { $transcripts = array(); if ( ! get_post_meta( $post->ID, 'enable_transcription', true ) ) { return $transcripts; } include AIOPOD_PLUGIN_DIR . 'includes/podcast-languages.php'; $podcast_language = aiopod_normalize_language_code( aiopod_get_podcast_language( (int) $post->post_parent ) ); foreach ( aiopod_get_usable_transcripts( $post->ID ) as $row ) { $loaded = aiopod_load_transcript( $row, $post->ID ); if ( '' !== $loaded['error'] ) continue; $transcript = array( 'row' => $row, 'language' => aiopod_normalize_language_code( '' !== $row['language'] ? $row['language'] : $podcast_language ), 'label' => '', 'is_original' => false, 'passages' => array(), 'html' => '' ); if ( ! empty( $loaded['cues'] ) ) { $transcript['passages'] = aiopod_group_transcript_passages( $loaded['cues'] ); } elseif ( 'html' === aiopod_get_transcript_format( $loaded['type'] ) ) { $transcript['html'] = wp_kses_post( $this->wrap_timestamps_with_links( $loaded['text'] ) ); } else { // Plain text is text, whatever it contains: escaped, never rendered as markup $transcript['html'] = wpautop( $this->wrap_timestamps_with_links( esc_html( $loaded['text'] ) ) ); } $transcripts[] = $transcript; } // Name each language plainly ("Portuguese"), adding the region only when two transcripts // share a language and need telling apart ("Portuguese (Brazil)") $primaries = array_count_values( array_map( function( $transcript ) { return strtok( $transcript['language'], '-' ); }, $transcripts ) ); foreach ( $transcripts as $index => $transcript ) { $language = $transcript['language']; $primary = strtok( $language, '-' ); if ( 1 === $primaries[ $primary ] && isset( $aiopod_languages[ $primary ] ) ) { $label = $aiopod_languages[ $primary ]; } elseif ( isset( $aiopod_languages[ $language ] ) ) { $label = $aiopod_languages[ $language ]; } else { $label = strtoupper( $language ); } $transcripts[ $index ]['label'] = $label; } // The original is the transcript in the podcast's language: the exact code first, then the same main language $original = aiopod_match_transcript_language( array_column( $transcripts, 'language' ), $podcast_language ); if ( false !== $original ) { $transcripts[ $original ]['is_original'] = true; } return $transcripts; } /** * Filters whether the current episode post is open for comments. * * @since 1.0.0 * @param bool $open Whether the current post is open for comments. * @param int $post_id The post ID. * @return bool $open True if the comments are open, false if not. */ public function comments_open( $open, $post_id ) { if ( $post_id > 0 ) { $post_type = get_post_type( $post_id ); if ( 'aiopod_episodes' === $post_type ) { $episode_settings = aiopod_get_option( 'aiopod_episode_settings' ); // Forcefully enable comments on all the episode pages if ( 'open' === $episode_settings['comments'] ) { $open = true; } // Forcefully disable comments on all the episode pages if ( 'closed' === $episode_settings['comments'] ) { $open = false; } } } return $open; } /** * Get the default shortcode attribute values. * * @since 1.0.0 * @return array An associative array of attributes. */ public function get_defaults() { if ( empty( $this->defaults ) ) { $player_settings = aiopod_get_option( 'aiopod_player_settings' ); $fields = aiopod_get_shortcode_fields(); foreach ( $fields['episodes']['sections']['player']['fields'] as $field ) { $this->defaults[ $field['name'] ] = $field['value']; } $this->defaults['source'] = 'episode'; $this->defaults['id'] = ''; $this->defaults['podcast'] = ''; $this->defaults['tags'] = ''; $this->defaults['participants'] = ''; $this->defaults['type'] = ''; $this->defaults['featured'] = 0; $this->defaults['season'] = ''; $this->defaults['orderby'] = 'date'; $this->defaults['order'] = 'desc'; $this->defaults['player_hotkeys'] = $player_settings['hotkeys']; } return $this->defaults; } }