You've already forked wp-fedistream
Fixed timing issue where WooCommerce integration hooks were registered too late during plugins_loaded. The constructor now calls check_woocommerce() directly instead of hooking it at priority 5 (which had already passed). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
747 lines
23 KiB
PHP
747 lines
23 KiB
PHP
<?php
|
|
/**
|
|
* WooCommerce Integration.
|
|
*
|
|
* @package WP_FediStream
|
|
*/
|
|
|
|
namespace WP_FediStream\WooCommerce;
|
|
|
|
// Prevent direct file access.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Main WooCommerce integration class.
|
|
*/
|
|
class Integration {
|
|
|
|
/**
|
|
* Whether WooCommerce is active.
|
|
*
|
|
* @var bool
|
|
*/
|
|
private bool $woocommerce_active = false;
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct() {
|
|
// Check WooCommerce immediately since we're instantiated during plugins_loaded.
|
|
$this->check_woocommerce();
|
|
|
|
// If plugins_loaded hasn't fully completed, hook init at priority 20.
|
|
// Otherwise, run init directly.
|
|
if ( ! did_action( 'plugins_loaded' ) || doing_action( 'plugins_loaded' ) ) {
|
|
add_action( 'plugins_loaded', array( $this, 'init' ), 20 );
|
|
} else {
|
|
$this->init();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if WooCommerce is active.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function check_woocommerce(): void {
|
|
$this->woocommerce_active = class_exists( 'WooCommerce' );
|
|
}
|
|
|
|
/**
|
|
* Initialize WooCommerce integration.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init(): void {
|
|
if ( ! $this->woocommerce_active ) {
|
|
return;
|
|
}
|
|
|
|
// Register custom product types.
|
|
add_filter( 'product_type_selector', array( $this, 'add_product_types' ) );
|
|
add_filter( 'woocommerce_product_class', array( $this, 'product_class' ), 10, 2 );
|
|
|
|
// Initialize product type classes.
|
|
add_action( 'init', array( $this, 'register_product_types' ), 5 );
|
|
|
|
// Add product data tabs.
|
|
add_filter( 'woocommerce_product_data_tabs', array( $this, 'add_product_data_tabs' ) );
|
|
add_action( 'woocommerce_product_data_panels', array( $this, 'add_product_data_panels' ) );
|
|
|
|
// Save product meta.
|
|
add_action( 'woocommerce_process_product_meta', array( $this, 'save_product_meta' ) );
|
|
|
|
// Frontend hooks.
|
|
add_action( 'woocommerce_single_product_summary', array( $this, 'display_track_preview' ), 25 );
|
|
|
|
// Purchase access hooks.
|
|
add_action( 'woocommerce_order_status_completed', array( $this, 'grant_access_on_purchase' ) );
|
|
|
|
// Download hooks.
|
|
add_filter( 'woocommerce_downloadable_file_allowed_mime_types', array( $this, 'allowed_audio_mimes' ) );
|
|
|
|
// Admin columns.
|
|
add_filter( 'manage_edit-product_columns', array( $this, 'add_product_columns' ) );
|
|
add_action( 'manage_product_posts_custom_column', array( $this, 'render_product_columns' ), 10, 2 );
|
|
}
|
|
|
|
/**
|
|
* Check if WooCommerce is active.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_active(): bool {
|
|
return $this->woocommerce_active;
|
|
}
|
|
|
|
/**
|
|
* Register custom product types.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_product_types(): void {
|
|
// Product types are registered via class loading.
|
|
}
|
|
|
|
/**
|
|
* Add custom product types to the selector.
|
|
*
|
|
* @param array $types Product types.
|
|
* @return array Modified product types.
|
|
*/
|
|
public function add_product_types( array $types ): array {
|
|
$types['fedistream_album'] = __( 'FediStream Album', 'wp-fedistream' );
|
|
$types['fedistream_track'] = __( 'FediStream Track', 'wp-fedistream' );
|
|
|
|
return $types;
|
|
}
|
|
|
|
/**
|
|
* Get product class for custom types.
|
|
*
|
|
* @param string $classname Product class name.
|
|
* @param string $product_type Product type.
|
|
* @return string Modified class name.
|
|
*/
|
|
public function product_class( string $classname, string $product_type ): string {
|
|
if ( 'fedistream_album' === $product_type ) {
|
|
return AlbumProduct::class;
|
|
}
|
|
|
|
if ( 'fedistream_track' === $product_type ) {
|
|
return TrackProduct::class;
|
|
}
|
|
|
|
return $classname;
|
|
}
|
|
|
|
/**
|
|
* Add product data tabs.
|
|
*
|
|
* @param array $tabs Product data tabs.
|
|
* @return array Modified tabs.
|
|
*/
|
|
public function add_product_data_tabs( array $tabs ): array {
|
|
$tabs['fedistream'] = array(
|
|
'label' => __( 'FediStream', 'wp-fedistream' ),
|
|
'target' => 'fedistream_product_data',
|
|
'class' => array( 'show_if_fedistream_album', 'show_if_fedistream_track' ),
|
|
'priority' => 21,
|
|
);
|
|
|
|
$tabs['fedistream_formats'] = array(
|
|
'label' => __( 'Audio Formats', 'wp-fedistream' ),
|
|
'target' => 'fedistream_formats_data',
|
|
'class' => array( 'show_if_fedistream_album', 'show_if_fedistream_track' ),
|
|
'priority' => 22,
|
|
);
|
|
|
|
return $tabs;
|
|
}
|
|
|
|
/**
|
|
* Add product data panels.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function add_product_data_panels(): void {
|
|
global $post;
|
|
|
|
$product_id = $post->ID;
|
|
|
|
// Get linked content.
|
|
$linked_album = get_post_meta( $product_id, '_fedistream_linked_album', true );
|
|
$linked_track = get_post_meta( $product_id, '_fedistream_linked_track', true );
|
|
|
|
// Get pricing options.
|
|
$pricing_type = get_post_meta( $product_id, '_fedistream_pricing_type', true ) ?: 'fixed';
|
|
$min_price = get_post_meta( $product_id, '_fedistream_min_price', true );
|
|
$suggested_price = get_post_meta( $product_id, '_fedistream_suggested_price', true );
|
|
|
|
// Get format options.
|
|
$available_formats = get_post_meta( $product_id, '_fedistream_available_formats', true ) ?: array( 'mp3' );
|
|
$include_streaming = get_post_meta( $product_id, '_fedistream_include_streaming', true );
|
|
|
|
// Get albums for dropdown.
|
|
$albums = get_posts(
|
|
array(
|
|
'post_type' => 'fedistream_album',
|
|
'post_status' => 'publish',
|
|
'posts_per_page' => -1,
|
|
'orderby' => 'title',
|
|
'order' => 'ASC',
|
|
)
|
|
);
|
|
|
|
// Get tracks for dropdown.
|
|
$tracks = get_posts(
|
|
array(
|
|
'post_type' => 'fedistream_track',
|
|
'post_status' => 'publish',
|
|
'posts_per_page' => -1,
|
|
'orderby' => 'title',
|
|
'order' => 'ASC',
|
|
)
|
|
);
|
|
|
|
?>
|
|
<div id="fedistream_product_data" class="panel woocommerce_options_panel">
|
|
<div class="options_group show_if_fedistream_album">
|
|
<p class="form-field">
|
|
<label for="_fedistream_linked_album"><?php esc_html_e( 'Linked Album', 'wp-fedistream' ); ?></label>
|
|
<select id="_fedistream_linked_album" name="_fedistream_linked_album" class="wc-enhanced-select" style="width: 50%;">
|
|
<option value=""><?php esc_html_e( 'Select an album...', 'wp-fedistream' ); ?></option>
|
|
<?php foreach ( $albums as $album ) : ?>
|
|
<option value="<?php echo esc_attr( $album->ID ); ?>" <?php selected( $linked_album, $album->ID ); ?>>
|
|
<?php echo esc_html( $album->post_title ); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<?php echo wc_help_tip( __( 'Select the FediStream album this product represents.', 'wp-fedistream' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="options_group show_if_fedistream_track">
|
|
<p class="form-field">
|
|
<label for="_fedistream_linked_track"><?php esc_html_e( 'Linked Track', 'wp-fedistream' ); ?></label>
|
|
<select id="_fedistream_linked_track" name="_fedistream_linked_track" class="wc-enhanced-select" style="width: 50%;">
|
|
<option value=""><?php esc_html_e( 'Select a track...', 'wp-fedistream' ); ?></option>
|
|
<?php foreach ( $tracks as $track ) : ?>
|
|
<option value="<?php echo esc_attr( $track->ID ); ?>" <?php selected( $linked_track, $track->ID ); ?>>
|
|
<?php echo esc_html( $track->post_title ); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<?php echo wc_help_tip( __( 'Select the FediStream track this product represents.', 'wp-fedistream' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="options_group">
|
|
<p class="form-field">
|
|
<label for="_fedistream_pricing_type"><?php esc_html_e( 'Pricing Type', 'wp-fedistream' ); ?></label>
|
|
<select id="_fedistream_pricing_type" name="_fedistream_pricing_type" class="wc-enhanced-select" style="width: 50%;">
|
|
<option value="fixed" <?php selected( $pricing_type, 'fixed' ); ?>><?php esc_html_e( 'Fixed Price', 'wp-fedistream' ); ?></option>
|
|
<option value="pwyw" <?php selected( $pricing_type, 'pwyw' ); ?>><?php esc_html_e( 'Pay What You Want', 'wp-fedistream' ); ?></option>
|
|
<option value="nyp" <?php selected( $pricing_type, 'nyp' ); ?>><?php esc_html_e( 'Name Your Price (Free+)', 'wp-fedistream' ); ?></option>
|
|
</select>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="options_group fedistream-pwyw-options">
|
|
<?php
|
|
woocommerce_wp_text_input(
|
|
array(
|
|
'id' => '_fedistream_min_price',
|
|
'label' => __( 'Minimum Price', 'wp-fedistream' ) . ' (' . get_woocommerce_currency_symbol() . ')',
|
|
'desc_tip' => true,
|
|
'description' => __( 'Minimum price for Pay What You Want. Leave empty for no minimum.', 'wp-fedistream' ),
|
|
'type' => 'text',
|
|
'data_type' => 'price',
|
|
'value' => $min_price,
|
|
)
|
|
);
|
|
|
|
woocommerce_wp_text_input(
|
|
array(
|
|
'id' => '_fedistream_suggested_price',
|
|
'label' => __( 'Suggested Price', 'wp-fedistream' ) . ' (' . get_woocommerce_currency_symbol() . ')',
|
|
'desc_tip' => true,
|
|
'description' => __( 'Suggested price shown to customers.', 'wp-fedistream' ),
|
|
'type' => 'text',
|
|
'data_type' => 'price',
|
|
'value' => $suggested_price,
|
|
)
|
|
);
|
|
?>
|
|
</div>
|
|
|
|
<div class="options_group">
|
|
<?php
|
|
woocommerce_wp_checkbox(
|
|
array(
|
|
'id' => '_fedistream_include_streaming',
|
|
'label' => __( 'Include Streaming', 'wp-fedistream' ),
|
|
'description' => __( 'Purchase unlocks full-quality streaming access.', 'wp-fedistream' ),
|
|
'value' => $include_streaming,
|
|
)
|
|
);
|
|
?>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="fedistream_formats_data" class="panel woocommerce_options_panel">
|
|
<div class="options_group">
|
|
<p class="form-field">
|
|
<label><?php esc_html_e( 'Available Formats', 'wp-fedistream' ); ?></label>
|
|
<span class="fedistream-format-checkboxes">
|
|
<?php
|
|
$formats = array(
|
|
'mp3' => 'MP3 (320kbps)',
|
|
'flac' => 'FLAC (Lossless)',
|
|
'wav' => 'WAV (Uncompressed)',
|
|
'aac' => 'AAC (256kbps)',
|
|
'ogg' => 'OGG Vorbis',
|
|
);
|
|
|
|
foreach ( $formats as $format => $label ) :
|
|
$checked = is_array( $available_formats ) && in_array( $format, $available_formats, true );
|
|
?>
|
|
<label style="display: block; margin-bottom: 5px;">
|
|
<input type="checkbox" name="_fedistream_available_formats[]" value="<?php echo esc_attr( $format ); ?>" <?php checked( $checked ); ?>>
|
|
<?php echo esc_html( $label ); ?>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</span>
|
|
</p>
|
|
<p class="description" style="margin-left: 150px;">
|
|
<?php esc_html_e( 'Select which audio formats customers can download after purchase.', 'wp-fedistream' ); ?>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
jQuery(function($) {
|
|
function togglePricingOptions() {
|
|
var type = $('#_fedistream_pricing_type').val();
|
|
if (type === 'pwyw' || type === 'nyp') {
|
|
$('.fedistream-pwyw-options').show();
|
|
} else {
|
|
$('.fedistream-pwyw-options').hide();
|
|
}
|
|
}
|
|
|
|
$('#_fedistream_pricing_type').on('change', togglePricingOptions);
|
|
togglePricingOptions();
|
|
|
|
// Show/hide tabs based on product type.
|
|
$('input#_virtual, input#_downloadable').on('change', function() {
|
|
var type = $('select#product-type').val();
|
|
if (type === 'fedistream_album' || type === 'fedistream_track') {
|
|
$('input#_virtual').prop('checked', true);
|
|
$('input#_downloadable').prop('checked', true);
|
|
}
|
|
});
|
|
|
|
$('select#product-type').on('change', function() {
|
|
var type = $(this).val();
|
|
if (type === 'fedistream_album' || type === 'fedistream_track') {
|
|
$('input#_virtual').prop('checked', true).trigger('change');
|
|
$('input#_downloadable').prop('checked', true).trigger('change');
|
|
}
|
|
}).trigger('change');
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Save product meta.
|
|
*
|
|
* @param int $product_id Product ID.
|
|
* @return void
|
|
*/
|
|
public function save_product_meta( int $product_id ): void {
|
|
// Linked content.
|
|
if ( isset( $_POST['_fedistream_linked_album'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_linked_album', absint( $_POST['_fedistream_linked_album'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
if ( isset( $_POST['_fedistream_linked_track'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_linked_track', absint( $_POST['_fedistream_linked_track'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
// Pricing options.
|
|
if ( isset( $_POST['_fedistream_pricing_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_pricing_type', sanitize_text_field( wp_unslash( $_POST['_fedistream_pricing_type'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
if ( isset( $_POST['_fedistream_min_price'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_min_price', wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['_fedistream_min_price'] ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
if ( isset( $_POST['_fedistream_suggested_price'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_suggested_price', wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['_fedistream_suggested_price'] ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
// Streaming access.
|
|
$include_streaming = isset( $_POST['_fedistream_include_streaming'] ) ? 'yes' : 'no'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_include_streaming', $include_streaming );
|
|
|
|
// Available formats.
|
|
if ( isset( $_POST['_fedistream_available_formats'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
$formats = array_map( 'sanitize_text_field', wp_unslash( $_POST['_fedistream_available_formats'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
update_post_meta( $product_id, '_fedistream_available_formats', $formats );
|
|
} else {
|
|
update_post_meta( $product_id, '_fedistream_available_formats', array() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display track preview on product page.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function display_track_preview(): void {
|
|
global $product;
|
|
|
|
if ( ! $product ) {
|
|
return;
|
|
}
|
|
|
|
$product_type = $product->get_type();
|
|
|
|
if ( 'fedistream_track' === $product_type ) {
|
|
$track_id = get_post_meta( $product->get_id(), '_fedistream_linked_track', true );
|
|
if ( $track_id ) {
|
|
$this->render_track_preview( $track_id );
|
|
}
|
|
} elseif ( 'fedistream_album' === $product_type ) {
|
|
$album_id = get_post_meta( $product->get_id(), '_fedistream_linked_album', true );
|
|
if ( $album_id ) {
|
|
$this->render_album_preview( $album_id );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render track preview player.
|
|
*
|
|
* @param int $track_id Track ID.
|
|
* @return void
|
|
*/
|
|
private function render_track_preview( int $track_id ): void {
|
|
$audio_id = get_post_meta( $track_id, '_fedistream_audio_file', true );
|
|
$audio_url = $audio_id ? wp_get_attachment_url( $audio_id ) : '';
|
|
|
|
if ( ! $audio_url ) {
|
|
return;
|
|
}
|
|
|
|
$duration = get_post_meta( $track_id, '_fedistream_duration', true );
|
|
|
|
?>
|
|
<div class="fedistream-product-preview">
|
|
<h4><?php esc_html_e( 'Preview', 'wp-fedistream' ); ?></h4>
|
|
<div class="fedistream-mini-player" data-track-id="<?php echo esc_attr( $track_id ); ?>">
|
|
<button class="fedistream-preview-play" type="button" aria-label="<?php esc_attr_e( 'Play preview', 'wp-fedistream' ); ?>">
|
|
<span class="dashicons dashicons-controls-play"></span>
|
|
</button>
|
|
<div class="fedistream-preview-progress">
|
|
<div class="fedistream-preview-progress-bar"></div>
|
|
</div>
|
|
<?php if ( $duration ) : ?>
|
|
<span class="fedistream-preview-duration"><?php echo esc_html( gmdate( 'i:s', $duration ) ); ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render album preview tracklist.
|
|
*
|
|
* @param int $album_id Album ID.
|
|
* @return void
|
|
*/
|
|
private function render_album_preview( int $album_id ): void {
|
|
$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,
|
|
),
|
|
),
|
|
)
|
|
);
|
|
|
|
if ( empty( $tracks ) ) {
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<div class="fedistream-product-tracklist">
|
|
<h4><?php esc_html_e( 'Tracklist', 'wp-fedistream' ); ?></h4>
|
|
<ol class="fedistream-album-tracks">
|
|
<?php foreach ( $tracks as $track ) : ?>
|
|
<?php
|
|
$duration = get_post_meta( $track->ID, '_fedistream_duration', true );
|
|
?>
|
|
<li class="fedistream-album-track" data-track-id="<?php echo esc_attr( $track->ID ); ?>">
|
|
<span class="fedistream-track-title"><?php echo esc_html( $track->post_title ); ?></span>
|
|
<?php if ( $duration ) : ?>
|
|
<span class="fedistream-track-duration"><?php echo esc_html( gmdate( 'i:s', $duration ) ); ?></span>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ol>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Grant streaming/download access on purchase completion.
|
|
*
|
|
* @param int $order_id Order ID.
|
|
* @return void
|
|
*/
|
|
public function grant_access_on_purchase( int $order_id ): void {
|
|
$order = wc_get_order( $order_id );
|
|
|
|
if ( ! $order ) {
|
|
return;
|
|
}
|
|
|
|
$customer_id = $order->get_customer_id();
|
|
if ( ! $customer_id ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $order->get_items() as $item ) {
|
|
$product_id = $item->get_product_id();
|
|
$product_type = \WC_Product_Factory::get_product_type( $product_id );
|
|
|
|
if ( 'fedistream_album' === $product_type ) {
|
|
$album_id = get_post_meta( $product_id, '_fedistream_linked_album', true );
|
|
if ( $album_id ) {
|
|
$this->grant_album_access( $customer_id, $album_id, $order_id );
|
|
}
|
|
} elseif ( 'fedistream_track' === $product_type ) {
|
|
$track_id = get_post_meta( $product_id, '_fedistream_linked_track', true );
|
|
if ( $track_id ) {
|
|
$this->grant_track_access( $customer_id, $track_id, $order_id );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grant album access to a customer.
|
|
*
|
|
* @param int $customer_id Customer ID.
|
|
* @param int $album_id Album ID.
|
|
* @param int $order_id Order ID.
|
|
* @return void
|
|
*/
|
|
private function grant_album_access( int $customer_id, int $album_id, int $order_id ): void {
|
|
global $wpdb;
|
|
|
|
$table = $wpdb->prefix . 'fedistream_purchases';
|
|
|
|
// Check if access already exists.
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$exists = $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
"SELECT id FROM {$table} WHERE user_id = %d AND content_type = 'album' AND content_id = %d",
|
|
$customer_id,
|
|
$album_id
|
|
)
|
|
);
|
|
|
|
if ( $exists ) {
|
|
return;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
|
$wpdb->insert(
|
|
$table,
|
|
array(
|
|
'user_id' => $customer_id,
|
|
'content_type' => 'album',
|
|
'content_id' => $album_id,
|
|
'order_id' => $order_id,
|
|
'purchased_at' => current_time( 'mysql' ),
|
|
),
|
|
array( '%d', '%s', '%d', '%d', '%s' )
|
|
);
|
|
|
|
// Also grant access to all tracks in the album.
|
|
$tracks = get_posts(
|
|
array(
|
|
'post_type' => 'fedistream_track',
|
|
'post_status' => 'publish',
|
|
'posts_per_page' => -1,
|
|
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
|
array(
|
|
'key' => '_fedistream_album_id',
|
|
'value' => $album_id,
|
|
),
|
|
),
|
|
)
|
|
);
|
|
|
|
foreach ( $tracks as $track ) {
|
|
$this->grant_track_access( $customer_id, $track->ID, $order_id );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grant track access to a customer.
|
|
*
|
|
* @param int $customer_id Customer ID.
|
|
* @param int $track_id Track ID.
|
|
* @param int $order_id Order ID.
|
|
* @return void
|
|
*/
|
|
private function grant_track_access( int $customer_id, int $track_id, int $order_id ): void {
|
|
global $wpdb;
|
|
|
|
$table = $wpdb->prefix . 'fedistream_purchases';
|
|
|
|
// Check if access already exists.
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$exists = $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
"SELECT id FROM {$table} WHERE user_id = %d AND content_type = 'track' AND content_id = %d",
|
|
$customer_id,
|
|
$track_id
|
|
)
|
|
);
|
|
|
|
if ( $exists ) {
|
|
return;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
|
$wpdb->insert(
|
|
$table,
|
|
array(
|
|
'user_id' => $customer_id,
|
|
'content_type' => 'track',
|
|
'content_id' => $track_id,
|
|
'order_id' => $order_id,
|
|
'purchased_at' => current_time( 'mysql' ),
|
|
),
|
|
array( '%d', '%s', '%d', '%d', '%s' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if user has purchased content.
|
|
*
|
|
* @param int $user_id User ID.
|
|
* @param string $content_type Content type (album or track).
|
|
* @param int $content_id Content ID.
|
|
* @return bool
|
|
*/
|
|
public static function user_has_purchased( int $user_id, string $content_type, int $content_id ): bool {
|
|
global $wpdb;
|
|
|
|
$table = $wpdb->prefix . 'fedistream_purchases';
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* Add allowed audio MIME types.
|
|
*
|
|
* @param array $types Allowed MIME types.
|
|
* @return array Modified MIME types.
|
|
*/
|
|
public function allowed_audio_mimes( array $types ): array {
|
|
$types['flac'] = 'audio/flac';
|
|
$types['wav'] = 'audio/wav';
|
|
$types['ogg'] = 'audio/ogg';
|
|
$types['aac'] = 'audio/aac';
|
|
|
|
return $types;
|
|
}
|
|
|
|
/**
|
|
* Add product columns.
|
|
*
|
|
* @param array $columns Columns.
|
|
* @return array Modified columns.
|
|
*/
|
|
public function add_product_columns( array $columns ): array {
|
|
$new_columns = array();
|
|
|
|
foreach ( $columns as $key => $value ) {
|
|
$new_columns[ $key ] = $value;
|
|
|
|
if ( 'product_type' === $key ) {
|
|
$new_columns['fedistream_linked'] = __( 'FediStream', 'wp-fedistream' );
|
|
}
|
|
}
|
|
|
|
return $new_columns;
|
|
}
|
|
|
|
/**
|
|
* Render product columns.
|
|
*
|
|
* @param string $column Column name.
|
|
* @param int $post_id Post ID.
|
|
* @return void
|
|
*/
|
|
public function render_product_columns( string $column, int $post_id ): void {
|
|
if ( 'fedistream_linked' !== $column ) {
|
|
return;
|
|
}
|
|
|
|
$product_type = \WC_Product_Factory::get_product_type( $post_id );
|
|
|
|
if ( 'fedistream_album' === $product_type ) {
|
|
$album_id = get_post_meta( $post_id, '_fedistream_linked_album', true );
|
|
if ( $album_id ) {
|
|
$album = get_post( $album_id );
|
|
if ( $album ) {
|
|
echo '<a href="' . esc_url( get_edit_post_link( $album_id ) ) . '">' . esc_html( $album->post_title ) . '</a>';
|
|
}
|
|
} else {
|
|
echo '<span class="na">–</span>';
|
|
}
|
|
} elseif ( 'fedistream_track' === $product_type ) {
|
|
$track_id = get_post_meta( $post_id, '_fedistream_linked_track', true );
|
|
if ( $track_id ) {
|
|
$track = get_post( $track_id );
|
|
if ( $track ) {
|
|
echo '<a href="' . esc_url( get_edit_post_link( $track_id ) ) . '">' . esc_html( $track->post_title ) . '</a>';
|
|
}
|
|
} else {
|
|
echo '<span class="na">–</span>';
|
|
}
|
|
} else {
|
|
echo '<span class="na">–</span>';
|
|
}
|
|
}
|
|
}
|