You've already forked wp-fedistream
521 lines
11 KiB
PHP
521 lines
11 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Track Product Type for WooCommerce.
|
||
|
|
*
|
||
|
|
* @package WP_FediStream
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace WP_FediStream\WooCommerce;
|
||
|
|
|
||
|
|
// Prevent direct file access.
|
||
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* FediStream Track product type.
|
||
|
|
*
|
||
|
|
* Digital product representing a single FediStream track.
|
||
|
|
*/
|
||
|
|
class TrackProduct extends \WC_Product {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Product type.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $product_type = 'fedistream_track';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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_track';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Tracks are virtual products.
|
||
|
|
*
|
||
|
|
* @param string $context View or edit context.
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function get_virtual( $context = 'view' ): bool {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Tracks are downloadable products.
|
||
|
|
*
|
||
|
|
* @param string $context View or edit context.
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function get_downloadable( $context = 'view' ): bool {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the linked track ID.
|
||
|
|
*
|
||
|
|
* @return int
|
||
|
|
*/
|
||
|
|
public function get_linked_track_id(): int {
|
||
|
|
return (int) $this->get_meta( '_fedistream_linked_track', true );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the linked track post.
|
||
|
|
*
|
||
|
|
* @return \WP_Post|null
|
||
|
|
*/
|
||
|
|
public function get_linked_track(): ?\WP_Post {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$track = get_post( $track_id );
|
||
|
|
|
||
|
|
if ( ! $track || 'fedistream_track' !== $track->post_type ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $track;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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 track duration in seconds.
|
||
|
|
*
|
||
|
|
* @return int
|
||
|
|
*/
|
||
|
|
public function get_duration(): int {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (int) get_post_meta( $track_id, '_fedistream_duration', true );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get formatted duration.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_formatted_duration(): string {
|
||
|
|
$seconds = $this->get_duration();
|
||
|
|
|
||
|
|
if ( ! $seconds ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$mins = floor( $seconds / 60 );
|
||
|
|
$secs = $seconds % 60;
|
||
|
|
|
||
|
|
return sprintf( '%d:%02d', $mins, $secs );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get artist name(s).
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_artist_name(): string {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$artist_ids = get_post_meta( $track_id, '_fedistream_artist_ids', true );
|
||
|
|
|
||
|
|
if ( ! is_array( $artist_ids ) || empty( $artist_ids ) ) {
|
||
|
|
// Fall back to album artist.
|
||
|
|
$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 '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$names = array();
|
||
|
|
foreach ( $artist_ids as $artist_id ) {
|
||
|
|
$artist = get_post( $artist_id );
|
||
|
|
if ( $artist ) {
|
||
|
|
$names[] = $artist->post_title;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return implode( ', ', $names );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get album name.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_album_name(): string {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
|
||
|
|
|
||
|
|
if ( ! $album_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$album = get_post( $album_id );
|
||
|
|
|
||
|
|
return $album ? $album->post_title : '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get track artwork URL.
|
||
|
|
*
|
||
|
|
* Falls back to album artwork if track has none.
|
||
|
|
*
|
||
|
|
* @param string $size Image size.
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_track_artwork( string $size = 'medium' ): string {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try track thumbnail first.
|
||
|
|
$thumbnail_id = get_post_thumbnail_id( $track_id );
|
||
|
|
|
||
|
|
// Fall back to album artwork.
|
||
|
|
if ( ! $thumbnail_id ) {
|
||
|
|
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
|
||
|
|
$thumbnail_id = $album_id ? get_post_thumbnail_id( $album_id ) : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! $thumbnail_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$image = wp_get_attachment_image_url( $thumbnail_id, $size );
|
||
|
|
|
||
|
|
return $image ?: '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get audio file URL.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_audio_url(): string {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$audio_id = get_post_meta( $track_id, '_fedistream_audio_file', true );
|
||
|
|
|
||
|
|
if ( ! $audio_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return wp_get_attachment_url( $audio_id ) ?: '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if track is explicit.
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function is_explicit(): bool {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (bool) get_post_meta( $track_id, '_fedistream_explicit', true );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get track BPM.
|
||
|
|
*
|
||
|
|
* @return int
|
||
|
|
*/
|
||
|
|
public function get_bpm(): int {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (int) get_post_meta( $track_id, '_fedistream_bpm', true );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get track musical key.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_musical_key(): string {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return get_post_meta( $track_id, '_fedistream_key', true ) ?: '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get ISRC code.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_isrc(): string {
|
||
|
|
$track_id = $this->get_linked_track_id();
|
||
|
|
|
||
|
|
if ( ! $track_id ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return get_post_meta( $track_id, '_fedistream_isrc', true ) ?: '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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 track.
|
||
|
|
if ( empty( $downloads ) && $this->get_linked_track_id() ) {
|
||
|
|
$downloads = $this->generate_track_downloads();
|
||
|
|
}
|
||
|
|
|
||
|
|
return $downloads;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate download files from linked track.
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
private function generate_track_downloads(): array {
|
||
|
|
$downloads = array();
|
||
|
|
$track = $this->get_linked_track();
|
||
|
|
$formats = $this->get_available_formats();
|
||
|
|
|
||
|
|
if ( ! $track ) {
|
||
|
|
return $downloads;
|
||
|
|
}
|
||
|
|
|
||
|
|
// For each format, create a download entry.
|
||
|
|
foreach ( $formats as $format ) {
|
||
|
|
$format_label = strtoupper( $format );
|
||
|
|
$download_id = 'track-' . $track->ID . '-' . $format;
|
||
|
|
|
||
|
|
$downloads[ $download_id ] = array(
|
||
|
|
'id' => $download_id,
|
||
|
|
'name' => sprintf(
|
||
|
|
/* translators: 1: Track name, 2: Format name */
|
||
|
|
__( '%1$s (%2$s)', 'wp-fedistream' ),
|
||
|
|
$track->post_title,
|
||
|
|
$format_label
|
||
|
|
),
|
||
|
|
'file' => add_query_arg(
|
||
|
|
array(
|
||
|
|
'fedistream_download' => 'track',
|
||
|
|
'track_id' => $track->ID,
|
||
|
|
'format' => $format,
|
||
|
|
),
|
||
|
|
home_url( '/' )
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $downloads;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if purchasable.
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function is_purchasable(): bool {
|
||
|
|
// Must have a linked track.
|
||
|
|
if ( ! $this->get_linked_track_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_track' !== $product->get_type() ) {
|
||
|
|
return $passed;
|
||
|
|
}
|
||
|
|
|
||
|
|
$pricing_type = $product->get_pricing_type();
|
||
|
|
|
||
|
|
if ( 'pwyw' === $pricing_type || 'nyp' === $pricing_type ) {
|
||
|
|
$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;
|
||
|
|
}
|
||
|
|
|
||
|
|
WC()->session->set( 'fedistream_custom_price_' . $product_id, $custom_price );
|
||
|
|
}
|
||
|
|
|
||
|
|
return $passed;
|
||
|
|
}
|
||
|
|
}
|