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.
242 lines
7.9 KiB
PHP
242 lines
7.9 KiB
PHP
<?php
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
final class EB4TEC_API_Client {
|
|
|
|
const BASE_URL = 'https://www.eventbriteapi.com/v3/';
|
|
const CACHE_TTL = 300;
|
|
|
|
private string $token;
|
|
private int $rate_limit_remaining = 1000;
|
|
|
|
public function __construct() {
|
|
$this->token = (string) get_option( 'eb4tec_api_token', '' );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Credentials / user
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function get_user_me(): array|WP_Error {
|
|
return $this->get( 'users/me/', cacheable: false );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Events
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function get_organization_events( string $org_id, int $page = 1 ): array|WP_Error {
|
|
return $this->get( "organizations/{$org_id}/events/", [
|
|
'status' => 'draft,live,started,ended,completed',
|
|
'expand' => 'venue,organizer,ticket_classes',
|
|
'page' => $page,
|
|
'page_size' => 50,
|
|
'order_by' => 'start_asc',
|
|
] );
|
|
}
|
|
|
|
public function get_event( string $eb_event_id ): array|WP_Error {
|
|
return $this->get( "events/{$eb_event_id}/", [ 'expand' => 'venue,organizer,ticket_classes' ] );
|
|
}
|
|
|
|
public function create_event( array $data ): array|WP_Error {
|
|
$org_id = (string) get_option( 'eb4tec_org_id', '' );
|
|
return $this->post( "organizations/{$org_id}/events/", $data );
|
|
}
|
|
|
|
public function update_event( string $eb_event_id, array $data ): array|WP_Error {
|
|
return $this->post( "events/{$eb_event_id}/", $data );
|
|
}
|
|
|
|
public function publish_event( string $eb_event_id ): array|WP_Error {
|
|
return $this->post( "events/{$eb_event_id}/publish/", [] );
|
|
}
|
|
|
|
public function unpublish_event( string $eb_event_id ): array|WP_Error {
|
|
return $this->post( "events/{$eb_event_id}/unpublish/", [] );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Ticket classes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function get_ticket_classes( string $eb_event_id ): array|WP_Error {
|
|
return $this->get( "events/{$eb_event_id}/ticket_classes/" );
|
|
}
|
|
|
|
public function create_ticket_class( string $eb_event_id, array $data ): array|WP_Error {
|
|
return $this->post( "events/{$eb_event_id}/ticket_classes/", $data );
|
|
}
|
|
|
|
public function update_ticket_class( string $eb_event_id, string $class_id, array $data ): array|WP_Error {
|
|
return $this->post( "events/{$eb_event_id}/ticket_classes/{$class_id}/", $data );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Attendees
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function create_attendee( string $eb_event_id, array $data ): array|WP_Error {
|
|
return $this->post( "events/{$eb_event_id}/attendees/", $data );
|
|
}
|
|
|
|
public function get_attendees( string $eb_event_id, int $page = 1 ): array|WP_Error {
|
|
return $this->get( "events/{$eb_event_id}/attendees/", [
|
|
'page' => $page,
|
|
'page_size' => 50,
|
|
'expand' => 'ticket_class',
|
|
], cacheable: false );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Venues
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function get_venue( string $venue_id ): array|WP_Error {
|
|
return $this->get( "venues/{$venue_id}/" );
|
|
}
|
|
|
|
public function create_venue( string $org_id, array $data ): array|WP_Error {
|
|
return $this->post( "organizations/{$org_id}/venues/", $data );
|
|
}
|
|
|
|
public function update_venue( string $venue_id, array $data ): array|WP_Error {
|
|
return $this->post( "venues/{$venue_id}/", $data );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Organizers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function get_organizer( string $organizer_id ): array|WP_Error {
|
|
return $this->get( "organizers/{$organizer_id}/" );
|
|
}
|
|
|
|
public function create_organizer( string $org_id, array $data ): array|WP_Error {
|
|
return $this->post( "organizations/{$org_id}/organizers/", $data );
|
|
}
|
|
|
|
public function update_organizer( string $organizer_id, array $data ): array|WP_Error {
|
|
return $this->post( "organizers/{$organizer_id}/", $data );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Webhooks
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function create_webhook( array $data ): array|WP_Error {
|
|
$org_id = (string) get_option( 'eb4tec_org_id', '' );
|
|
return $this->post( "organizations/{$org_id}/webhooks/", $data );
|
|
}
|
|
|
|
public function list_webhooks(): array|WP_Error {
|
|
$org_id = (string) get_option( 'eb4tec_org_id', '' );
|
|
return $this->get( "organizations/{$org_id}/webhooks/", cacheable: false );
|
|
}
|
|
|
|
public function delete_webhook( string $webhook_id ): array|WP_Error {
|
|
return $this->delete_request( "webhooks/{$webhook_id}/" );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cache helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public function bust_cache( string $endpoint ): void {
|
|
delete_transient( $this->cache_key( $endpoint, [] ) );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// HTTP internals
|
|
// ---------------------------------------------------------------------------
|
|
|
|
private function get( string $endpoint, array $query = [], bool $cacheable = true ): array|WP_Error {
|
|
if ( $cacheable ) {
|
|
$key = $this->cache_key( $endpoint, $query );
|
|
$cached = get_transient( $key );
|
|
if ( false !== $cached ) {
|
|
return $cached;
|
|
}
|
|
}
|
|
|
|
$url = self::BASE_URL . $endpoint;
|
|
$result = $this->request( 'GET', $url, [ 'body' => $query ] );
|
|
|
|
if ( ! is_wp_error( $result ) && $cacheable ) {
|
|
set_transient( $key, $result, self::CACHE_TTL );
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function post( string $endpoint, array $body ): array|WP_Error {
|
|
$url = self::BASE_URL . $endpoint;
|
|
return $this->request( 'POST', $url, [
|
|
'body' => wp_json_encode( $body ),
|
|
'content-type' => 'application/json',
|
|
] );
|
|
}
|
|
|
|
private function delete_request( string $endpoint ): array|WP_Error {
|
|
$url = self::BASE_URL . $endpoint;
|
|
return $this->request( 'DELETE', $url, [] );
|
|
}
|
|
|
|
private function request( string $method, string $url, array $args ): array|WP_Error {
|
|
if ( empty( $this->token ) ) {
|
|
return new WP_Error( 'eb4tec_no_token', __( 'Eventbrite API token is not configured.', 'eb4tec' ) );
|
|
}
|
|
|
|
$defaults = [
|
|
'method' => $method,
|
|
'timeout' => 15,
|
|
'headers' => [
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
'Content-Type' => 'application/json',
|
|
'Accept' => 'application/json',
|
|
],
|
|
];
|
|
|
|
$args = array_merge( $defaults, $args );
|
|
$response = wp_remote_request( $url, $args );
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
return $response;
|
|
}
|
|
|
|
$status = wp_remote_retrieve_response_code( $response );
|
|
$body = wp_remote_retrieve_body( $response );
|
|
|
|
// Track rate limit.
|
|
$remaining = wp_remote_retrieve_header( $response, 'x-ratelimit-remaining' );
|
|
if ( '' !== $remaining ) {
|
|
$this->rate_limit_remaining = (int) $remaining;
|
|
if ( $this->rate_limit_remaining <= 10 ) {
|
|
set_transient( 'eb4tec_rate_limit_warning', true, 60 );
|
|
}
|
|
}
|
|
|
|
$decoded = json_decode( $body, true );
|
|
|
|
if ( $status < 200 || $status >= 300 ) {
|
|
$message = $decoded['error_description'] ?? $decoded['error'] ?? __( 'Unknown Eventbrite API error.', 'eb4tec' );
|
|
return new WP_Error(
|
|
'eb4tec_api_error',
|
|
$message,
|
|
[ 'status' => $status, 'response' => $decoded ]
|
|
);
|
|
}
|
|
|
|
return $decoded ?? [];
|
|
}
|
|
|
|
private function cache_key( string $endpoint, array $query ): string {
|
|
return 'eb4tec_' . md5( $endpoint . serialize( $query ) );
|
|
}
|
|
|
|
public function get_rate_limit_remaining(): int {
|
|
return $this->rate_limit_remaining;
|
|
}
|
|
}
|