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:
499
includes/WooCommerce/AlbumProduct.php
Normal file
499
includes/WooCommerce/AlbumProduct.php
Normal file
@@ -0,0 +1,499 @@
|
||||
<?php
|
||||
/**
|
||||
* Album Product Type for WooCommerce.
|
||||
*
|
||||
* @package WP_FediStream
|
||||
*/
|
||||
|
||||
namespace WP_FediStream\WooCommerce;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* FediStream Album product type.
|
||||
*
|
||||
* Digital product representing a FediStream album.
|
||||
*/
|
||||
class AlbumProduct extends \WC_Product {
|
||||
|
||||
/**
|
||||
* Product type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $product_type = 'fedistream_album';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int|\WC_Product|object $product Product ID or object.
|
||||
*/
|
||||
public function __construct( $product = 0 ) {
|
||||
parent::__construct( $product );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type(): string {
|
||||
return 'fedistream_album';
|
||||
}
|
||||
|
||||
/**
|
||||
* Albums are virtual products.
|
||||
*
|
||||
* @param string $context View or edit context.
|
||||
* @return bool
|
||||
*/
|
||||
public function get_virtual( $context = 'view' ): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Albums are downloadable products.
|
||||
*
|
||||
* @param string $context View or edit context.
|
||||
* @return bool
|
||||
*/
|
||||
public function get_downloadable( $context = 'view' ): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the linked album ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_linked_album_id(): int {
|
||||
return (int) $this->get_meta( '_fedistream_linked_album', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the linked album post.
|
||||
*
|
||||
* @return \WP_Post|null
|
||||
*/
|
||||
public function get_linked_album(): ?\WP_Post {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$album = get_post( $album_id );
|
||||
|
||||
if ( ! $album || 'fedistream_album' !== $album->post_type ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $album;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pricing type.
|
||||
*
|
||||
* @return string fixed, pwyw, or nyp
|
||||
*/
|
||||
public function get_pricing_type(): string {
|
||||
return $this->get_meta( '_fedistream_pricing_type', true ) ?: 'fixed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum price for PWYW.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function get_min_price(): float {
|
||||
return (float) $this->get_meta( '_fedistream_min_price', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get suggested price for PWYW.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function get_suggested_price(): float {
|
||||
return (float) $this->get_meta( '_fedistream_suggested_price', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if streaming is included.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function includes_streaming(): bool {
|
||||
return 'yes' === $this->get_meta( '_fedistream_include_streaming', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available download formats.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_available_formats(): array {
|
||||
$formats = $this->get_meta( '_fedistream_available_formats', true );
|
||||
|
||||
return is_array( $formats ) ? $formats : array( 'mp3' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tracks in this album.
|
||||
*
|
||||
* @return array Array of WP_Post objects.
|
||||
*/
|
||||
public function get_tracks(): array {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$tracks = get_posts(
|
||||
array(
|
||||
'post_type' => 'fedistream_track',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'meta_key' => '_fedistream_track_number', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
array(
|
||||
'key' => '_fedistream_album_id',
|
||||
'value' => $album_id,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return $tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get track count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_track_count(): int {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = get_post_meta( $album_id, '_fedistream_total_tracks', true );
|
||||
|
||||
if ( $count ) {
|
||||
return (int) $count;
|
||||
}
|
||||
|
||||
return count( $this->get_tracks() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total duration in seconds.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_duration(): int {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$duration = get_post_meta( $album_id, '_fedistream_total_duration', true );
|
||||
|
||||
if ( $duration ) {
|
||||
return (int) $duration;
|
||||
}
|
||||
|
||||
// Calculate from tracks.
|
||||
$tracks = $this->get_tracks();
|
||||
$duration = 0;
|
||||
|
||||
foreach ( $tracks as $track ) {
|
||||
$duration += (int) get_post_meta( $track->ID, '_fedistream_duration', true );
|
||||
}
|
||||
|
||||
return $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted duration.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_formatted_duration(): string {
|
||||
$seconds = $this->get_total_duration();
|
||||
|
||||
if ( ! $seconds ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hours = floor( $seconds / 3600 );
|
||||
$mins = floor( ( $seconds % 3600 ) / 60 );
|
||||
$secs = $seconds % 60;
|
||||
|
||||
if ( $hours > 0 ) {
|
||||
return sprintf( '%d:%02d:%02d', $hours, $mins, $secs );
|
||||
}
|
||||
|
||||
return sprintf( '%d:%02d', $mins, $secs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get artist name(s).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_artist_name(): string {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$artist_id = get_post_meta( $album_id, '_fedistream_album_artist', true );
|
||||
|
||||
if ( ! $artist_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$artist = get_post( $artist_id );
|
||||
|
||||
return $artist ? $artist->post_title : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get album artwork URL.
|
||||
*
|
||||
* @param string $size Image size.
|
||||
* @return string
|
||||
*/
|
||||
public function get_album_artwork( string $size = 'medium' ): string {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $album_id );
|
||||
|
||||
if ( ! $thumbnail_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$image = wp_get_attachment_image_url( $thumbnail_id, $size );
|
||||
|
||||
return $image ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get release date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_release_date(): string {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return get_post_meta( $album_id, '_fedistream_release_date', true ) ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get album type (album, ep, single, compilation).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_album_type(): string {
|
||||
$album_id = $this->get_linked_album_id();
|
||||
|
||||
if ( ! $album_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return get_post_meta( $album_id, '_fedistream_album_type', true ) ?: 'album';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get downloads for this product.
|
||||
*
|
||||
* Generates downloadable files based on available formats.
|
||||
*
|
||||
* @param string $context View or edit context.
|
||||
* @return array
|
||||
*/
|
||||
public function get_downloads( $context = 'view' ): array {
|
||||
$downloads = parent::get_downloads( $context );
|
||||
|
||||
// If no manual downloads set, generate from linked album.
|
||||
if ( empty( $downloads ) && $this->get_linked_album_id() ) {
|
||||
$downloads = $this->generate_album_downloads();
|
||||
}
|
||||
|
||||
return $downloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate download files from linked album.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generate_album_downloads(): array {
|
||||
$downloads = array();
|
||||
$tracks = $this->get_tracks();
|
||||
$formats = $this->get_available_formats();
|
||||
$album = $this->get_linked_album();
|
||||
|
||||
if ( empty( $tracks ) || ! $album ) {
|
||||
return $downloads;
|
||||
}
|
||||
|
||||
// For each format, create a download entry.
|
||||
foreach ( $formats as $format ) {
|
||||
$format_label = strtoupper( $format );
|
||||
|
||||
// Create album ZIP download entry.
|
||||
$download_id = 'album-' . $album->ID . '-' . $format;
|
||||
|
||||
$downloads[ $download_id ] = array(
|
||||
'id' => $download_id,
|
||||
'name' => sprintf(
|
||||
/* translators: 1: Album name, 2: Format name */
|
||||
__( '%1$s (%2$s)', 'wp-fedistream' ),
|
||||
$album->post_title,
|
||||
$format_label
|
||||
),
|
||||
'file' => add_query_arg(
|
||||
array(
|
||||
'fedistream_download' => 'album',
|
||||
'album_id' => $album->ID,
|
||||
'format' => $format,
|
||||
),
|
||||
home_url( '/' )
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $downloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if purchasable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_purchasable(): bool {
|
||||
// Must have a linked album.
|
||||
if ( ! $this->get_linked_album_id() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check price for fixed pricing.
|
||||
if ( 'fixed' === $this->get_pricing_type() ) {
|
||||
return $this->get_price() !== '' && $this->get_price() >= 0;
|
||||
}
|
||||
|
||||
// PWYW and NYP are always purchasable.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get price HTML.
|
||||
*
|
||||
* @param string $price Price HTML.
|
||||
* @return string
|
||||
*/
|
||||
public function get_price_html( $price = '' ): string {
|
||||
$pricing_type = $this->get_pricing_type();
|
||||
|
||||
if ( 'nyp' === $pricing_type ) {
|
||||
return '<span class="fedistream-nyp-price">' . esc_html__( 'Name Your Price', 'wp-fedistream' ) . '</span>';
|
||||
}
|
||||
|
||||
if ( 'pwyw' === $pricing_type ) {
|
||||
$min_price = $this->get_min_price();
|
||||
$suggested = $this->get_suggested_price();
|
||||
|
||||
$html = '<span class="fedistream-pwyw-price">';
|
||||
|
||||
if ( $min_price > 0 ) {
|
||||
$html .= sprintf(
|
||||
/* translators: %s: Minimum price */
|
||||
esc_html__( 'From %s', 'wp-fedistream' ),
|
||||
wc_price( $min_price )
|
||||
);
|
||||
} else {
|
||||
$html .= esc_html__( 'Pay What You Want', 'wp-fedistream' );
|
||||
}
|
||||
|
||||
if ( $suggested > 0 ) {
|
||||
$html .= ' <span class="fedistream-suggested">';
|
||||
$html .= sprintf(
|
||||
/* translators: %s: Suggested price */
|
||||
esc_html__( '(Suggested: %s)', 'wp-fedistream' ),
|
||||
wc_price( $suggested )
|
||||
);
|
||||
$html .= '</span>';
|
||||
}
|
||||
|
||||
$html .= '</span>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
return parent::get_price_html( $price );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to cart validation for PWYW products.
|
||||
*
|
||||
* @param bool $passed Validation passed.
|
||||
* @param int $product_id Product ID.
|
||||
* @param int $quantity Quantity.
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate_add_to_cart( bool $passed, int $product_id, int $quantity ): bool {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( ! $product || 'fedistream_album' !== $product->get_type() ) {
|
||||
return $passed;
|
||||
}
|
||||
|
||||
$pricing_type = $product->get_pricing_type();
|
||||
|
||||
if ( 'pwyw' === $pricing_type || 'nyp' === $pricing_type ) {
|
||||
// Check if custom price is set.
|
||||
$custom_price = isset( $_POST['fedistream_custom_price'] ) ? // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['fedistream_custom_price'] ) ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
0;
|
||||
|
||||
$min_price = $product->get_min_price();
|
||||
|
||||
if ( 'pwyw' === $pricing_type && $custom_price < $min_price ) {
|
||||
wc_add_notice(
|
||||
sprintf(
|
||||
/* translators: %s: Minimum price */
|
||||
__( 'Please enter at least %s', 'wp-fedistream' ),
|
||||
wc_price( $min_price )
|
||||
),
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store custom price in session for cart.
|
||||
WC()->session->set( 'fedistream_custom_price_' . $product_id, $custom_price );
|
||||
}
|
||||
|
||||
return $passed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user