eventbrite-for-the-events-c.../includes/class-eb4tec-api-client.php
Laurence Horrocks-Barlow f3bc795d9a Initial release of Eventbrite for The Events Calendar (v1.0.0)
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.
2026-05-17 08:48:04 +01:00

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;
}
}