Bidirectional sync between Eventbrite and The Events Calendar, with WooCommerce ticket purchasing that bypasses Eventbrite's processing fees by registering buyers as free attendees via API. Includes venue/ organizer sync, QR code ticket generation, attendee management with CSV export, scheduled sync via WP-Cron, and real-time Eventbrite webhooks.
196 lines
5 KiB
PHP
196 lines
5 KiB
PHP
<?php
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
final class EB4TEC_Frontend {
|
|
|
|
public function __construct( private readonly EB4TEC_Ticket_Manager $ticket_manager ) {
|
|
add_action( 'tribe_events_single_event_after_the_meta', [ $this, 'after_event_meta' ] );
|
|
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] );
|
|
}
|
|
|
|
public function after_event_meta(): void {
|
|
$post_id = get_the_ID();
|
|
if ( ! $post_id ) {
|
|
return;
|
|
}
|
|
|
|
$eb_event_id = (string) get_post_meta( $post_id, '_eb4tec_event_id', true );
|
|
$product_id = (int) get_post_meta( $post_id, '_eb4tec_wp_product_id', true );
|
|
$eb_url = (string) get_post_meta( $post_id, '_eb4tec_eb_url', true );
|
|
|
|
// Don't render if there's no Eventbrite link and no WC product.
|
|
if ( ! $eb_event_id && ! $product_id ) {
|
|
return;
|
|
}
|
|
|
|
// Determine if event has ended.
|
|
$end_date = (string) get_post_meta( $post_id, '_EventEndDate', true );
|
|
$timezone = (string) get_post_meta( $post_id, '_EventTimezone', true ) ?: wp_timezone_string();
|
|
$has_ended = false;
|
|
if ( $end_date ) {
|
|
try {
|
|
$tz_obj = new DateTimeZone( $timezone );
|
|
$end_dt = new DateTime( $end_date, $tz_obj );
|
|
$now = new DateTime( 'now', $tz_obj );
|
|
$has_ended = $now > $end_dt;
|
|
} catch ( \Exception ) {
|
|
// Keep $has_ended = false.
|
|
}
|
|
}
|
|
|
|
echo '<div class="eb4tec-ticket-section">';
|
|
|
|
if ( $product_id && ! $has_ended ) {
|
|
$this->render_capacity_bar( $post_id );
|
|
$this->render_buy_button( $post_id, $product_id );
|
|
} elseif ( $has_ended ) {
|
|
echo '<p class="eb4tec-event-ended">' . esc_html__( 'This event has ended.', 'eb4tec' ) . '</p>';
|
|
}
|
|
|
|
if ( $eb_url ) {
|
|
echo '<p class="eb4tec-eb-link"><a href="' . esc_url( $eb_url ) . '" target="_blank" rel="noopener">' .
|
|
esc_html__( 'View on Eventbrite', 'eb4tec' ) . ' ↗</a></p>';
|
|
}
|
|
|
|
echo '</div>';
|
|
}
|
|
|
|
private function render_capacity_bar( int $post_id ): void {
|
|
$total = (int) get_post_meta( $post_id, '_eb4tec_capacity', true );
|
|
$available = $this->ticket_manager->get_available_capacity( $post_id );
|
|
|
|
if ( $total <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
$sold = max( 0, $total - $available );
|
|
$percent = (int) round( ( $sold / $total ) * 100 );
|
|
|
|
echo '<div class="eb4tec-capacity-wrap">';
|
|
printf(
|
|
'<div class="eb4tec-capacity-bar" role="progressbar" aria-valuenow="%d" aria-valuemin="0" aria-valuemax="%d"><div class="eb4tec-capacity-bar__fill" style="width:%d%%"></div></div>',
|
|
$sold,
|
|
$total,
|
|
$percent
|
|
);
|
|
|
|
if ( $available > 0 ) {
|
|
echo '<p class="eb4tec-capacity-label">' . esc_html( sprintf(
|
|
/* translators: 1: available spots, 2: total */
|
|
_n( '%1$d of %2$d spot remaining', '%1$d of %2$d spots remaining', $available, 'eb4tec' ),
|
|
$available,
|
|
$total
|
|
) ) . '</p>';
|
|
} else {
|
|
echo '<p class="eb4tec-capacity-label eb4tec-capacity-label--sold-out">' . esc_html__( 'Sold out', 'eb4tec' ) . '</p>';
|
|
}
|
|
|
|
echo '</div>';
|
|
}
|
|
|
|
private function render_buy_button( int $post_id, int $product_id ): void {
|
|
$available = $this->ticket_manager->get_available_capacity( $post_id );
|
|
$button_html = '';
|
|
|
|
if ( $available > 0 ) {
|
|
$product_url = get_permalink( $product_id );
|
|
$button_html = sprintf(
|
|
'<a href="%s" class="button eb4tec-buy-button">%s</a>',
|
|
esc_url( $product_url ),
|
|
esc_html__( 'Buy Tickets', 'eb4tec' )
|
|
);
|
|
} else {
|
|
$button_html = '<span class="eb4tec-sold-out-badge">' . esc_html__( 'Sold Out', 'eb4tec' ) . '</span>';
|
|
}
|
|
|
|
echo apply_filters( 'eb4tec_ticket_button_html', $button_html, $post_id );
|
|
}
|
|
|
|
public function enqueue_styles(): void {
|
|
if ( ! is_singular( 'tribe_events' ) ) {
|
|
return;
|
|
}
|
|
|
|
wp_register_style( 'eb4tec-frontend', false );
|
|
wp_enqueue_style( 'eb4tec-frontend' );
|
|
wp_add_inline_style( 'eb4tec-frontend', $this->get_inline_css() );
|
|
}
|
|
|
|
private function get_inline_css(): string {
|
|
return '
|
|
.eb4tec-ticket-section {
|
|
margin: 20px 0;
|
|
padding: 16px 0;
|
|
border-top: 1px solid #e5e5e5;
|
|
}
|
|
.eb4tec-capacity-wrap {
|
|
margin-bottom: 12px;
|
|
}
|
|
.eb4tec-capacity-bar {
|
|
background: #e5e5e5;
|
|
border-radius: 4px;
|
|
height: 8px;
|
|
overflow: hidden;
|
|
max-width: 300px;
|
|
}
|
|
.eb4tec-capacity-bar__fill {
|
|
background: #2271b1;
|
|
height: 100%;
|
|
transition: width 0.3s ease;
|
|
}
|
|
.eb4tec-capacity-label {
|
|
font-size: 0.875em;
|
|
color: #555;
|
|
margin: 4px 0 0;
|
|
}
|
|
.eb4tec-capacity-label--sold-out {
|
|
color: #d63638;
|
|
font-weight: 600;
|
|
}
|
|
.eb4tec-buy-button {
|
|
display: inline-block;
|
|
padding: 10px 20px;
|
|
background: #2271b1;
|
|
color: #fff !important;
|
|
text-decoration: none;
|
|
border-radius: 4px;
|
|
font-weight: 600;
|
|
margin-right: 8px;
|
|
}
|
|
.eb4tec-buy-button:hover {
|
|
background: #135e96;
|
|
}
|
|
.eb4tec-sold-out-badge {
|
|
display: inline-block;
|
|
padding: 8px 16px;
|
|
background: #d63638;
|
|
color: #fff;
|
|
border-radius: 4px;
|
|
font-weight: 600;
|
|
}
|
|
.eb4tec-event-ended {
|
|
color: #555;
|
|
font-style: italic;
|
|
}
|
|
.eb4tec-eb-link {
|
|
margin-top: 8px;
|
|
font-size: 0.875em;
|
|
}
|
|
.eb4tec-ticket-classes {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0 0 12px;
|
|
}
|
|
.eb4tec-ticket-class {
|
|
padding: 6px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
.eb4tec-ticket-sold-out {
|
|
color: #d63638;
|
|
font-size: 0.85em;
|
|
}
|
|
';
|
|
}
|
|
}
|