Files
wp-fedistream/includes/User/LibraryPage.php
magdev 4a5d7b9f4d 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>
2026-01-28 23:23:05 +01:00

325 lines
10 KiB
PHP

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