plugin = Plugin::get_instance(); $this->unlicensed_mode = $unlicensed_mode; $this->register_shortcodes(); } /** * Get the unlicensed message HTML. * * @return string */ private function get_unlicensed_message(): string { return '
' . '

' . esc_html__( 'This content requires a valid FediStream license.', 'wp-fedistream' ) . '

'; } /** * Register all shortcodes. * * @return void */ private function register_shortcodes(): void { add_shortcode( 'fedistream_artist', array( $this, 'render_artist' ) ); add_shortcode( 'fedistream_album', array( $this, 'render_album' ) ); add_shortcode( 'fedistream_track', array( $this, 'render_track' ) ); add_shortcode( 'fedistream_playlist', array( $this, 'render_playlist' ) ); add_shortcode( 'fedistream_latest_releases', array( $this, 'render_latest_releases' ) ); add_shortcode( 'fedistream_popular_tracks', array( $this, 'render_popular_tracks' ) ); add_shortcode( 'fedistream_artists', array( $this, 'render_artists_grid' ) ); add_shortcode( 'fedistream_player', array( $this, 'render_player' ) ); } /** * Render single artist shortcode. * * [fedistream_artist id="123" show_albums="true" show_tracks="true"] * * @param array $atts Shortcode attributes. * @return string */ public function render_artist( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'id' => 0, 'slug' => '', 'show_albums' => 'true', 'show_tracks' => 'true', 'layout' => 'full', // full, card, compact ), $atts, 'fedistream_artist' ); $post = $this->get_post( $atts, 'fedistream_artist' ); if ( ! $post ) { TemplateLoader::exit_shortcode_context(); return ''; } $context = array( 'post' => TemplateLoader::get_artist_data( $post ), 'show_albums' => filter_var( $atts['show_albums'], FILTER_VALIDATE_BOOLEAN ), 'show_tracks' => filter_var( $atts['show_tracks'], FILTER_VALIDATE_BOOLEAN ), 'layout' => sanitize_key( $atts['layout'] ), ); $template = 'card' === $atts['layout'] ? 'partials/card-artist' : 'shortcodes/artist'; return $this->render_template( $template, $context ); } /** * Render single album shortcode. * * [fedistream_album id="123" show_tracks="true"] * * @param array $atts Shortcode attributes. * @return string */ public function render_album( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'id' => 0, 'slug' => '', 'show_tracks' => 'true', 'layout' => 'full', // full, card, compact ), $atts, 'fedistream_album' ); $post = $this->get_post( $atts, 'fedistream_album' ); if ( ! $post ) { TemplateLoader::exit_shortcode_context(); return ''; } $context = array( 'post' => TemplateLoader::get_album_data( $post ), 'show_tracks' => filter_var( $atts['show_tracks'], FILTER_VALIDATE_BOOLEAN ), 'layout' => sanitize_key( $atts['layout'] ), ); $template = 'card' === $atts['layout'] ? 'partials/card-album' : 'shortcodes/album'; return $this->render_template( $template, $context ); } /** * Render single track shortcode. * * [fedistream_track id="123" show_player="true"] * * @param array $atts Shortcode attributes. * @return string */ public function render_track( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'id' => 0, 'slug' => '', 'show_player' => 'true', 'layout' => 'full', // full, card, compact ), $atts, 'fedistream_track' ); $post = $this->get_post( $atts, 'fedistream_track' ); if ( ! $post ) { TemplateLoader::exit_shortcode_context(); return ''; } $context = array( 'post' => TemplateLoader::get_track_data( $post ), 'show_player' => filter_var( $atts['show_player'], FILTER_VALIDATE_BOOLEAN ), 'layout' => sanitize_key( $atts['layout'] ), ); $template = 'card' === $atts['layout'] ? 'partials/card-track' : 'shortcodes/track'; return $this->render_template( $template, $context ); } /** * Render playlist shortcode. * * [fedistream_playlist id="123" show_tracks="true"] * * @param array $atts Shortcode attributes. * @return string */ public function render_playlist( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'id' => 0, 'slug' => '', 'show_tracks' => 'true', 'layout' => 'full', // full, card, compact ), $atts, 'fedistream_playlist' ); $post = $this->get_post( $atts, 'fedistream_playlist' ); if ( ! $post ) { TemplateLoader::exit_shortcode_context(); return ''; } $context = array( 'post' => TemplateLoader::get_playlist_data( $post ), 'show_tracks' => filter_var( $atts['show_tracks'], FILTER_VALIDATE_BOOLEAN ), 'layout' => sanitize_key( $atts['layout'] ), ); $template = 'card' === $atts['layout'] ? 'partials/card-playlist' : 'shortcodes/playlist'; return $this->render_template( $template, $context ); } /** * Render latest releases shortcode. * * [fedistream_latest_releases count="6" type="album" columns="3"] * * @param array $atts Shortcode attributes. * @return string */ public function render_latest_releases( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'count' => 6, 'type' => '', // album, ep, single, compilation or empty for all 'columns' => 3, 'artist' => 0, // Filter by artist ID ), $atts, 'fedistream_latest_releases' ); $query_args = array( 'post_type' => 'fedistream_album', 'posts_per_page' => absint( $atts['count'] ), 'orderby' => 'meta_value', 'meta_key' => '_fedistream_release_date', 'order' => 'DESC', 'post_status' => 'publish', ); // Filter by album type. if ( ! empty( $atts['type'] ) ) { $query_args['meta_query'][] = array( 'key' => '_fedistream_album_type', 'value' => sanitize_key( $atts['type'] ), ); } // Filter by artist. if ( ! empty( $atts['artist'] ) ) { $query_args['meta_query'][] = array( 'key' => '_fedistream_artist_id', 'value' => absint( $atts['artist'] ), ); } $query = new \WP_Query( $query_args ); $posts = array(); if ( $query->have_posts() ) { while ( $query->have_posts() ) { $query->the_post(); $posts[] = TemplateLoader::get_album_data( get_post() ); } wp_reset_postdata(); } $context = array( 'posts' => $posts, 'columns' => absint( $atts['columns'] ), 'title' => __( 'Latest Releases', 'wp-fedistream' ), ); return $this->render_template( 'shortcodes/releases-grid', $context ); } /** * Render popular tracks shortcode. * * [fedistream_popular_tracks count="10" columns="1"] * * @param array $atts Shortcode attributes. * @return string */ public function render_popular_tracks( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'count' => 10, 'columns' => 1, 'artist' => 0, // Filter by artist ID 'genre' => '', // Filter by genre slug ), $atts, 'fedistream_popular_tracks' ); $query_args = array( 'post_type' => 'fedistream_track', 'posts_per_page' => absint( $atts['count'] ), 'orderby' => 'meta_value_num', 'meta_key' => '_fedistream_play_count', 'order' => 'DESC', 'post_status' => 'publish', ); // Filter by artist. if ( ! empty( $atts['artist'] ) ) { $query_args['meta_query'][] = array( 'key' => '_fedistream_artist_ids', 'value' => absint( $atts['artist'] ), 'compare' => 'LIKE', ); } // Filter by genre. if ( ! empty( $atts['genre'] ) ) { $query_args['tax_query'][] = array( 'taxonomy' => 'fedistream_genre', 'field' => 'slug', 'terms' => sanitize_title( $atts['genre'] ), ); } $query = new \WP_Query( $query_args ); $posts = array(); if ( $query->have_posts() ) { while ( $query->have_posts() ) { $query->the_post(); $posts[] = TemplateLoader::get_track_data( get_post() ); } wp_reset_postdata(); } $context = array( 'posts' => $posts, 'columns' => absint( $atts['columns'] ), 'title' => __( 'Popular Tracks', 'wp-fedistream' ), ); return $this->render_template( 'shortcodes/tracks-list', $context ); } /** * Render artists grid shortcode. * * [fedistream_artists count="12" columns="4" type="band"] * * @param array $atts Shortcode attributes. * @return string */ public function render_artists_grid( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'count' => 12, 'columns' => 4, 'type' => '', // solo, band, duo, collective, or empty for all 'genre' => '', // Filter by genre slug 'orderby' => 'title', 'order' => 'ASC', ), $atts, 'fedistream_artists' ); $query_args = array( 'post_type' => 'fedistream_artist', 'posts_per_page' => absint( $atts['count'] ), 'orderby' => sanitize_key( $atts['orderby'] ), 'order' => 'DESC' === strtoupper( $atts['order'] ) ? 'DESC' : 'ASC', 'post_status' => 'publish', ); // Filter by artist type. if ( ! empty( $atts['type'] ) ) { $query_args['meta_query'][] = array( 'key' => '_fedistream_artist_type', 'value' => sanitize_key( $atts['type'] ), ); } // Filter by genre. if ( ! empty( $atts['genre'] ) ) { $query_args['tax_query'][] = array( 'taxonomy' => 'fedistream_genre', 'field' => 'slug', 'terms' => sanitize_title( $atts['genre'] ), ); } $query = new \WP_Query( $query_args ); $posts = array(); if ( $query->have_posts() ) { while ( $query->have_posts() ) { $query->the_post(); $posts[] = TemplateLoader::get_artist_data( get_post() ); } wp_reset_postdata(); } $context = array( 'posts' => $posts, 'columns' => absint( $atts['columns'] ), 'title' => __( 'Artists', 'wp-fedistream' ), ); return $this->render_template( 'shortcodes/artists-grid', $context ); } /** * Render audio player shortcode. * * [fedistream_player track="123" autoplay="false"] * * @param array $atts Shortcode attributes. * @return string */ public function render_player( array $atts ): string { // Enter shortcode context to prevent recursive shortcode processing during data loading. TemplateLoader::enter_shortcode_context(); $atts = shortcode_atts( array( 'track' => 0, 'playlist' => 0, 'album' => 0, 'autoplay' => 'false', 'style' => 'default', // default, compact, mini ), $atts, 'fedistream_player' ); $tracks = array(); // Get tracks based on source. if ( ! empty( $atts['track'] ) ) { $post = get_post( absint( $atts['track'] ) ); if ( $post && 'fedistream_track' === $post->post_type ) { $tracks[] = TemplateLoader::get_track_data( $post ); } } elseif ( ! empty( $atts['album'] ) ) { $album_id = absint( $atts['album'] ); $track_ids = get_post_meta( $album_id, '_fedistream_track_ids', true ); if ( is_array( $track_ids ) ) { foreach ( $track_ids as $track_id ) { $post = get_post( $track_id ); if ( $post && 'fedistream_track' === $post->post_type ) { $tracks[] = TemplateLoader::get_track_data( $post ); } } } } elseif ( ! empty( $atts['playlist'] ) ) { $playlist_id = absint( $atts['playlist'] ); $track_ids = get_post_meta( $playlist_id, '_fedistream_track_ids', true ); if ( is_array( $track_ids ) ) { foreach ( $track_ids as $track_id ) { $post = get_post( $track_id ); if ( $post && 'fedistream_track' === $post->post_type ) { $tracks[] = TemplateLoader::get_track_data( $post ); } } } } if ( empty( $tracks ) ) { TemplateLoader::exit_shortcode_context(); return ''; } $context = array( 'tracks' => $tracks, 'autoplay' => filter_var( $atts['autoplay'], FILTER_VALIDATE_BOOLEAN ), 'style' => sanitize_key( $atts['style'] ), ); return $this->render_template( 'shortcodes/player', $context ); } /** * Get post by ID or slug. * * @param array $atts Shortcode attributes. * @param string $post_type Post type. * @return \WP_Post|null */ private function get_post( array $atts, string $post_type ): ?\WP_Post { if ( ! empty( $atts['id'] ) ) { $post = get_post( absint( $atts['id'] ) ); if ( $post && $post->post_type === $post_type ) { return $post; } } if ( ! empty( $atts['slug'] ) ) { $posts = get_posts( array( 'name' => sanitize_title( $atts['slug'] ), 'post_type' => $post_type, 'posts_per_page' => 1, 'post_status' => 'publish', ) ); if ( ! empty( $posts ) ) { return $posts[0]; } } return null; } /** * Render a Twig template. * * @param string $template Template name. * @param array $context Template context. * @return string */ private function render_template( string $template, array $context ): string { // Block shortcode rendering while loading page template to prevent recursion. // This catches any shortcodes triggered by theme header/footer, widgets, etc. if ( TemplateLoader::is_loading_page_template() ) { return ''; } // Check for unlicensed mode. if ( $this->unlicensed_mode ) { return $this->get_unlicensed_message(); } // Enter shortcode context to prevent recursive shortcode processing. TemplateLoader::enter_shortcode_context(); try { $result = $this->plugin->render( $template, $context ); } catch ( \Exception $e ) { TemplateLoader::exit_shortcode_context(); if ( WP_DEBUG ) { return '

' . esc_html( $e->getMessage() ) . '

'; } return ''; } TemplateLoader::exit_shortcode_context(); return $result; } }