feat: Initial release v0.1.0

WP FediStream - Stream music over ActivityPub

Features:
- Custom post types: Artist, Album, Track, Playlist
- Custom taxonomies: Genre, Mood, License
- User roles: Artist, Label
- Admin dashboard with statistics
- Frontend templates and shortcodes
- Audio player with queue management
- ActivityPub integration with actor support
- WooCommerce product types for albums/tracks
- User library with favorites and history
- Notification system (in-app and email)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 23:23:05 +01:00
commit 4a5d7b9f4d
91 changed files with 22750 additions and 0 deletions

172
includes/Frontend/Ajax.php Normal file
View File

@@ -0,0 +1,172 @@
<?php
/**
* AJAX handlers for frontend functionality.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles AJAX requests for the frontend.
*/
class Ajax {
/**
* Constructor.
*/
public function __construct() {
// Track data endpoint (public).
add_action( 'wp_ajax_fedistream_get_track', array( $this, 'get_track' ) );
add_action( 'wp_ajax_nopriv_fedistream_get_track', array( $this, 'get_track' ) );
// Record play endpoint (public).
add_action( 'wp_ajax_fedistream_record_play', array( $this, 'record_play' ) );
add_action( 'wp_ajax_nopriv_fedistream_record_play', array( $this, 'record_play' ) );
}
/**
* Get track data via AJAX.
*
* @return void
*/
public function get_track(): void {
// Verify nonce.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp-fedistream-nonce' ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'wp-fedistream' ) ) );
}
$track_id = isset( $_POST['track_id'] ) ? absint( $_POST['track_id'] ) : 0;
if ( ! $track_id ) {
wp_send_json_error( array( 'message' => __( 'Invalid track ID.', 'wp-fedistream' ) ) );
}
$track = get_post( $track_id );
if ( ! $track || 'fedistream_track' !== $track->post_type ) {
wp_send_json_error( array( 'message' => __( 'Track not found.', 'wp-fedistream' ) ) );
}
// Check if track is published.
if ( 'publish' !== $track->post_status ) {
wp_send_json_error( array( 'message' => __( 'Track not available.', 'wp-fedistream' ) ) );
}
// Get audio file.
$audio_id = get_post_meta( $track_id, '_fedistream_audio_file', true );
$audio_url = $audio_id ? wp_get_attachment_url( $audio_id ) : '';
if ( ! $audio_url ) {
wp_send_json_error( array( 'message' => __( 'No audio file available.', 'wp-fedistream' ) ) );
}
// Get track metadata.
$thumbnail_id = get_post_thumbnail_id( $track_id );
$thumbnail = $thumbnail_id ? wp_get_attachment_image_url( $thumbnail_id, 'medium' ) : '';
// Get album info.
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
$album = $album_id ? get_post( $album_id ) : null;
// Get artists.
$artist_ids = get_post_meta( $track_id, '_fedistream_artist_ids', true );
$artists = array();
if ( is_array( $artist_ids ) && ! empty( $artist_ids ) ) {
foreach ( $artist_ids as $artist_id ) {
$artist = get_post( $artist_id );
if ( $artist && 'fedistream_artist' === $artist->post_type ) {
$artists[] = array(
'id' => $artist->ID,
'name' => $artist->post_title,
'link' => get_permalink( $artist->ID ),
);
}
}
}
// Get duration.
$duration = get_post_meta( $track_id, '_fedistream_duration', true );
$duration_formatted = '';
if ( $duration ) {
$mins = floor( $duration / 60 );
$secs = $duration % 60;
$duration_formatted = $mins . ':' . str_pad( $secs, 2, '0', STR_PAD_LEFT );
}
$data = array(
'id' => $track->ID,
'title' => $track->post_title,
'permalink' => get_permalink( $track->ID ),
'audio_url' => $audio_url,
'thumbnail' => $thumbnail,
'artists' => $artists,
'artist' => ! empty( $artists ) ? $artists[0]['name'] : '',
'album' => $album ? $album->post_title : '',
'album_id' => $album ? $album->ID : 0,
'album_link' => $album ? get_permalink( $album->ID ) : '',
'duration' => $duration,
'duration_formatted' => $duration_formatted,
'explicit' => (bool) get_post_meta( $track_id, '_fedistream_explicit', true ),
);
wp_send_json_success( $data );
}
/**
* Record a track play via AJAX.
*
* @return void
*/
public function record_play(): void {
// Verify nonce.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp-fedistream-nonce' ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'wp-fedistream' ) ) );
}
$track_id = isset( $_POST['track_id'] ) ? absint( $_POST['track_id'] ) : 0;
if ( ! $track_id ) {
wp_send_json_error( array( 'message' => __( 'Invalid track ID.', 'wp-fedistream' ) ) );
}
$track = get_post( $track_id );
if ( ! $track || 'fedistream_track' !== $track->post_type ) {
wp_send_json_error( array( 'message' => __( 'Track not found.', 'wp-fedistream' ) ) );
}
global $wpdb;
// Insert play record.
$table = $wpdb->prefix . 'fedistream_plays';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->insert(
$table,
array(
'track_id' => $track_id,
'user_id' => get_current_user_id() ?: null,
'played_at' => current_time( 'mysql' ),
),
array( '%d', '%d', '%s' )
);
// Update play count in post meta.
$play_count = (int) get_post_meta( $track_id, '_fedistream_play_count', true );
update_post_meta( $track_id, '_fedistream_play_count', $play_count + 1 );
wp_send_json_success(
array(
'message' => __( 'Play recorded.', 'wp-fedistream' ),
'play_count' => $play_count + 1,
)
);
}
}

View File

@@ -0,0 +1,513 @@
<?php
/**
* Shortcodes handler.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend;
use WP_FediStream\Plugin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Registers and handles all plugin shortcodes.
*/
class Shortcodes {
/**
* Plugin instance.
*
* @var Plugin
*/
private Plugin $plugin;
/**
* Constructor.
*/
public function __construct() {
$this->plugin = Plugin::get_instance();
$this->register_shortcodes();
}
/**
* 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 {
$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 ) {
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 {
$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 ) {
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 {
$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 ) {
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 {
$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 ) {
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 {
$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 {
$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 {
$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 {
$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 ) ) {
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 {
try {
return $this->plugin->render( $template, $context );
} catch ( \Exception $e ) {
if ( WP_DEBUG ) {
return '<p class="fedistream-error">' . esc_html( $e->getMessage() ) . '</p>';
}
return '';
}
}
}

View File

@@ -0,0 +1,528 @@
<?php
/**
* Template loader for frontend display.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend;
use WP_FediStream\Plugin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* TemplateLoader class.
*
* Handles loading custom templates for FediStream post types.
*/
class TemplateLoader {
/**
* Constructor.
*/
public function __construct() {
add_filter( 'template_include', array( $this, 'template_include' ) );
add_filter( 'body_class', array( $this, 'body_class' ) );
}
/**
* Include custom templates for FediStream post types.
*
* @param string $template Template path.
* @return string Modified template path.
*/
public function template_include( string $template ): string {
// Check if we're on a FediStream page.
if ( is_singular( 'fedistream_artist' ) ) {
return $this->get_template( 'single-artist' );
}
if ( is_singular( 'fedistream_album' ) ) {
return $this->get_template( 'single-album' );
}
if ( is_singular( 'fedistream_track' ) ) {
return $this->get_template( 'single-track' );
}
if ( is_singular( 'fedistream_playlist' ) ) {
return $this->get_template( 'single-playlist' );
}
if ( is_post_type_archive( 'fedistream_artist' ) ) {
return $this->get_template( 'archive-artist' );
}
if ( is_post_type_archive( 'fedistream_album' ) ) {
return $this->get_template( 'archive-album' );
}
if ( is_post_type_archive( 'fedistream_track' ) ) {
return $this->get_template( 'archive-track' );
}
if ( is_post_type_archive( 'fedistream_playlist' ) ) {
return $this->get_template( 'archive-playlist' );
}
if ( is_tax( 'fedistream_genre' ) ) {
return $this->get_template( 'taxonomy-genre' );
}
if ( is_tax( 'fedistream_mood' ) ) {
return $this->get_template( 'taxonomy-mood' );
}
return $template;
}
/**
* Get template file path.
*
* First checks theme for override, then uses plugin template.
*
* @param string $template_name Template name without extension.
* @return string Template path.
*/
private function get_template( string $template_name ): string {
// Check theme for override.
$theme_template = locate_template(
array(
"fedistream/{$template_name}.php",
"fedistream/{$template_name}.twig",
)
);
if ( $theme_template ) {
return $theme_template;
}
// Use plugin template wrapper.
return WP_FEDISTREAM_PATH . 'includes/Frontend/template-wrapper.php';
}
/**
* Add FediStream classes to body.
*
* @param array $classes Body classes.
* @return array Modified classes.
*/
public function body_class( array $classes ): array {
if ( $this->is_fedistream_page() ) {
$classes[] = 'fedistream';
if ( is_singular( 'fedistream_artist' ) ) {
$classes[] = 'fedistream-artist';
$classes[] = 'fedistream-single';
} elseif ( is_singular( 'fedistream_album' ) ) {
$classes[] = 'fedistream-album';
$classes[] = 'fedistream-single';
} elseif ( is_singular( 'fedistream_track' ) ) {
$classes[] = 'fedistream-track';
$classes[] = 'fedistream-single';
} elseif ( is_singular( 'fedistream_playlist' ) ) {
$classes[] = 'fedistream-playlist';
$classes[] = 'fedistream-single';
} elseif ( is_post_type_archive( 'fedistream_artist' ) ) {
$classes[] = 'fedistream-archive';
$classes[] = 'fedistream-artists';
} elseif ( is_post_type_archive( 'fedistream_album' ) ) {
$classes[] = 'fedistream-archive';
$classes[] = 'fedistream-albums';
} elseif ( is_post_type_archive( 'fedistream_track' ) ) {
$classes[] = 'fedistream-archive';
$classes[] = 'fedistream-tracks';
} elseif ( is_post_type_archive( 'fedistream_playlist' ) ) {
$classes[] = 'fedistream-archive';
$classes[] = 'fedistream-playlists';
} elseif ( is_tax( 'fedistream_genre' ) || is_tax( 'fedistream_mood' ) ) {
$classes[] = 'fedistream-archive';
$classes[] = 'fedistream-taxonomy';
}
}
return $classes;
}
/**
* Check if current page is a FediStream page.
*
* @return bool
*/
public function is_fedistream_page(): bool {
return is_singular( array( 'fedistream_artist', 'fedistream_album', 'fedistream_track', 'fedistream_playlist' ) )
|| is_post_type_archive( array( 'fedistream_artist', 'fedistream_album', 'fedistream_track', 'fedistream_playlist' ) )
|| is_tax( array( 'fedistream_genre', 'fedistream_mood', 'fedistream_license' ) );
}
/**
* Get template context for current page.
*
* @return array Template context.
*/
public static function get_context(): array {
$context = array(
'site_name' => get_bloginfo( 'name' ),
'site_url' => home_url(),
'is_singular' => is_singular(),
'is_archive' => is_archive(),
'current_url' => get_permalink(),
);
if ( is_singular() ) {
global $post;
$context['post'] = self::get_post_data( $post );
}
if ( is_post_type_archive() || is_tax() ) {
$context['posts'] = self::get_archive_posts();
$context['pagination'] = self::get_pagination();
$context['archive_title'] = self::get_archive_title();
$context['archive_description'] = self::get_archive_description();
}
return $context;
}
/**
* Get post data for template.
*
* @param \WP_Post $post Post object.
* @return array Post data.
*/
public static function get_post_data( \WP_Post $post ): array {
$data = array(
'id' => $post->ID,
'title' => get_the_title( $post ),
'content' => apply_filters( 'the_content', $post->post_content ),
'excerpt' => get_the_excerpt( $post ),
'permalink' => get_permalink( $post ),
'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'large' ),
'date' => get_the_date( '', $post ),
'author' => get_the_author_meta( 'display_name', $post->post_author ),
);
// Add post type specific data.
switch ( $post->post_type ) {
case 'fedistream_artist':
$data = array_merge( $data, self::get_artist_data( $post->ID ) );
break;
case 'fedistream_album':
$data = array_merge( $data, self::get_album_data( $post->ID ) );
break;
case 'fedistream_track':
$data = array_merge( $data, self::get_track_data( $post->ID ) );
break;
case 'fedistream_playlist':
$data = array_merge( $data, self::get_playlist_data( $post->ID ) );
break;
}
// Add taxonomies.
$data['genres'] = self::get_terms( $post->ID, 'fedistream_genre' );
$data['moods'] = self::get_terms( $post->ID, 'fedistream_mood' );
return $data;
}
/**
* Get artist-specific data.
*
* @param int $post_id Post ID.
* @return array Artist data.
*/
private static function get_artist_data( int $post_id ): array {
$type = get_post_meta( $post_id, '_fedistream_artist_type', true ) ?: 'solo';
$types = array(
'solo' => __( 'Solo Artist', 'wp-fedistream' ),
'band' => __( 'Band', 'wp-fedistream' ),
'duo' => __( 'Duo', 'wp-fedistream' ),
'collective' => __( 'Collective', 'wp-fedistream' ),
);
$albums = get_posts(
array(
'post_type' => 'fedistream_album',
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_key' => '_fedistream_album_artist',
'meta_value' => $post_id,
'orderby' => 'meta_value',
'meta_query' => array(
array(
'key' => '_fedistream_album_release_date',
'compare' => 'EXISTS',
),
),
'order' => 'DESC',
)
);
return array(
'artist_type' => $type,
'artist_type_label' => $types[ $type ] ?? $types['solo'],
'formed_date' => get_post_meta( $post_id, '_fedistream_artist_formed_date', true ),
'location' => get_post_meta( $post_id, '_fedistream_artist_location', true ),
'website' => get_post_meta( $post_id, '_fedistream_artist_website', true ),
'social_links' => get_post_meta( $post_id, '_fedistream_artist_social_links', true ) ?: array(),
'members' => get_post_meta( $post_id, '_fedistream_artist_members', true ) ?: array(),
'albums' => array_map( array( __CLASS__, 'get_post_data' ), $albums ),
'album_count' => count( $albums ),
);
}
/**
* Get album-specific data.
*
* @param int $post_id Post ID.
* @return array Album data.
*/
private static function get_album_data( int $post_id ): array {
$type = get_post_meta( $post_id, '_fedistream_album_type', true ) ?: 'album';
$types = array(
'album' => __( 'Album', 'wp-fedistream' ),
'ep' => __( 'EP', 'wp-fedistream' ),
'single' => __( 'Single', 'wp-fedistream' ),
'compilation' => __( 'Compilation', 'wp-fedistream' ),
'live' => __( 'Live Album', 'wp-fedistream' ),
'remix' => __( 'Remix Album', 'wp-fedistream' ),
);
$artist_id = get_post_meta( $post_id, '_fedistream_album_artist', true );
$tracks = get_posts(
array(
'post_type' => 'fedistream_track',
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_key' => '_fedistream_track_album',
'meta_value' => $post_id,
'orderby' => 'meta_value_num',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '_fedistream_track_number',
'compare' => 'EXISTS',
),
),
'order' => 'ASC',
)
);
return array(
'album_type' => $type,
'album_type_label' => $types[ $type ] ?? $types['album'],
'release_date' => get_post_meta( $post_id, '_fedistream_album_release_date', true ),
'release_year' => date( 'Y', strtotime( get_post_meta( $post_id, '_fedistream_album_release_date', true ) ?: 'now' ) ),
'artist_id' => $artist_id,
'artist_name' => $artist_id ? get_the_title( $artist_id ) : '',
'artist_url' => $artist_id ? get_permalink( $artist_id ) : '',
'upc' => get_post_meta( $post_id, '_fedistream_album_upc', true ),
'catalog_number' => get_post_meta( $post_id, '_fedistream_album_catalog_number', true ),
'total_tracks' => count( $tracks ),
'total_duration' => (int) get_post_meta( $post_id, '_fedistream_album_total_duration', true ),
'tracks' => array_map( array( __CLASS__, 'get_post_data' ), $tracks ),
);
}
/**
* Get track-specific data.
*
* @param int $post_id Post ID.
* @return array Track data.
*/
private static function get_track_data( int $post_id ): array {
$album_id = get_post_meta( $post_id, '_fedistream_track_album', true );
$audio_file = get_post_meta( $post_id, '_fedistream_track_audio_file', true );
$artists = get_post_meta( $post_id, '_fedistream_track_artists', true ) ?: array();
$duration = (int) get_post_meta( $post_id, '_fedistream_track_duration', true );
$artist_data = array();
foreach ( $artists as $artist_id ) {
$artist = get_post( $artist_id );
if ( $artist ) {
$artist_data[] = array(
'id' => $artist_id,
'name' => $artist->post_title,
'url' => get_permalink( $artist_id ),
);
}
}
return array(
'track_number' => (int) get_post_meta( $post_id, '_fedistream_track_number', true ),
'disc_number' => (int) get_post_meta( $post_id, '_fedistream_track_disc_number', true ) ?: 1,
'duration' => $duration,
'duration_formatted' => $duration ? sprintf( '%d:%02d', floor( $duration / 60 ), $duration % 60 ) : '',
'audio_url' => $audio_file ? wp_get_attachment_url( $audio_file ) : '',
'audio_format' => get_post_meta( $post_id, '_fedistream_track_audio_format', true ),
'bpm' => (int) get_post_meta( $post_id, '_fedistream_track_bpm', true ),
'key' => get_post_meta( $post_id, '_fedistream_track_key', true ),
'explicit' => (bool) get_post_meta( $post_id, '_fedistream_track_explicit', true ),
'isrc' => get_post_meta( $post_id, '_fedistream_track_isrc', true ),
'album_id' => $album_id,
'album_title' => $album_id ? get_the_title( $album_id ) : '',
'album_url' => $album_id ? get_permalink( $album_id ) : '',
'album_artwork' => $album_id ? get_the_post_thumbnail_url( $album_id, 'medium' ) : '',
'artists' => $artist_data,
);
}
/**
* Get playlist-specific data.
*
* @param int $post_id Post ID.
* @return array Playlist data.
*/
private static function get_playlist_data( int $post_id ): array {
global $wpdb;
$table = $wpdb->prefix . 'fedistream_playlist_tracks';
$duration = (int) get_post_meta( $post_id, '_fedistream_playlist_total_duration', true );
// Get tracks.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$track_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT track_id FROM $table WHERE playlist_id = %d ORDER BY position ASC",
$post_id
)
);
$tracks = array();
foreach ( $track_ids as $track_id ) {
$track = get_post( $track_id );
if ( $track && 'publish' === $track->post_status ) {
$tracks[] = self::get_post_data( $track );
}
}
return array(
'visibility' => get_post_meta( $post_id, '_fedistream_playlist_visibility', true ) ?: 'public',
'collaborative' => (bool) get_post_meta( $post_id, '_fedistream_playlist_collaborative', true ),
'federated' => (bool) get_post_meta( $post_id, '_fedistream_playlist_federated', true ),
'track_count' => count( $tracks ),
'total_duration' => $duration,
'duration_formatted' => $duration >= 3600
? sprintf( '%d:%02d:%02d', floor( $duration / 3600 ), floor( ( $duration % 3600 ) / 60 ), $duration % 60 )
: sprintf( '%d:%02d', floor( $duration / 60 ), $duration % 60 ),
'tracks' => $tracks,
);
}
/**
* Get taxonomy terms for post.
*
* @param int $post_id Post ID.
* @param string $taxonomy Taxonomy name.
* @return array Terms with name and URL.
*/
private static function get_terms( int $post_id, string $taxonomy ): array {
$terms = get_the_terms( $post_id, $taxonomy );
if ( ! $terms || is_wp_error( $terms ) ) {
return array();
}
return array_map(
function ( $term ) {
return array(
'id' => $term->term_id,
'name' => $term->name,
'slug' => $term->slug,
'url' => get_term_link( $term ),
);
},
$terms
);
}
/**
* Get archive posts.
*
* @return array Posts for archive.
*/
private static function get_archive_posts(): array {
global $wp_query;
$posts = array();
if ( $wp_query->have_posts() ) {
while ( $wp_query->have_posts() ) {
$wp_query->the_post();
$posts[] = self::get_post_data( get_post() );
}
wp_reset_postdata();
}
return $posts;
}
/**
* Get pagination data.
*
* @return array Pagination data.
*/
private static function get_pagination(): array {
global $wp_query;
$total_pages = $wp_query->max_num_pages;
$current = max( 1, get_query_var( 'paged' ) );
return array(
'total_pages' => $total_pages,
'current_page' => $current,
'has_prev' => $current > 1,
'has_next' => $current < $total_pages,
'prev_url' => $current > 1 ? get_pagenum_link( $current - 1 ) : '',
'next_url' => $current < $total_pages ? get_pagenum_link( $current + 1 ) : '',
'links' => paginate_links(
array(
'total' => $total_pages,
'current' => $current,
'type' => 'array',
'prev_text' => __( '&laquo; Previous', 'wp-fedistream' ),
'next_text' => __( 'Next &raquo;', 'wp-fedistream' ),
)
) ?: array(),
);
}
/**
* Get archive title.
*
* @return string Archive title.
*/
private static function get_archive_title(): string {
if ( is_post_type_archive( 'fedistream_artist' ) ) {
return __( 'Artists', 'wp-fedistream' );
}
if ( is_post_type_archive( 'fedistream_album' ) ) {
return __( 'Albums', 'wp-fedistream' );
}
if ( is_post_type_archive( 'fedistream_track' ) ) {
return __( 'Tracks', 'wp-fedistream' );
}
if ( is_post_type_archive( 'fedistream_playlist' ) ) {
return __( 'Playlists', 'wp-fedistream' );
}
if ( is_tax() ) {
return single_term_title( '', false );
}
return get_the_archive_title();
}
/**
* Get archive description.
*
* @return string Archive description.
*/
private static function get_archive_description(): string {
if ( is_tax() ) {
return term_description();
}
return get_the_archive_description();
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Widgets handler.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Registers and manages all plugin widgets.
*/
class Widgets {
/**
* Constructor.
*/
public function __construct() {
add_action( 'widgets_init', array( $this, 'register_widgets' ) );
}
/**
* Register all widgets.
*
* @return void
*/
public function register_widgets(): void {
register_widget( Widgets\RecentReleasesWidget::class );
register_widget( Widgets\PopularTracksWidget::class );
register_widget( Widgets\FeaturedArtistWidget::class );
register_widget( Widgets\NowPlayingWidget::class );
}
}

View File

@@ -0,0 +1,162 @@
<?php
/**
* Featured Artist Widget.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend\Widgets;
use WP_FediStream\Frontend\TemplateLoader;
use WP_FediStream\Plugin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Displays a featured artist.
*/
class FeaturedArtistWidget extends \WP_Widget {
/**
* Constructor.
*/
public function __construct() {
parent::__construct(
'fedistream_featured_artist',
__( 'FediStream: Featured Artist', 'wp-fedistream' ),
array(
'description' => __( 'Display a featured artist.', 'wp-fedistream' ),
'classname' => 'fedistream-widget fedistream-widget--featured-artist',
)
);
}
/**
* Front-end display.
*
* @param array $args Widget arguments.
* @param array $instance Saved values from database.
* @return void
*/
public function widget( $args, $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Featured Artist', 'wp-fedistream' );
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
$artist_id = ! empty( $instance['artist_id'] ) ? absint( $instance['artist_id'] ) : 0;
$random = ! empty( $instance['random'] ) && $instance['random'];
$post = null;
if ( $random ) {
// Get a random artist.
$posts = get_posts(
array(
'post_type' => 'fedistream_artist',
'posts_per_page' => 1,
'orderby' => 'rand',
'post_status' => 'publish',
)
);
if ( ! empty( $posts ) ) {
$post = $posts[0];
}
} elseif ( $artist_id ) {
$post = get_post( $artist_id );
if ( $post && 'fedistream_artist' !== $post->post_type ) {
$post = null;
}
}
if ( ! $post ) {
return;
}
$artist_data = TemplateLoader::get_artist_data( $post );
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
try {
$plugin = Plugin::get_instance();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $plugin->render(
'widgets/featured-artist',
array(
'post' => $artist_data,
)
);
} catch ( \Exception $e ) {
if ( WP_DEBUG ) {
echo '<p class="fedistream-error">' . esc_html( $e->getMessage() ) . '</p>';
}
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Back-end widget form.
*
* @param array $instance Previously saved values from database.
* @return void
*/
public function form( $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Featured Artist', 'wp-fedistream' );
$artist_id = ! empty( $instance['artist_id'] ) ? absint( $instance['artist_id'] ) : 0;
$random = ! empty( $instance['random'] ) && $instance['random'];
// Get all artists for dropdown.
$artists = get_posts(
array(
'post_type' => 'fedistream_artist',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
'post_status' => 'publish',
)
);
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'wp-fedistream' ); ?></label>
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
</p>
<p>
<label>
<input type="checkbox" id="<?php echo esc_attr( $this->get_field_id( 'random' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'random' ) ); ?>" value="1" <?php checked( $random ); ?>>
<?php esc_html_e( 'Show random artist', 'wp-fedistream' ); ?>
</label>
</p>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'artist_id' ) ); ?>"><?php esc_html_e( 'Or select specific artist:', 'wp-fedistream' ); ?></label>
<select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'artist_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'artist_id' ) ); ?>">
<option value=""><?php esc_html_e( '-- Select Artist --', 'wp-fedistream' ); ?></option>
<?php foreach ( $artists as $artist ) : ?>
<option value="<?php echo esc_attr( $artist->ID ); ?>" <?php selected( $artist_id, $artist->ID ); ?>>
<?php echo esc_html( $artist->post_title ); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<?php
}
/**
* Sanitize widget form values as they are saved.
*
* @param array $new_instance Values just sent to be saved.
* @param array $old_instance Previously saved values from database.
* @return array Updated safe values to be saved.
*/
public function update( $new_instance, $old_instance ): array {
$instance = array();
$instance['title'] = ! empty( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['artist_id'] = ! empty( $new_instance['artist_id'] ) ? absint( $new_instance['artist_id'] ) : 0;
$instance['random'] = ! empty( $new_instance['random'] );
return $instance;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* Now Playing Widget.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend\Widgets;
use WP_FediStream\Plugin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Displays the currently playing track (updates via JavaScript).
*/
class NowPlayingWidget extends \WP_Widget {
/**
* Constructor.
*/
public function __construct() {
parent::__construct(
'fedistream_now_playing',
__( 'FediStream: Now Playing', 'wp-fedistream' ),
array(
'description' => __( 'Display the currently playing track.', 'wp-fedistream' ),
'classname' => 'fedistream-widget fedistream-widget--now-playing',
)
);
}
/**
* Front-end display.
*
* @param array $args Widget arguments.
* @param array $instance Saved values from database.
* @return void
*/
public function widget( $args, $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Now Playing', 'wp-fedistream' );
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
$show_player = ! empty( $instance['show_player'] );
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
try {
$plugin = Plugin::get_instance();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $plugin->render(
'widgets/now-playing',
array(
'show_player' => $show_player,
)
);
} catch ( \Exception $e ) {
if ( WP_DEBUG ) {
echo '<p class="fedistream-error">' . esc_html( $e->getMessage() ) . '</p>';
}
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Back-end widget form.
*
* @param array $instance Previously saved values from database.
* @return void
*/
public function form( $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Now Playing', 'wp-fedistream' );
$show_player = ! empty( $instance['show_player'] );
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'wp-fedistream' ); ?></label>
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
</p>
<p>
<label>
<input type="checkbox" id="<?php echo esc_attr( $this->get_field_id( 'show_player' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'show_player' ) ); ?>" value="1" <?php checked( $show_player ); ?>>
<?php esc_html_e( 'Show player controls', 'wp-fedistream' ); ?>
</label>
</p>
<p class="description">
<?php esc_html_e( 'This widget shows information about the currently playing track and updates automatically via JavaScript.', 'wp-fedistream' ); ?>
</p>
<?php
}
/**
* Sanitize widget form values as they are saved.
*
* @param array $new_instance Values just sent to be saved.
* @param array $old_instance Previously saved values from database.
* @return array Updated safe values to be saved.
*/
public function update( $new_instance, $old_instance ): array {
$instance = array();
$instance['title'] = ! empty( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['show_player'] = ! empty( $new_instance['show_player'] );
return $instance;
}
}

View File

@@ -0,0 +1,127 @@
<?php
/**
* Popular Tracks Widget.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend\Widgets;
use WP_FediStream\Frontend\TemplateLoader;
use WP_FediStream\Plugin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Displays popular tracks based on play count.
*/
class PopularTracksWidget extends \WP_Widget {
/**
* Constructor.
*/
public function __construct() {
parent::__construct(
'fedistream_popular_tracks',
__( 'FediStream: Popular Tracks', 'wp-fedistream' ),
array(
'description' => __( 'Display popular tracks by play count.', 'wp-fedistream' ),
'classname' => 'fedistream-widget fedistream-widget--popular-tracks',
)
);
}
/**
* Front-end display.
*
* @param array $args Widget arguments.
* @param array $instance Saved values from database.
* @return void
*/
public function widget( $args, $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Popular Tracks', 'wp-fedistream' );
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
$count = ! empty( $instance['count'] ) ? absint( $instance['count'] ) : 5;
$query_args = array(
'post_type' => 'fedistream_track',
'posts_per_page' => $count,
'orderby' => 'meta_value_num',
'meta_key' => '_fedistream_play_count',
'order' => 'DESC',
'post_status' => 'publish',
);
$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();
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
try {
$plugin = Plugin::get_instance();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $plugin->render(
'widgets/popular-tracks',
array(
'posts' => $posts,
)
);
} catch ( \Exception $e ) {
if ( WP_DEBUG ) {
echo '<p class="fedistream-error">' . esc_html( $e->getMessage() ) . '</p>';
}
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Back-end widget form.
*
* @param array $instance Previously saved values from database.
* @return void
*/
public function form( $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Popular Tracks', 'wp-fedistream' );
$count = ! empty( $instance['count'] ) ? absint( $instance['count'] ) : 5;
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'wp-fedistream' ); ?></label>
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
</p>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Number of tracks:', 'wp-fedistream' ); ?></label>
<input class="tiny-text" id="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'count' ) ); ?>" type="number" min="1" max="20" value="<?php echo esc_attr( $count ); ?>">
</p>
<?php
}
/**
* Sanitize widget form values as they are saved.
*
* @param array $new_instance Values just sent to be saved.
* @param array $old_instance Previously saved values from database.
* @return array Updated safe values to be saved.
*/
public function update( $new_instance, $old_instance ): array {
$instance = array();
$instance['title'] = ! empty( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['count'] = ! empty( $new_instance['count'] ) ? absint( $new_instance['count'] ) : 5;
return $instance;
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* Recent Releases Widget.
*
* @package WP_FediStream
*/
namespace WP_FediStream\Frontend\Widgets;
use WP_FediStream\Frontend\TemplateLoader;
use WP_FediStream\Plugin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Displays recent album releases.
*/
class RecentReleasesWidget extends \WP_Widget {
/**
* Constructor.
*/
public function __construct() {
parent::__construct(
'fedistream_recent_releases',
__( 'FediStream: Recent Releases', 'wp-fedistream' ),
array(
'description' => __( 'Display recent album releases.', 'wp-fedistream' ),
'classname' => 'fedistream-widget fedistream-widget--recent-releases',
)
);
}
/**
* Front-end display.
*
* @param array $args Widget arguments.
* @param array $instance Saved values from database.
* @return void
*/
public function widget( $args, $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Recent Releases', 'wp-fedistream' );
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
$count = ! empty( $instance['count'] ) ? absint( $instance['count'] ) : 5;
$type = ! empty( $instance['type'] ) ? $instance['type'] : '';
$query_args = array(
'post_type' => 'fedistream_album',
'posts_per_page' => $count,
'orderby' => 'meta_value',
'meta_key' => '_fedistream_release_date',
'order' => 'DESC',
'post_status' => 'publish',
);
if ( ! empty( $type ) ) {
$query_args['meta_query'][] = array(
'key' => '_fedistream_album_type',
'value' => $type,
);
}
$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();
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
try {
$plugin = Plugin::get_instance();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $plugin->render(
'widgets/recent-releases',
array(
'posts' => $posts,
)
);
} catch ( \Exception $e ) {
if ( WP_DEBUG ) {
echo '<p class="fedistream-error">' . esc_html( $e->getMessage() ) . '</p>';
}
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Back-end widget form.
*
* @param array $instance Previously saved values from database.
* @return void
*/
public function form( $instance ): void {
$title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Recent Releases', 'wp-fedistream' );
$count = ! empty( $instance['count'] ) ? absint( $instance['count'] ) : 5;
$type = ! empty( $instance['type'] ) ? $instance['type'] : '';
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'wp-fedistream' ); ?></label>
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
</p>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Number of releases:', 'wp-fedistream' ); ?></label>
<input class="tiny-text" id="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'count' ) ); ?>" type="number" min="1" max="20" value="<?php echo esc_attr( $count ); ?>">
</p>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'type' ) ); ?>"><?php esc_html_e( 'Release type:', 'wp-fedistream' ); ?></label>
<select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'type' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'type' ) ); ?>">
<option value="" <?php selected( $type, '' ); ?>><?php esc_html_e( 'All types', 'wp-fedistream' ); ?></option>
<option value="album" <?php selected( $type, 'album' ); ?>><?php esc_html_e( 'Album', 'wp-fedistream' ); ?></option>
<option value="ep" <?php selected( $type, 'ep' ); ?>><?php esc_html_e( 'EP', 'wp-fedistream' ); ?></option>
<option value="single" <?php selected( $type, 'single' ); ?>><?php esc_html_e( 'Single', 'wp-fedistream' ); ?></option>
<option value="compilation" <?php selected( $type, 'compilation' ); ?>><?php esc_html_e( 'Compilation', 'wp-fedistream' ); ?></option>
</select>
</p>
<?php
}
/**
* Sanitize widget form values as they are saved.
*
* @param array $new_instance Values just sent to be saved.
* @param array $old_instance Previously saved values from database.
* @return array Updated safe values to be saved.
*/
public function update( $new_instance, $old_instance ): array {
$instance = array();
$instance['title'] = ! empty( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['count'] = ! empty( $new_instance['count'] ) ? absint( $new_instance['count'] ) : 5;
$instance['type'] = ! empty( $new_instance['type'] ) ? sanitize_key( $new_instance['type'] ) : '';
return $instance;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Template wrapper for FediStream Twig templates.
*
* @package WP_FediStream
*/
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WP_FediStream\Plugin;
use WP_FediStream\Frontend\TemplateLoader;
// Get template context.
$context = TemplateLoader::get_context();
// Determine template name.
$template_name = '';
if ( is_singular( 'fedistream_artist' ) ) {
$template_name = 'single/artist';
} elseif ( is_singular( 'fedistream_album' ) ) {
$template_name = 'single/album';
} elseif ( is_singular( 'fedistream_track' ) ) {
$template_name = 'single/track';
} elseif ( is_singular( 'fedistream_playlist' ) ) {
$template_name = 'single/playlist';
} elseif ( is_post_type_archive( 'fedistream_artist' ) ) {
$template_name = 'archive/artist';
} elseif ( is_post_type_archive( 'fedistream_album' ) ) {
$template_name = 'archive/album';
} elseif ( is_post_type_archive( 'fedistream_track' ) ) {
$template_name = 'archive/track';
} elseif ( is_post_type_archive( 'fedistream_playlist' ) ) {
$template_name = 'archive/playlist';
} elseif ( is_tax( 'fedistream_genre' ) ) {
$template_name = 'archive/taxonomy';
$context['taxonomy_name'] = __( 'Genre', 'wp-fedistream' );
} elseif ( is_tax( 'fedistream_mood' ) ) {
$template_name = 'archive/taxonomy';
$context['taxonomy_name'] = __( 'Mood', 'wp-fedistream' );
}
// Get the plugin instance.
$plugin = Plugin::get_instance();
get_header();
?>
<main id="fedistream-content" class="fedistream-main">
<?php
if ( $template_name ) {
try {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $plugin->render( $template_name, $context );
} catch ( \Exception $e ) {
if ( WP_DEBUG ) {
echo '<div class="fedistream-error">';
echo '<p>' . esc_html__( 'Template Error:', 'wp-fedistream' ) . ' ' . esc_html( $e->getMessage() ) . '</p>';
echo '</div>';
}
}
} else {
// Fallback to default content.
if ( have_posts() ) {
while ( have_posts() ) {
the_post();
the_content();
}
}
}
?>
</main>
<?php
get_footer();