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:
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