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;
}
}