pull_from_eventbrite(); if ( is_wp_error( $pull_result ) ) { $errors[] = $pull_result->get_error_message(); } else { $pulled = $pull_result; } } if ( in_array( $direction, [ 'both', 'tec_to_eb' ], true ) ) { $push_result = $this->push_to_eventbrite(); if ( is_wp_error( $push_result ) ) { $errors[] = $push_result->get_error_message(); } else { $pushed = $push_result; } } return [ 'pulled' => $pulled, 'pushed' => $pushed, 'errors' => count( $errors ) ]; } // --------------------------------------------------------------------------- // Pull: Eventbrite → TEC // --------------------------------------------------------------------------- public function pull_from_eventbrite(): int|WP_Error { $org_id = (string) get_option( 'eb4tec_org_id', '' ); if ( empty( $org_id ) ) { return new WP_Error( 'eb4tec_no_org', __( 'Eventbrite organization ID not configured.', 'eb4tec' ) ); } $page = 1; $pulled = 0; do { $response = $this->api->get_organization_events( $org_id, $page ); if ( is_wp_error( $response ) ) { return $response; } $events = $response['events'] ?? []; $pagination = $response['pagination'] ?? []; foreach ( $events as $eb_event ) { $result = $this->pull_event_from_data( $eb_event ); if ( ! is_wp_error( $result ) && $result > 0 ) { $pulled++; } } $has_more = ! empty( $pagination['has_more_items'] ); $page++; } while ( $has_more ); return $pulled; } public function pull_event( string $eb_event_id ): int|WP_Error { $data = $this->api->get_event( $eb_event_id ); if ( is_wp_error( $data ) ) { return $data; } return $this->pull_event_from_data( $data ); } private function pull_event_from_data( array $eb_event ): int|WP_Error { $eb_id = $eb_event['id'] ?? ''; if ( empty( $eb_id ) ) { return new WP_Error( 'eb4tec_no_id', __( 'Eventbrite event has no ID.', 'eb4tec' ) ); } $existing_id = $this->find_tec_event( $eb_id ); // Skip events the user has marked to exclude from sync. if ( $existing_id && get_post_meta( $existing_id, '_eb4tec_exclude_sync', true ) ) { return $existing_id; } $post_args = $this->eb_event_to_tec_args( $eb_event ); if ( $existing_id ) { $post_args['ID'] = $existing_id; // Remove status from update args — don't override draft if admin changed it. unset( $post_args['post_status'] ); $post_id = wp_update_post( $post_args, true ); } else { $post_id = wp_insert_post( $post_args, true ); } if ( is_wp_error( $post_id ) ) { return $post_id; } $this->update_tec_from_eb( $post_id, $eb_event ); // Sync venue. if ( get_option( 'eb4tec_sync_venues' ) && ! empty( $eb_event['venue_id'] ) ) { $venue_post_id = $this->venue_sync->pull_venue( $eb_event['venue_id'] ); if ( ! is_wp_error( $venue_post_id ) && $venue_post_id > 0 ) { update_post_meta( $post_id, '_EventVenueID', $venue_post_id ); } } // Sync organizer. if ( get_option( 'eb4tec_sync_organizers' ) && ! empty( $eb_event['organizer_id'] ) ) { $org_post_id = $this->organizer_sync->pull_organizer( $eb_event['organizer_id'] ); if ( ! is_wp_error( $org_post_id ) && $org_post_id > 0 ) { update_post_meta( $post_id, '_EventOrganizerID', $org_post_id ); } } // Ensure WooCommerce product exists. $this->woocommerce->sync_event_product( $post_id ); // Ensure hidden ticket class exists on Eventbrite. $capacity = (int) get_post_meta( $post_id, '_eb4tec_capacity', true ); if ( $capacity > 0 ) { $this->ticket_manager->ensure_wp_ticket_class( $eb_id, $post_id, $capacity ); } return $post_id; } // --------------------------------------------------------------------------- // Push: TEC → Eventbrite // --------------------------------------------------------------------------- public function push_to_eventbrite(): int|WP_Error { // Find events that need pushing: no EB ID yet, or flagged for push on last save. $query = new WP_Query( [ 'post_type' => 'tribe_events', 'post_status' => [ 'publish', 'draft' ], 'posts_per_page' => 100, 'fields' => 'ids', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_eb4tec_event_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_eb4tec_push_on_save', 'value' => '1', ], ], ] ); $pushed = 0; foreach ( $query->posts as $post_id ) { if ( get_post_meta( $post_id, '_eb4tec_exclude_sync', true ) ) { continue; } $result = $this->push_event( $post_id ); if ( ! is_wp_error( $result ) ) { $pushed++; } // Clear the flag after processing. delete_post_meta( $post_id, '_eb4tec_push_on_save' ); } return $pushed; } public function push_event( int $post_id ): string|WP_Error { $org_id = (string) get_option( 'eb4tec_org_id', '' ); if ( empty( $org_id ) ) { return new WP_Error( 'eb4tec_no_org', __( 'Eventbrite organization ID not configured.', 'eb4tec' ) ); } $eb_event_id = (string) get_post_meta( $post_id, '_eb4tec_event_id', true ); $body = $this->tec_post_to_eb_body( $post_id ); if ( $eb_event_id ) { $result = $this->api->update_event( $eb_event_id, $body ); } else { $result = $this->api->create_event( $body ); } if ( is_wp_error( $result ) ) { return $result; } $new_eb_id = $result['id'] ?? ''; if ( empty( $new_eb_id ) ) { return new WP_Error( 'eb4tec_no_event_id', __( 'Eventbrite did not return an event ID.', 'eb4tec' ) ); } update_post_meta( $post_id, '_eb4tec_event_id', sanitize_text_field( $new_eb_id ) ); update_post_meta( $post_id, '_eb4tec_eb_url', esc_url_raw( $result['url'] ?? '' ) ); update_post_meta( $post_id, '_eb4tec_last_synced', time() ); // Auto-publish if configured. if ( get_option( 'eb4tec_auto_publish' ) && get_post_status( $post_id ) === 'publish' ) { $this->api->publish_event( $new_eb_id ); } // Push venue. if ( get_option( 'eb4tec_sync_venues' ) ) { $venue_id = (int) get_post_meta( $post_id, '_EventVenueID', true ); if ( $venue_id ) { $this->venue_sync->push_venue( $venue_id ); } } // Push organizer. if ( get_option( 'eb4tec_sync_organizers' ) ) { $org_post_id = (int) get_post_meta( $post_id, '_EventOrganizerID', true ); if ( $org_post_id ) { $this->organizer_sync->push_organizer( $org_post_id ); } } // Create WC product if not already linked. $this->woocommerce->sync_event_product( $post_id ); // Create hidden ticket class on Eventbrite. $capacity = (int) get_post_meta( $post_id, '_eb4tec_capacity', true ); if ( $capacity > 0 ) { $this->ticket_manager->ensure_wp_ticket_class( $new_eb_id, $post_id, $capacity ); } return $new_eb_id; } // --------------------------------------------------------------------------- // Meta box // --------------------------------------------------------------------------- public function add_meta_box(): void { add_meta_box( 'eb4tec_event_meta', __( 'Eventbrite Sync', 'eb4tec' ), [ $this, 'render_meta_box' ], 'tribe_events', 'side', 'default' ); } public function render_meta_box( WP_Post $post ): void { wp_nonce_field( 'eb4tec_event_meta_save', '_eb4tec_event_meta_nonce' ); $eb_id = esc_attr( get_post_meta( $post->ID, '_eb4tec_event_id', true ) ); $eb_url = esc_url( get_post_meta( $post->ID, '_eb4tec_eb_url', true ) ); $last_synced = (int) get_post_meta( $post->ID, '_eb4tec_last_synced', true ); $eb_status = esc_html( get_post_meta( $post->ID, '_eb4tec_eb_status', true ) ); $push_on_save = (bool) get_post_meta( $post->ID, '_eb4tec_push_on_save', true ); $exclude = (bool) get_post_meta( $post->ID, '_eb4tec_exclude_sync', true ); $capacity = (int) get_post_meta( $post->ID, '_eb4tec_capacity', true ); ?>


|


push_event( $post_id ); add_action( 'save_post_tribe_events', [ $this, 'on_save_post' ], 20, 3 ); } } // --------------------------------------------------------------------------- // Helper: find TEC post by EB event ID // --------------------------------------------------------------------------- public function find_tec_event( string $eb_event_id ): int { $query = new WP_Query( [ 'post_type' => 'tribe_events', 'post_status' => [ 'publish', 'draft', 'private' ], 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ [ 'key' => '_eb4tec_event_id', 'value' => $eb_event_id, ] ], ] ); return $query->posts[0] ?? 0; } // --------------------------------------------------------------------------- // Field mapping // --------------------------------------------------------------------------- private function eb_event_to_tec_args( array $eb_event ): array { $status = $eb_event['status'] ?? 'draft'; $post_status = in_array( $status, [ 'live', 'started', 'ended', 'completed' ], true ) ? 'publish' : 'draft'; return [ 'post_title' => sanitize_text_field( $eb_event['name']['text'] ?? __( 'Untitled Event', 'eb4tec' ) ), 'post_content' => wp_kses_post( $eb_event['description']['html'] ?? '' ), 'post_type' => 'tribe_events', 'post_status' => $post_status, ]; } private function update_tec_from_eb( int $post_id, array $eb_event ): void { update_post_meta( $post_id, '_eb4tec_event_id', sanitize_text_field( $eb_event['id'] ) ); update_post_meta( $post_id, '_eb4tec_eb_url', esc_url_raw( $eb_event['url'] ?? '' ) ); update_post_meta( $post_id, '_eb4tec_eb_status', sanitize_text_field( $eb_event['status'] ?? '' ) ); update_post_meta( $post_id, '_eb4tec_last_synced', time() ); // Dates — stored in local time in TEC. $start_local = sanitize_text_field( $eb_event['start']['local'] ?? '' ); $end_local = sanitize_text_field( $eb_event['end']['local'] ?? '' ); $timezone = sanitize_text_field( $eb_event['start']['timezone'] ?? '' ); if ( $start_local ) { update_post_meta( $post_id, '_EventStartDate', $start_local ); } if ( $end_local ) { update_post_meta( $post_id, '_EventEndDate', $end_local ); } if ( $timezone ) { update_post_meta( $post_id, '_EventTimezone', $timezone ); } // Capacity. $capacity = (int) ( $eb_event['capacity'] ?? 0 ); if ( $capacity > 0 ) { update_post_meta( $post_id, '_eb4tec_capacity', $capacity ); update_post_meta( $post_id, '_EventCapacity', $capacity ); } // Cost — try to get from ticket classes. $ticket_classes = $eb_event['ticket_classes'] ?? []; $min_cost = null; foreach ( $ticket_classes as $tc ) { if ( ! empty( $tc['free'] ) ) { continue; } $cost = (float) ( $tc['cost']['major_value'] ?? 0 ); if ( null === $min_cost || $cost < $min_cost ) { $min_cost = $cost; } } if ( null !== $min_cost ) { update_post_meta( $post_id, '_EventCost', (string) $min_cost ); update_post_meta( $post_id, '_EventCurrencySymbol', sanitize_text_field( $ticket_classes[0]['cost']['currency'] ?? 'GBP' ) ); } // Featured image. if ( ! has_post_thumbnail( $post_id ) && ! empty( $eb_event['logo']['url'] ) ) { $this->sideload_image( $eb_event['logo']['url'], $post_id, get_the_title( $post_id ) ); } } private function tec_post_to_eb_body( int $post_id ): array { $post = get_post( $post_id ); $start = (string) get_post_meta( $post_id, '_EventStartDate', true ); $end = (string) get_post_meta( $post_id, '_EventEndDate', true ); $timezone = (string) get_post_meta( $post_id, '_EventTimezone', true ) ?: wp_timezone_string(); $currency = (string) get_post_meta( $post_id, '_EventCurrencySymbol', true ) ?: 'GBP'; // Convert local dates to UTC for the EB API. $tz_obj = new DateTimeZone( $timezone ); $start_dt = new DateTime( $start, $tz_obj ); $end_dt = new DateTime( $end, $tz_obj ); $start_dt->setTimezone( new DateTimeZone( 'UTC' ) ); $end_dt->setTimezone( new DateTimeZone( 'UTC' ) ); return [ 'event' => [ 'name' => [ 'html' => $post->post_title ], 'description' => [ 'html' => $post->post_content ], 'start' => [ 'timezone' => $timezone, 'utc' => $start_dt->format( 'Y-m-d\TH:i:s\Z' ), ], 'end' => [ 'timezone' => $timezone, 'utc' => $end_dt->format( 'Y-m-d\TH:i:s\Z' ), ], 'currency' => $currency, 'online_event' => false, 'listed' => true, 'shareable' => true, ], ]; } private function sideload_image( string $url, int $post_id, string $title ): void { if ( ! function_exists( 'media_sideload_image' ) ) { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; } $attachment_id = media_sideload_image( $url, $post_id, $title, 'id' ); if ( ! is_wp_error( $attachment_id ) ) { set_post_thumbnail( $post_id, $attachment_id ); } } }