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