You've already forked wp-fedistream
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:
794
includes/User/Library.php
Normal file
794
includes/User/Library.php
Normal file
@@ -0,0 +1,794 @@
|
||||
<?php
|
||||
/**
|
||||
* User Library class.
|
||||
*
|
||||
* @package WP_FediStream
|
||||
*/
|
||||
|
||||
namespace WP_FediStream\User;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles user library features (favorites, follows, history).
|
||||
*/
|
||||
class Library {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'wp_ajax_fedistream_toggle_favorite', array( $this, 'ajax_toggle_favorite' ) );
|
||||
add_action( 'wp_ajax_fedistream_toggle_follow', array( $this, 'ajax_toggle_follow' ) );
|
||||
add_action( 'wp_ajax_fedistream_get_library', array( $this, 'ajax_get_library' ) );
|
||||
add_action( 'wp_ajax_fedistream_get_followed_artists', array( $this, 'ajax_get_followed_artists' ) );
|
||||
add_action( 'wp_ajax_fedistream_get_history', array( $this, 'ajax_get_history' ) );
|
||||
add_action( 'wp_ajax_fedistream_clear_history', array( $this, 'ajax_clear_history' ) );
|
||||
|
||||
// Add library buttons to content.
|
||||
add_filter( 'fedistream_track_actions', array( $this, 'add_favorite_button' ), 10, 2 );
|
||||
add_filter( 'fedistream_album_actions', array( $this, 'add_favorite_button' ), 10, 2 );
|
||||
add_filter( 'fedistream_artist_actions', array( $this, 'add_follow_button' ), 10, 2 );
|
||||
|
||||
// Record play history.
|
||||
add_action( 'fedistream_track_played', array( $this, 'record_play_history' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle favorite via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_toggle_favorite(): void {
|
||||
check_ajax_referer( 'fedistream_library', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$content_type = isset( $_POST['content_type'] ) ? sanitize_text_field( wp_unslash( $_POST['content_type'] ) ) : '';
|
||||
$content_id = isset( $_POST['content_id'] ) ? absint( $_POST['content_id'] ) : 0;
|
||||
|
||||
if ( ! in_array( $content_type, array( 'track', 'album', 'playlist' ), true ) || ! $content_id ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Invalid request.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$is_favorited = self::is_favorited( $user_id, $content_type, $content_id );
|
||||
|
||||
if ( $is_favorited ) {
|
||||
$result = self::remove_favorite( $user_id, $content_type, $content_id );
|
||||
$action = 'removed';
|
||||
} else {
|
||||
$result = self::add_favorite( $user_id, $content_type, $content_id );
|
||||
$action = 'added';
|
||||
}
|
||||
|
||||
if ( $result ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'action' => $action,
|
||||
'is_favorited' => ! $is_favorited,
|
||||
'message' => 'added' === $action
|
||||
? __( 'Added to your library.', 'wp-fedistream' )
|
||||
: __( 'Removed from your library.', 'wp-fedistream' ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to update library.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle follow via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_toggle_follow(): void {
|
||||
check_ajax_referer( 'fedistream_library', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$artist_id = isset( $_POST['artist_id'] ) ? absint( $_POST['artist_id'] ) : 0;
|
||||
|
||||
if ( ! $artist_id ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Invalid artist.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$is_following = self::is_following( $user_id, $artist_id );
|
||||
|
||||
if ( $is_following ) {
|
||||
$result = self::unfollow_artist( $user_id, $artist_id );
|
||||
$action = 'unfollowed';
|
||||
} else {
|
||||
$result = self::follow_artist( $user_id, $artist_id );
|
||||
$action = 'followed';
|
||||
}
|
||||
|
||||
if ( $result ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'action' => $action,
|
||||
'is_following' => ! $is_following,
|
||||
'message' => 'followed' === $action
|
||||
? __( 'You are now following this artist.', 'wp-fedistream' )
|
||||
: __( 'You unfollowed this artist.', 'wp-fedistream' ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to update follow status.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user library via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_library(): void {
|
||||
check_ajax_referer( 'fedistream_library', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : 'all';
|
||||
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
|
||||
$per_page = 20;
|
||||
|
||||
$library = self::get_user_library( $user_id, $type, $page, $per_page );
|
||||
|
||||
wp_send_json_success( $library );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get followed artists via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_followed_artists(): void {
|
||||
check_ajax_referer( 'fedistream_library', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
|
||||
$per_page = 20;
|
||||
|
||||
$artists = self::get_followed_artists( $user_id, $page, $per_page );
|
||||
|
||||
wp_send_json_success( $artists );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get listening history via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_history(): void {
|
||||
check_ajax_referer( 'fedistream_library', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
|
||||
$per_page = 50;
|
||||
|
||||
$history = self::get_listening_history( $user_id, $page, $per_page );
|
||||
|
||||
wp_send_json_success( $history );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear listening history via AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_clear_history(): void {
|
||||
check_ajax_referer( 'fedistream_library', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$result = self::clear_listening_history( $user_id );
|
||||
|
||||
if ( $result ) {
|
||||
wp_send_json_success( array( 'message' => __( 'History cleared.', 'wp-fedistream' ) ) );
|
||||
} else {
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to clear history.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a favorite.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $content_type Content type (track, album, playlist).
|
||||
* @param int $content_id Content ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function add_favorite( int $user_id, string $content_type, int $content_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_favorites';
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'content_type' => $content_type,
|
||||
'content_id' => $content_id,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%d', '%s', '%d', '%s' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
do_action( 'fedistream_favorite_added', $user_id, $content_type, $content_id );
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a favorite.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $content_type Content type (track, album, playlist).
|
||||
* @param int $content_id Content ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function remove_favorite( int $user_id, string $content_type, int $content_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_favorites';
|
||||
|
||||
$result = $wpdb->delete(
|
||||
$table,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'content_type' => $content_type,
|
||||
'content_id' => $content_id,
|
||||
),
|
||||
array( '%d', '%s', '%d' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
do_action( 'fedistream_favorite_removed', $user_id, $content_type, $content_id );
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content is favorited.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $content_type Content type.
|
||||
* @param int $content_id Content ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_favorited( int $user_id, string $content_type, int $content_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_favorites';
|
||||
|
||||
$exists = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT id FROM {$table} WHERE user_id = %d AND content_type = %s AND content_id = %d",
|
||||
$user_id,
|
||||
$content_type,
|
||||
$content_id
|
||||
)
|
||||
);
|
||||
|
||||
return (bool) $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow an artist.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $artist_id Artist post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function follow_artist( int $user_id, int $artist_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_user_follows';
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'artist_id' => $artist_id,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%d', '%d', '%s' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
do_action( 'fedistream_artist_followed', $user_id, $artist_id );
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow an artist.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $artist_id Artist post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function unfollow_artist( int $user_id, int $artist_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_user_follows';
|
||||
|
||||
$result = $wpdb->delete(
|
||||
$table,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'artist_id' => $artist_id,
|
||||
),
|
||||
array( '%d', '%d' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
do_action( 'fedistream_artist_unfollowed', $user_id, $artist_id );
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is following an artist.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $artist_id Artist post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_following( int $user_id, int $artist_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_user_follows';
|
||||
|
||||
$exists = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT id FROM {$table} WHERE user_id = %d AND artist_id = %d",
|
||||
$user_id,
|
||||
$artist_id
|
||||
)
|
||||
);
|
||||
|
||||
return (bool) $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record play history.
|
||||
*
|
||||
* @param int $track_id Track post ID.
|
||||
* @param int $user_id User ID (0 for anonymous).
|
||||
* @return void
|
||||
*/
|
||||
public function record_play_history( int $track_id, int $user_id ): void {
|
||||
if ( ! $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_listening_history';
|
||||
|
||||
// Check if recently played (within last 30 seconds) to avoid duplicates.
|
||||
$recent = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT id FROM {$table} WHERE user_id = %d AND track_id = %d AND played_at > DATE_SUB(NOW(), INTERVAL 30 SECOND)",
|
||||
$user_id,
|
||||
$track_id
|
||||
)
|
||||
);
|
||||
|
||||
if ( $recent ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'track_id' => $track_id,
|
||||
'played_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%d', '%d', '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user library.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $type Content type filter (all, tracks, albums, playlists).
|
||||
* @param int $page Page number.
|
||||
* @param int $per_page Items per page.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_user_library( int $user_id, string $type = 'all', int $page = 1, int $per_page = 20 ): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_favorites';
|
||||
$offset = ( $page - 1 ) * $per_page;
|
||||
|
||||
$where = 'WHERE user_id = %d';
|
||||
$params = array( $user_id );
|
||||
|
||||
if ( 'all' !== $type && in_array( $type, array( 'track', 'album', 'playlist' ), true ) ) {
|
||||
$where .= ' AND content_type = %s';
|
||||
$params[] = $type;
|
||||
}
|
||||
|
||||
$total = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} {$where}",
|
||||
...$params
|
||||
)
|
||||
);
|
||||
|
||||
$params[] = $per_page;
|
||||
$params[] = $offset;
|
||||
|
||||
$favorites = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT content_type, content_id, created_at FROM {$table} {$where} ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
...$params
|
||||
)
|
||||
);
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach ( $favorites as $favorite ) {
|
||||
$post = get_post( $favorite->content_id );
|
||||
|
||||
if ( ! $post || 'publish' !== $post->post_status ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array(
|
||||
'id' => $post->ID,
|
||||
'type' => $favorite->content_type,
|
||||
'title' => $post->post_title,
|
||||
'permalink' => get_permalink( $post ),
|
||||
'added_at' => $favorite->created_at,
|
||||
'thumbnail' => get_the_post_thumbnail_url( $post, 'medium' ),
|
||||
);
|
||||
|
||||
if ( 'track' === $favorite->content_type ) {
|
||||
$item['duration'] = get_post_meta( $post->ID, '_fedistream_duration', true );
|
||||
$item['artist'] = self::get_track_artist_name( $post->ID );
|
||||
} elseif ( 'album' === $favorite->content_type ) {
|
||||
$item['artist'] = self::get_album_artist_name( $post->ID );
|
||||
$item['track_count'] = get_post_meta( $post->ID, '_fedistream_total_tracks', true );
|
||||
}
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
return array(
|
||||
'items' => $items,
|
||||
'total' => (int) $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => (int) ceil( $total / $per_page ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get followed artists.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $page Page number.
|
||||
* @param int $per_page Items per page.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_followed_artists( int $user_id, int $page = 1, int $per_page = 20 ): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_user_follows';
|
||||
$offset = ( $page - 1 ) * $per_page;
|
||||
|
||||
$total = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d",
|
||||
$user_id
|
||||
)
|
||||
);
|
||||
|
||||
$follows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT artist_id, created_at FROM {$table} WHERE user_id = %d ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
$user_id,
|
||||
$per_page,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
|
||||
$artists = array();
|
||||
|
||||
foreach ( $follows as $follow ) {
|
||||
$post = get_post( $follow->artist_id );
|
||||
|
||||
if ( ! $post || 'publish' !== $post->post_status ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$artists[] = array(
|
||||
'id' => $post->ID,
|
||||
'name' => $post->post_title,
|
||||
'permalink' => get_permalink( $post ),
|
||||
'followed_at' => $follow->created_at,
|
||||
'thumbnail' => get_the_post_thumbnail_url( $post, 'thumbnail' ),
|
||||
'type' => get_post_meta( $post->ID, '_fedistream_artist_type', true ) ?: 'solo',
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'artists' => $artists,
|
||||
'total' => (int) $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => (int) ceil( $total / $per_page ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get listening history.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $page Page number.
|
||||
* @param int $per_page Items per page.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_listening_history( int $user_id, int $page = 1, int $per_page = 50 ): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_listening_history';
|
||||
$offset = ( $page - 1 ) * $per_page;
|
||||
|
||||
$total = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d",
|
||||
$user_id
|
||||
)
|
||||
);
|
||||
|
||||
$history = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT track_id, played_at FROM {$table} WHERE user_id = %d ORDER BY played_at DESC LIMIT %d OFFSET %d",
|
||||
$user_id,
|
||||
$per_page,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
|
||||
$tracks = array();
|
||||
|
||||
foreach ( $history as $item ) {
|
||||
$post = get_post( $item->track_id );
|
||||
|
||||
if ( ! $post || 'publish' !== $post->post_status ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tracks[] = array(
|
||||
'id' => $post->ID,
|
||||
'title' => $post->post_title,
|
||||
'permalink' => get_permalink( $post ),
|
||||
'played_at' => $item->played_at,
|
||||
'thumbnail' => get_the_post_thumbnail_url( $post, 'thumbnail' ),
|
||||
'duration' => get_post_meta( $post->ID, '_fedistream_duration', true ),
|
||||
'artist' => self::get_track_artist_name( $post->ID ),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'tracks' => $tracks,
|
||||
'total' => (int) $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => (int) ceil( $total / $per_page ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear listening history.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function clear_listening_history( int $user_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_listening_history';
|
||||
$result = $wpdb->delete( $table, array( 'user_id' => $user_id ), array( '%d' ) );
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get track artist name.
|
||||
*
|
||||
* @param int $track_id Track post ID.
|
||||
* @return string
|
||||
*/
|
||||
private static function get_track_artist_name( int $track_id ): string {
|
||||
$artist_ids = get_post_meta( $track_id, '_fedistream_artist_ids', true );
|
||||
|
||||
if ( is_array( $artist_ids ) && ! empty( $artist_ids ) ) {
|
||||
$names = array();
|
||||
foreach ( $artist_ids as $artist_id ) {
|
||||
$artist = get_post( $artist_id );
|
||||
if ( $artist ) {
|
||||
$names[] = $artist->post_title;
|
||||
}
|
||||
}
|
||||
return implode( ', ', $names );
|
||||
}
|
||||
|
||||
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
|
||||
$artist_id = $album_id ? get_post_meta( $album_id, '_fedistream_album_artist', true ) : 0;
|
||||
|
||||
if ( $artist_id ) {
|
||||
$artist = get_post( $artist_id );
|
||||
return $artist ? $artist->post_title : '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get album artist name.
|
||||
*
|
||||
* @param int $album_id Album post ID.
|
||||
* @return string
|
||||
*/
|
||||
private static function get_album_artist_name( int $album_id ): string {
|
||||
$artist_id = get_post_meta( $album_id, '_fedistream_album_artist', true );
|
||||
|
||||
if ( $artist_id ) {
|
||||
$artist = get_post( $artist_id );
|
||||
return $artist ? $artist->post_title : '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add favorite button to track/album actions.
|
||||
*
|
||||
* @param string $actions HTML actions.
|
||||
* @param int $post_id Post ID.
|
||||
* @return string
|
||||
*/
|
||||
public function add_favorite_button( string $actions, int $post_id ): string {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$content_type = str_replace( 'fedistream_', '', $post->post_type );
|
||||
$user_id = get_current_user_id();
|
||||
$is_favorited = self::is_favorited( $user_id, $content_type, $post_id );
|
||||
|
||||
$button = sprintf(
|
||||
'<button class="fedistream-favorite-btn%s" data-content-type="%s" data-content-id="%d" title="%s">
|
||||
<span class="dashicons dashicons-heart"></span>
|
||||
</button>',
|
||||
$is_favorited ? ' is-favorited' : '',
|
||||
esc_attr( $content_type ),
|
||||
$post_id,
|
||||
$is_favorited ? esc_attr__( 'Remove from library', 'wp-fedistream' ) : esc_attr__( 'Add to library', 'wp-fedistream' )
|
||||
);
|
||||
|
||||
return $actions . $button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add follow button to artist actions.
|
||||
*
|
||||
* @param string $actions HTML actions.
|
||||
* @param int $artist_id Artist post ID.
|
||||
* @return string
|
||||
*/
|
||||
public function add_follow_button( string $actions, int $artist_id ): string {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$is_following = self::is_following( $user_id, $artist_id );
|
||||
|
||||
$button = sprintf(
|
||||
'<button class="fedistream-follow-btn%s" data-artist-id="%d">
|
||||
<span class="dashicons dashicons-%s"></span>
|
||||
<span class="button-text">%s</span>
|
||||
</button>',
|
||||
$is_following ? ' is-following' : '',
|
||||
$artist_id,
|
||||
$is_following ? 'yes' : 'plus',
|
||||
$is_following ? esc_html__( 'Following', 'wp-fedistream' ) : esc_html__( 'Follow', 'wp-fedistream' )
|
||||
);
|
||||
|
||||
return $actions . $button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's favorite count.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $content_type Optional content type filter.
|
||||
* @return int
|
||||
*/
|
||||
public static function get_favorite_count( int $user_id, string $content_type = '' ): int {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_favorites';
|
||||
|
||||
if ( $content_type ) {
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d AND content_type = %s",
|
||||
$user_id,
|
||||
$content_type
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d",
|
||||
$user_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (int) $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's followed artist count.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return int
|
||||
*/
|
||||
public static function get_following_count( int $user_id ): int {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_user_follows';
|
||||
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d",
|
||||
$user_id
|
||||
)
|
||||
);
|
||||
|
||||
return (int) $count;
|
||||
}
|
||||
}
|
||||
324
includes/User/LibraryPage.php
Normal file
324
includes/User/LibraryPage.php
Normal file
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
/**
|
||||
* User Library Page class.
|
||||
*
|
||||
* @package WP_FediStream
|
||||
*/
|
||||
|
||||
namespace WP_FediStream\User;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the My Library page display.
|
||||
*/
|
||||
class LibraryPage {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_shortcode( 'fedistream_library', array( $this, 'render_library_shortcode' ) );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue library scripts and styles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts(): void {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'fedistream-library',
|
||||
WP_FEDISTREAM_URL . 'assets/js/library.js',
|
||||
array( 'jquery' ),
|
||||
WP_FEDISTREAM_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'fedistream-library',
|
||||
'fedistreamLibrary',
|
||||
array(
|
||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'fedistream_library' ),
|
||||
'i18n' => array(
|
||||
'loading' => __( 'Loading...', 'wp-fedistream' ),
|
||||
'noFavorites' => __( 'No favorites yet.', 'wp-fedistream' ),
|
||||
'noArtists' => __( 'Not following any artists yet.', 'wp-fedistream' ),
|
||||
'noHistory' => __( 'No listening history.', 'wp-fedistream' ),
|
||||
'confirmClear' => __( 'Are you sure you want to clear your listening history?', 'wp-fedistream' ),
|
||||
'historyCleared' => __( 'History cleared.', 'wp-fedistream' ),
|
||||
'error' => __( 'An error occurred. Please try again.', 'wp-fedistream' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the library shortcode.
|
||||
*
|
||||
* @param array $atts Shortcode attributes.
|
||||
* @return string
|
||||
*/
|
||||
public function render_library_shortcode( array $atts = array() ): string {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'tab' => 'favorites',
|
||||
),
|
||||
$atts,
|
||||
'fedistream_library'
|
||||
);
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return $this->render_login_required();
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Get counts for tabs.
|
||||
$favorite_count = Library::get_favorite_count( $user_id );
|
||||
$following_count = Library::get_following_count( $user_id );
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="fedistream-library" data-initial-tab="<?php echo esc_attr( $atts['tab'] ); ?>">
|
||||
<nav class="fedistream-library-nav">
|
||||
<button class="tab-btn active" data-tab="favorites">
|
||||
<?php esc_html_e( 'Favorites', 'wp-fedistream' ); ?>
|
||||
<span class="count"><?php echo esc_html( $favorite_count ); ?></span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="artists">
|
||||
<?php esc_html_e( 'Artists', 'wp-fedistream' ); ?>
|
||||
<span class="count"><?php echo esc_html( $following_count ); ?></span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="history">
|
||||
<?php esc_html_e( 'History', 'wp-fedistream' ); ?>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="fedistream-library-filters" data-tab="favorites">
|
||||
<select class="filter-type">
|
||||
<option value="all"><?php esc_html_e( 'All', 'wp-fedistream' ); ?></option>
|
||||
<option value="track"><?php esc_html_e( 'Tracks', 'wp-fedistream' ); ?></option>
|
||||
<option value="album"><?php esc_html_e( 'Albums', 'wp-fedistream' ); ?></option>
|
||||
<option value="playlist"><?php esc_html_e( 'Playlists', 'wp-fedistream' ); ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="fedistream-library-content">
|
||||
<div class="tab-content active" id="tab-favorites">
|
||||
<div class="library-grid favorites-grid">
|
||||
<!-- Loaded via AJAX -->
|
||||
</div>
|
||||
<div class="library-pagination" data-tab="favorites"></div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="tab-artists">
|
||||
<div class="library-grid artists-grid">
|
||||
<!-- Loaded via AJAX -->
|
||||
</div>
|
||||
<div class="library-pagination" data-tab="artists"></div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="tab-history">
|
||||
<div class="history-actions">
|
||||
<button class="btn-clear-history">
|
||||
<?php esc_html_e( 'Clear History', 'wp-fedistream' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="library-list history-list">
|
||||
<!-- Loaded via AJAX -->
|
||||
</div>
|
||||
<div class="library-pagination" data-tab="history"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fedistream-library-loading">
|
||||
<span class="spinner"></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render login required message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function render_login_required(): string {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="fedistream-login-required">
|
||||
<p><?php esc_html_e( 'Please log in to view your library.', 'wp-fedistream' ); ?></p>
|
||||
<a href="<?php echo esc_url( wp_login_url( get_permalink() ) ); ?>" class="btn btn-primary">
|
||||
<?php esc_html_e( 'Log In', 'wp-fedistream' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a favorite item.
|
||||
*
|
||||
* @param array $item Item data.
|
||||
* @return string
|
||||
*/
|
||||
public static function render_favorite_item( array $item ): string {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="library-item favorite-item" data-type="<?php echo esc_attr( $item['type'] ); ?>" data-id="<?php echo esc_attr( $item['id'] ); ?>">
|
||||
<div class="item-thumbnail">
|
||||
<?php if ( ! empty( $item['thumbnail'] ) ) : ?>
|
||||
<img src="<?php echo esc_url( $item['thumbnail'] ); ?>" alt="<?php echo esc_attr( $item['title'] ); ?>">
|
||||
<?php else : ?>
|
||||
<div class="placeholder-thumbnail">
|
||||
<span class="dashicons dashicons-<?php echo 'track' === $item['type'] ? 'format-audio' : ( 'album' === $item['type'] ? 'album' : 'playlist-audio' ); ?>"></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ( 'track' === $item['type'] ) : ?>
|
||||
<button class="play-btn" data-track-id="<?php echo esc_attr( $item['id'] ); ?>">
|
||||
<span class="dashicons dashicons-controls-play"></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<h4 class="item-title">
|
||||
<a href="<?php echo esc_url( $item['permalink'] ); ?>"><?php echo esc_html( $item['title'] ); ?></a>
|
||||
</h4>
|
||||
<?php if ( ! empty( $item['artist'] ) ) : ?>
|
||||
<p class="item-artist"><?php echo esc_html( $item['artist'] ); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ( 'track' === $item['type'] && ! empty( $item['duration'] ) ) : ?>
|
||||
<p class="item-duration"><?php echo esc_html( self::format_duration( $item['duration'] ) ); ?></p>
|
||||
<?php elseif ( 'album' === $item['type'] && ! empty( $item['track_count'] ) ) : ?>
|
||||
<p class="item-tracks">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: number of tracks */
|
||||
esc_html( _n( '%d track', '%d tracks', (int) $item['track_count'], 'wp-fedistream' ) ),
|
||||
(int) $item['track_count']
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<button class="unfavorite-btn" data-content-type="<?php echo esc_attr( $item['type'] ); ?>" data-content-id="<?php echo esc_attr( $item['id'] ); ?>" title="<?php esc_attr_e( 'Remove from library', 'wp-fedistream' ); ?>">
|
||||
<span class="dashicons dashicons-heart"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an artist item.
|
||||
*
|
||||
* @param array $artist Artist data.
|
||||
* @return string
|
||||
*/
|
||||
public static function render_artist_item( array $artist ): string {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="library-item artist-item" data-id="<?php echo esc_attr( $artist['id'] ); ?>">
|
||||
<div class="item-thumbnail artist-avatar">
|
||||
<?php if ( ! empty( $artist['thumbnail'] ) ) : ?>
|
||||
<img src="<?php echo esc_url( $artist['thumbnail'] ); ?>" alt="<?php echo esc_attr( $artist['name'] ); ?>">
|
||||
<?php else : ?>
|
||||
<div class="placeholder-thumbnail">
|
||||
<span class="dashicons dashicons-<?php echo 'band' === $artist['type'] ? 'groups' : 'admin-users'; ?>"></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<h4 class="item-title">
|
||||
<a href="<?php echo esc_url( $artist['permalink'] ); ?>"><?php echo esc_html( $artist['name'] ); ?></a>
|
||||
</h4>
|
||||
<p class="item-type">
|
||||
<?php echo 'band' === $artist['type'] ? esc_html__( 'Band', 'wp-fedistream' ) : esc_html__( 'Artist', 'wp-fedistream' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<button class="unfollow-btn" data-artist-id="<?php echo esc_attr( $artist['id'] ); ?>" title="<?php esc_attr_e( 'Unfollow', 'wp-fedistream' ); ?>">
|
||||
<span class="dashicons dashicons-minus"></span>
|
||||
<span class="button-text"><?php esc_html_e( 'Unfollow', 'wp-fedistream' ); ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a history item.
|
||||
*
|
||||
* @param array $track Track data.
|
||||
* @return string
|
||||
*/
|
||||
public static function render_history_item( array $track ): string {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="library-item history-item" data-id="<?php echo esc_attr( $track['id'] ); ?>">
|
||||
<div class="item-thumbnail">
|
||||
<?php if ( ! empty( $track['thumbnail'] ) ) : ?>
|
||||
<img src="<?php echo esc_url( $track['thumbnail'] ); ?>" alt="<?php echo esc_attr( $track['title'] ); ?>">
|
||||
<?php else : ?>
|
||||
<div class="placeholder-thumbnail">
|
||||
<span class="dashicons dashicons-format-audio"></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button class="play-btn" data-track-id="<?php echo esc_attr( $track['id'] ); ?>">
|
||||
<span class="dashicons dashicons-controls-play"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<h4 class="item-title">
|
||||
<a href="<?php echo esc_url( $track['permalink'] ); ?>"><?php echo esc_html( $track['title'] ); ?></a>
|
||||
</h4>
|
||||
<?php if ( ! empty( $track['artist'] ) ) : ?>
|
||||
<p class="item-artist"><?php echo esc_html( $track['artist'] ); ?></p>
|
||||
<?php endif; ?>
|
||||
<p class="item-played">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: relative time */
|
||||
esc_html__( 'Played %s', 'wp-fedistream' ),
|
||||
esc_html( human_time_diff( strtotime( $track['played_at'] ), current_time( 'timestamp' ) ) . ' ' . __( 'ago', 'wp-fedistream' ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-meta">
|
||||
<?php if ( ! empty( $track['duration'] ) ) : ?>
|
||||
<span class="item-duration"><?php echo esc_html( self::format_duration( $track['duration'] ) ); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration in seconds to MM:SS.
|
||||
*
|
||||
* @param int $seconds Duration in seconds.
|
||||
* @return string
|
||||
*/
|
||||
private static function format_duration( int $seconds ): string {
|
||||
$minutes = floor( $seconds / 60 );
|
||||
$secs = $seconds % 60;
|
||||
|
||||
return sprintf( '%d:%02d', $minutes, $secs );
|
||||
}
|
||||
}
|
||||
828
includes/User/Notifications.php
Normal file
828
includes/User/Notifications.php
Normal file
@@ -0,0 +1,828 @@
|
||||
<?php
|
||||
/**
|
||||
* User Notifications class.
|
||||
*
|
||||
* @package WP_FediStream
|
||||
*/
|
||||
|
||||
namespace WP_FediStream\User;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles user notifications (in-app and email).
|
||||
*/
|
||||
class Notifications {
|
||||
|
||||
/**
|
||||
* Notification types.
|
||||
*/
|
||||
const TYPE_NEW_RELEASE = 'new_release';
|
||||
const TYPE_NEW_FOLLOWER = 'new_follower';
|
||||
const TYPE_FEDIVERSE_LIKE = 'fediverse_like';
|
||||
const TYPE_FEDIVERSE_BOOST = 'fediverse_boost';
|
||||
const TYPE_PLAYLIST_ADDED = 'playlist_added';
|
||||
const TYPE_PURCHASE = 'purchase';
|
||||
const TYPE_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// AJAX handlers.
|
||||
add_action( 'wp_ajax_fedistream_get_notifications', array( $this, 'ajax_get_notifications' ) );
|
||||
add_action( 'wp_ajax_fedistream_mark_notification_read', array( $this, 'ajax_mark_read' ) );
|
||||
add_action( 'wp_ajax_fedistream_mark_all_notifications_read', array( $this, 'ajax_mark_all_read' ) );
|
||||
add_action( 'wp_ajax_fedistream_delete_notification', array( $this, 'ajax_delete' ) );
|
||||
|
||||
// Notification triggers.
|
||||
add_action( 'fedistream_album_published', array( $this, 'notify_new_release' ), 10, 1 );
|
||||
add_action( 'fedistream_track_published', array( $this, 'notify_new_track' ), 10, 1 );
|
||||
add_action( 'fedistream_artist_followed', array( $this, 'notify_artist_followed' ), 10, 2 );
|
||||
add_action( 'fedistream_activitypub_like_received', array( $this, 'notify_fediverse_like' ), 10, 2 );
|
||||
add_action( 'fedistream_activitypub_announce_received', array( $this, 'notify_fediverse_boost' ), 10, 2 );
|
||||
|
||||
// Email notifications.
|
||||
add_action( 'fedistream_notification_created', array( $this, 'maybe_send_email' ), 10, 2 );
|
||||
|
||||
// Admin bar notification count.
|
||||
add_action( 'admin_bar_menu', array( $this, 'add_notification_indicator' ), 100 );
|
||||
|
||||
// Enqueue scripts.
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue notification scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts(): void {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'fedistream-notifications',
|
||||
WP_FEDISTREAM_URL . 'assets/js/notifications.js',
|
||||
array( 'jquery' ),
|
||||
WP_FEDISTREAM_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'fedistream-notifications',
|
||||
'fedistreamNotifications',
|
||||
array(
|
||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'fedistream_notifications' ),
|
||||
'pollInterval' => 60000, // 1 minute.
|
||||
'i18n' => array(
|
||||
'noNotifications' => __( 'No notifications', 'wp-fedistream' ),
|
||||
'markAllRead' => __( 'Mark all as read', 'wp-fedistream' ),
|
||||
'viewAll' => __( 'View all notifications', 'wp-fedistream' ),
|
||||
'justNow' => __( 'Just now', 'wp-fedistream' ),
|
||||
'error' => __( 'An error occurred.', 'wp-fedistream' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a notification.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $type Notification type.
|
||||
* @param string $title Notification title.
|
||||
* @param string $message Notification message.
|
||||
* @param array $data Additional data.
|
||||
* @return int|false Notification ID or false on failure.
|
||||
*/
|
||||
public static function create( int $user_id, string $type, string $title, string $message, array $data = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_notifications';
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'type' => $type,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'data' => wp_json_encode( $data ),
|
||||
'is_read' => 0,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%d', '%s', '%s', '%s', '%s', '%d', '%s' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
$notification_id = $wpdb->insert_id;
|
||||
|
||||
/**
|
||||
* Fires when a notification is created.
|
||||
*
|
||||
* @param int $notification_id Notification ID.
|
||||
* @param array $notification Notification data.
|
||||
*/
|
||||
do_action(
|
||||
'fedistream_notification_created',
|
||||
$notification_id,
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'type' => $type,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
)
|
||||
);
|
||||
|
||||
return $notification_id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notifications for a user.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param bool $unread_only Only get unread notifications.
|
||||
* @param int $limit Number of notifications to retrieve.
|
||||
* @param int $offset Offset for pagination.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_for_user( int $user_id, bool $unread_only = false, int $limit = 20, int $offset = 0 ): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_notifications';
|
||||
$where = 'WHERE user_id = %d';
|
||||
$params = array( $user_id );
|
||||
|
||||
if ( $unread_only ) {
|
||||
$where .= ' AND is_read = 0';
|
||||
}
|
||||
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
|
||||
$notifications = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$table} {$where} ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
...$params
|
||||
)
|
||||
);
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ( $notifications as $notification ) {
|
||||
$result[] = array(
|
||||
'id' => (int) $notification->id,
|
||||
'type' => $notification->type,
|
||||
'title' => $notification->title,
|
||||
'message' => $notification->message,
|
||||
'data' => json_decode( $notification->data, true ) ?: array(),
|
||||
'is_read' => (bool) $notification->is_read,
|
||||
'created_at' => $notification->created_at,
|
||||
'read_at' => $notification->read_at,
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unread count for a user.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return int
|
||||
*/
|
||||
public static function get_unread_count( int $user_id ): int {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_notifications';
|
||||
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d AND is_read = 0",
|
||||
$user_id
|
||||
)
|
||||
);
|
||||
|
||||
return (int) $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a notification as read.
|
||||
*
|
||||
* @param int $notification_id Notification ID.
|
||||
* @param int $user_id User ID (for verification).
|
||||
* @return bool
|
||||
*/
|
||||
public static function mark_read( int $notification_id, int $user_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_notifications';
|
||||
|
||||
$result = $wpdb->update(
|
||||
$table,
|
||||
array(
|
||||
'is_read' => 1,
|
||||
'read_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array(
|
||||
'id' => $notification_id,
|
||||
'user_id' => $user_id,
|
||||
),
|
||||
array( '%d', '%s' ),
|
||||
array( '%d', '%d' )
|
||||
);
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all notifications as read for a user.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function mark_all_read( int $user_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_notifications';
|
||||
|
||||
$result = $wpdb->update(
|
||||
$table,
|
||||
array(
|
||||
'is_read' => 1,
|
||||
'read_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'is_read' => 0,
|
||||
),
|
||||
array( '%d', '%s' ),
|
||||
array( '%d', '%d' )
|
||||
);
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a notification.
|
||||
*
|
||||
* @param int $notification_id Notification ID.
|
||||
* @param int $user_id User ID (for verification).
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete( int $notification_id, int $user_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_notifications';
|
||||
|
||||
$result = $wpdb->delete(
|
||||
$table,
|
||||
array(
|
||||
'id' => $notification_id,
|
||||
'user_id' => $user_id,
|
||||
),
|
||||
array( '%d', '%d' )
|
||||
);
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Get notifications.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_notifications(): void {
|
||||
check_ajax_referer( 'fedistream_notifications', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Not logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$unread_only = isset( $_POST['unread_only'] ) && $_POST['unread_only'];
|
||||
$limit = isset( $_POST['limit'] ) ? absint( $_POST['limit'] ) : 20;
|
||||
$offset = isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0;
|
||||
|
||||
$notifications = self::get_for_user( $user_id, $unread_only, $limit, $offset );
|
||||
$unread_count = self::get_unread_count( $user_id );
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'notifications' => $notifications,
|
||||
'unread_count' => $unread_count,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Mark notification as read.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_mark_read(): void {
|
||||
check_ajax_referer( 'fedistream_notifications', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Not logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$notification_id = isset( $_POST['notification_id'] ) ? absint( $_POST['notification_id'] ) : 0;
|
||||
|
||||
if ( ! $notification_id ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Invalid notification.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$result = self::mark_read( $notification_id, $user_id );
|
||||
|
||||
if ( $result ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'unread_count' => self::get_unread_count( $user_id ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to update notification.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Mark all notifications as read.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_mark_all_read(): void {
|
||||
check_ajax_referer( 'fedistream_notifications', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Not logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$result = self::mark_all_read( $user_id );
|
||||
|
||||
if ( $result ) {
|
||||
wp_send_json_success( array( 'unread_count' => 0 ) );
|
||||
} else {
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to update notifications.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Delete notification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_delete(): void {
|
||||
check_ajax_referer( 'fedistream_notifications', 'nonce' );
|
||||
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Not logged in.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$notification_id = isset( $_POST['notification_id'] ) ? absint( $_POST['notification_id'] ) : 0;
|
||||
|
||||
if ( ! $notification_id ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Invalid notification.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$result = self::delete( $notification_id, $user_id );
|
||||
|
||||
if ( $result ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'unread_count' => self::get_unread_count( $user_id ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to delete notification.', 'wp-fedistream' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify followers of a new album release.
|
||||
*
|
||||
* @param int $album_id Album post ID.
|
||||
* @return void
|
||||
*/
|
||||
public function notify_new_release( int $album_id ): void {
|
||||
$album = get_post( $album_id );
|
||||
if ( ! $album ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$artist_id = get_post_meta( $album_id, '_fedistream_album_artist', true );
|
||||
if ( ! $artist_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$artist = get_post( $artist_id );
|
||||
if ( ! $artist ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all local followers of this artist.
|
||||
$followers = $this->get_artist_local_followers( $artist_id );
|
||||
|
||||
foreach ( $followers as $user_id ) {
|
||||
self::create(
|
||||
$user_id,
|
||||
self::TYPE_NEW_RELEASE,
|
||||
sprintf(
|
||||
/* translators: %s: artist name */
|
||||
__( 'New release from %s', 'wp-fedistream' ),
|
||||
$artist->post_title
|
||||
),
|
||||
sprintf(
|
||||
/* translators: 1: artist name, 2: album title */
|
||||
__( '%1$s released a new album: %2$s', 'wp-fedistream' ),
|
||||
$artist->post_title,
|
||||
$album->post_title
|
||||
),
|
||||
array(
|
||||
'album_id' => $album_id,
|
||||
'artist_id' => $artist_id,
|
||||
'album_url' => get_permalink( $album ),
|
||||
'artist_url' => get_permalink( $artist ),
|
||||
'thumbnail' => get_the_post_thumbnail_url( $album, 'thumbnail' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify followers of a new track.
|
||||
*
|
||||
* @param int $track_id Track post ID.
|
||||
* @return void
|
||||
*/
|
||||
public function notify_new_track( int $track_id ): void {
|
||||
$track = get_post( $track_id );
|
||||
if ( ! $track ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get artist from track or album.
|
||||
$artist_ids = get_post_meta( $track_id, '_fedistream_artist_ids', true );
|
||||
if ( empty( $artist_ids ) ) {
|
||||
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
|
||||
$artist_id = $album_id ? get_post_meta( $album_id, '_fedistream_album_artist', true ) : 0;
|
||||
$artist_ids = $artist_id ? array( $artist_id ) : array();
|
||||
}
|
||||
|
||||
if ( empty( $artist_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$artist_id = $artist_ids[0];
|
||||
$artist = get_post( $artist_id );
|
||||
if ( ! $artist ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only notify for single releases (tracks without album).
|
||||
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
|
||||
if ( $album_id ) {
|
||||
return; // Album release handles notification.
|
||||
}
|
||||
|
||||
$followers = $this->get_artist_local_followers( $artist_id );
|
||||
|
||||
foreach ( $followers as $user_id ) {
|
||||
self::create(
|
||||
$user_id,
|
||||
self::TYPE_NEW_RELEASE,
|
||||
sprintf(
|
||||
/* translators: %s: artist name */
|
||||
__( 'New track from %s', 'wp-fedistream' ),
|
||||
$artist->post_title
|
||||
),
|
||||
sprintf(
|
||||
/* translators: 1: artist name, 2: track title */
|
||||
__( '%1$s released a new track: %2$s', 'wp-fedistream' ),
|
||||
$artist->post_title,
|
||||
$track->post_title
|
||||
),
|
||||
array(
|
||||
'track_id' => $track_id,
|
||||
'artist_id' => $artist_id,
|
||||
'track_url' => get_permalink( $track ),
|
||||
'artist_url' => get_permalink( $artist ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify artist when they get a new local follower.
|
||||
*
|
||||
* @param int $user_id User ID who followed.
|
||||
* @param int $artist_id Artist post ID.
|
||||
* @return void
|
||||
*/
|
||||
public function notify_artist_followed( int $user_id, int $artist_id ): void {
|
||||
$artist = get_post( $artist_id );
|
||||
if ( ! $artist ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the artist's WordPress user ID.
|
||||
$artist_user_id = get_post_meta( $artist_id, '_fedistream_user_id', true );
|
||||
if ( ! $artist_user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$follower = get_user_by( 'id', $user_id );
|
||||
if ( ! $follower ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::create(
|
||||
$artist_user_id,
|
||||
self::TYPE_NEW_FOLLOWER,
|
||||
__( 'New follower', 'wp-fedistream' ),
|
||||
sprintf(
|
||||
/* translators: %s: follower display name */
|
||||
__( '%s started following you', 'wp-fedistream' ),
|
||||
$follower->display_name
|
||||
),
|
||||
array(
|
||||
'follower_id' => $user_id,
|
||||
'follower_name' => $follower->display_name,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify of a Fediverse like.
|
||||
*
|
||||
* @param int $content_id Content post ID.
|
||||
* @param array $actor Actor data.
|
||||
* @return void
|
||||
*/
|
||||
public function notify_fediverse_like( int $content_id, array $actor ): void {
|
||||
$post = get_post( $content_id );
|
||||
if ( ! $post ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$artist_user_id = $this->get_content_owner_user_id( $content_id );
|
||||
if ( ! $artist_user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actor_name = $actor['name'] ?? $actor['preferredUsername'] ?? __( 'Someone', 'wp-fedistream' );
|
||||
|
||||
self::create(
|
||||
$artist_user_id,
|
||||
self::TYPE_FEDIVERSE_LIKE,
|
||||
__( 'New like from Fediverse', 'wp-fedistream' ),
|
||||
sprintf(
|
||||
/* translators: 1: actor name, 2: content title */
|
||||
__( '%1$s liked your %2$s', 'wp-fedistream' ),
|
||||
$actor_name,
|
||||
$post->post_title
|
||||
),
|
||||
array(
|
||||
'content_id' => $content_id,
|
||||
'content_type' => $post->post_type,
|
||||
'actor_uri' => $actor['id'] ?? '',
|
||||
'actor_name' => $actor_name,
|
||||
'actor_icon' => $actor['icon']['url'] ?? '',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify of a Fediverse boost/announce.
|
||||
*
|
||||
* @param int $content_id Content post ID.
|
||||
* @param array $actor Actor data.
|
||||
* @return void
|
||||
*/
|
||||
public function notify_fediverse_boost( int $content_id, array $actor ): void {
|
||||
$post = get_post( $content_id );
|
||||
if ( ! $post ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$artist_user_id = $this->get_content_owner_user_id( $content_id );
|
||||
if ( ! $artist_user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actor_name = $actor['name'] ?? $actor['preferredUsername'] ?? __( 'Someone', 'wp-fedistream' );
|
||||
|
||||
self::create(
|
||||
$artist_user_id,
|
||||
self::TYPE_FEDIVERSE_BOOST,
|
||||
__( 'New boost from Fediverse', 'wp-fedistream' ),
|
||||
sprintf(
|
||||
/* translators: 1: actor name, 2: content title */
|
||||
__( '%1$s boosted your %2$s', 'wp-fedistream' ),
|
||||
$actor_name,
|
||||
$post->post_title
|
||||
),
|
||||
array(
|
||||
'content_id' => $content_id,
|
||||
'content_type' => $post->post_type,
|
||||
'actor_uri' => $actor['id'] ?? '',
|
||||
'actor_name' => $actor_name,
|
||||
'actor_icon' => $actor['icon']['url'] ?? '',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe send email notification.
|
||||
*
|
||||
* @param int $notification_id Notification ID.
|
||||
* @param array $notification Notification data.
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_send_email( int $notification_id, array $notification ): void {
|
||||
$user_id = $notification['user_id'];
|
||||
$type = $notification['type'];
|
||||
|
||||
// Check user preference for email notifications.
|
||||
$email_enabled = get_user_meta( $user_id, 'fedistream_email_notifications', true );
|
||||
if ( '0' === $email_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check specific notification type preference.
|
||||
$type_enabled = get_user_meta( $user_id, 'fedistream_email_' . $type, true );
|
||||
if ( '0' === $type_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
if ( ! $user || ! $user->user_email ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subject = sprintf(
|
||||
/* translators: 1: site name, 2: notification title */
|
||||
__( '[%1$s] %2$s', 'wp-fedistream' ),
|
||||
get_bloginfo( 'name' ),
|
||||
$notification['title']
|
||||
);
|
||||
|
||||
$message = $this->build_email_message( $notification );
|
||||
|
||||
$headers = array(
|
||||
'Content-Type: text/html; charset=UTF-8',
|
||||
);
|
||||
|
||||
wp_mail( $user->user_email, $subject, $message, $headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build email message HTML.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
* @return string
|
||||
*/
|
||||
private function build_email_message( array $notification ): string {
|
||||
$site_name = get_bloginfo( 'name' );
|
||||
$site_url = home_url();
|
||||
|
||||
$html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>';
|
||||
$html .= '<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">';
|
||||
$html .= '<h2 style="color: #333;">' . esc_html( $notification['title'] ) . '</h2>';
|
||||
$html .= '<p style="color: #666; font-size: 16px;">' . esc_html( $notification['message'] ) . '</p>';
|
||||
|
||||
// Add action link if available.
|
||||
$data = $notification['data'];
|
||||
$link = '';
|
||||
|
||||
if ( ! empty( $data['album_url'] ) ) {
|
||||
$link = $data['album_url'];
|
||||
} elseif ( ! empty( $data['track_url'] ) ) {
|
||||
$link = $data['track_url'];
|
||||
} elseif ( ! empty( $data['artist_url'] ) ) {
|
||||
$link = $data['artist_url'];
|
||||
}
|
||||
|
||||
if ( $link ) {
|
||||
$html .= '<p><a href="' . esc_url( $link ) . '" style="display: inline-block; padding: 10px 20px; background: #0073aa; color: #fff; text-decoration: none; border-radius: 4px;">' . esc_html__( 'View Details', 'wp-fedistream' ) . '</a></p>';
|
||||
}
|
||||
|
||||
$html .= '<hr style="margin: 30px 0; border: none; border-top: 1px solid #eee;">';
|
||||
$html .= '<p style="color: #999; font-size: 12px;">' . sprintf(
|
||||
/* translators: %s: site name */
|
||||
esc_html__( 'This email was sent by %s.', 'wp-fedistream' ),
|
||||
'<a href="' . esc_url( $site_url ) . '">' . esc_html( $site_name ) . '</a>'
|
||||
) . '</p>';
|
||||
$html .= '</div></body></html>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notification indicator to admin bar.
|
||||
*
|
||||
* @param \WP_Admin_Bar $admin_bar Admin bar instance.
|
||||
* @return void
|
||||
*/
|
||||
public function add_notification_indicator( \WP_Admin_Bar $admin_bar ): void {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$unread_count = self::get_unread_count( $user_id );
|
||||
|
||||
$title = '<span class="ab-icon dashicons dashicons-bell"></span>';
|
||||
if ( $unread_count > 0 ) {
|
||||
$title .= '<span class="fedistream-notification-count">' . esc_html( $unread_count ) . '</span>';
|
||||
}
|
||||
|
||||
$admin_bar->add_node(
|
||||
array(
|
||||
'id' => 'fedistream-notifications',
|
||||
'title' => $title,
|
||||
'href' => '#',
|
||||
'meta' => array(
|
||||
'class' => 'fedistream-notifications-menu',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local followers for an artist.
|
||||
*
|
||||
* @param int $artist_id Artist post ID.
|
||||
* @return array User IDs.
|
||||
*/
|
||||
private function get_artist_local_followers( int $artist_id ): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_user_follows';
|
||||
|
||||
$user_ids = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT user_id FROM {$table} WHERE artist_id = %d",
|
||||
$artist_id
|
||||
)
|
||||
);
|
||||
|
||||
return array_map( 'intval', $user_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WordPress user ID who owns a piece of content.
|
||||
*
|
||||
* @param int $content_id Content post ID.
|
||||
* @return int|null User ID or null.
|
||||
*/
|
||||
private function get_content_owner_user_id( int $content_id ): ?int {
|
||||
$post = get_post( $content_id );
|
||||
if ( ! $post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// For tracks, get artist.
|
||||
if ( 'fedistream_track' === $post->post_type ) {
|
||||
$artist_ids = get_post_meta( $content_id, '_fedistream_artist_ids', true );
|
||||
if ( ! empty( $artist_ids ) ) {
|
||||
$artist_id = $artist_ids[0];
|
||||
return (int) get_post_meta( $artist_id, '_fedistream_user_id', true ) ?: null;
|
||||
}
|
||||
|
||||
$album_id = get_post_meta( $content_id, '_fedistream_album_id', true );
|
||||
$artist_id = $album_id ? get_post_meta( $album_id, '_fedistream_album_artist', true ) : 0;
|
||||
if ( $artist_id ) {
|
||||
return (int) get_post_meta( $artist_id, '_fedistream_user_id', true ) ?: null;
|
||||
}
|
||||
}
|
||||
|
||||
// For albums, get artist.
|
||||
if ( 'fedistream_album' === $post->post_type ) {
|
||||
$artist_id = get_post_meta( $content_id, '_fedistream_album_artist', true );
|
||||
if ( $artist_id ) {
|
||||
return (int) get_post_meta( $artist_id, '_fedistream_user_id', true ) ?: null;
|
||||
}
|
||||
}
|
||||
|
||||
// For artists.
|
||||
if ( 'fedistream_artist' === $post->post_type ) {
|
||||
return (int) get_post_meta( $content_id, '_fedistream_user_id', true ) ?: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user