update_event_product( $existing_product_id, $post_id ); return $existing_product_id; } return $this->create_event_product( $post_id ); } private function create_event_product( int $post_id ): int|WP_Error { $event = get_post( $post_id ); $title = $event ? $event->post_title . ' — ' . __( 'Tickets', 'eb4tec' ) : __( 'Event Tickets', 'eb4tec' ); $cost = (float) get_post_meta( $post_id, '_EventCost', true ); $capacity = (int) get_post_meta( $post_id, '_eb4tec_capacity', true ); $product = new WC_Product_Simple(); $product->set_name( $title ); $product->set_status( 'publish' ); $product->set_regular_price( (string) $cost ); $product->set_price( (string) $cost ); $product->set_virtual( true ); $product->set_sold_individually( false ); if ( $capacity > 0 ) { $product->set_manage_stock( true ); $product->set_stock_quantity( $capacity ); $product->set_stock_status( 'instock' ); $product->set_backorders( 'no' ); } // Assign ticket category if configured. $cat_id = (int) get_option( 'eb4tec_woo_category_id', 0 ); if ( $cat_id ) { $product->set_category_ids( [ $cat_id ] ); } $product_id = $product->save(); if ( ! $product_id ) { return new WP_Error( 'eb4tec_wc_product', __( 'Failed to create WooCommerce product.', 'eb4tec' ) ); } update_post_meta( $product_id, '_eb4tec_event_post_id', $post_id ); update_post_meta( $product_id, '_eb4tec_is_event_ticket', 'yes' ); update_post_meta( $post_id, '_eb4tec_wp_product_id', $product_id ); // Transfer featured image. $thumbnail_id = get_post_thumbnail_id( $post_id ); if ( $thumbnail_id ) { set_post_thumbnail( $product_id, $thumbnail_id ); } return $product_id; } private function update_event_product( int $product_id, int $post_id ): void { $product = wc_get_product( $product_id ); if ( ! $product ) { return; } $event = get_post( $post_id ); $title = $event ? $event->post_title . ' — ' . __( 'Tickets', 'eb4tec' ) : $product->get_name(); $cost = (float) get_post_meta( $post_id, '_EventCost', true ); $capacity = (int) get_post_meta( $post_id, '_eb4tec_capacity', true ); $product->set_name( $title ); $product->set_regular_price( (string) $cost ); $product->set_price( (string) $cost ); if ( $capacity > 0 ) { $product->set_manage_stock( true ); $product->set_stock_quantity( $capacity ); } $product->save(); } // --------------------------------------------------------------------------- // Order completed: register attendees in Eventbrite // --------------------------------------------------------------------------- public function on_order_completed( int $order_id ): void { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } foreach ( $order->get_items() as $item_id => $item ) { $product_id = $item->get_product_id(); if ( get_post_meta( $product_id, '_eb4tec_is_event_ticket', true ) !== 'yes' ) { continue; } $post_id = (int) get_post_meta( $product_id, '_eb4tec_event_post_id', true ); $eb_event_id = (string) get_post_meta( $post_id, '_eb4tec_event_id', true ); if ( ! $post_id || ! $eb_event_id ) { continue; } $capacity = (int) get_post_meta( $post_id, '_eb4tec_capacity', true ); $class_id_or_error = $this->ticket_manager->ensure_wp_ticket_class( $eb_event_id, $post_id, $capacity ); if ( is_wp_error( $class_id_or_error ) ) { $order->add_order_note( sprintf( __( 'EB4TEC: Failed to get ticket class for event %d: %s', 'eb4tec' ), $post_id, $class_id_or_error->get_error_message() ) ); continue; } $qty = (int) $item->get_quantity(); $attendee_ids = []; $qr_codes = []; for ( $i = 0; $i < $qty; $i++ ) { $attendee_result = $this->register_attendee( $order_id, $order, $eb_event_id, $class_id_or_error, $i ); if ( is_wp_error( $attendee_result ) ) { $order->add_order_note( sprintf( __( 'EB4TEC: Failed to register attendee %d for event %d: %s', 'eb4tec' ), $i + 1, $post_id, $attendee_result->get_error_message() ) ); continue; } $attendee_ids[] = $attendee_result; // Generate QR code for this attendee. $qr_data = $this->qr->get_attendee_qr_data( $attendee_result, (string) $order_id, $eb_event_id ); $qr_url = $this->qr->generate( $qr_data ); if ( ! is_wp_error( $qr_url ) ) { $qr_codes[] = [ 'attendee_id' => $attendee_result, 'qr_url' => $qr_url, ]; } } // Persist to order meta (append to any already registered for other items). $existing_ids = json_decode( get_post_meta( $order_id, '_eb4tec_attendee_ids', true ) ?: '[]', true ); $existing_codes = json_decode( get_post_meta( $order_id, '_eb4tec_qr_codes', true ) ?: '[]', true ); update_post_meta( $order_id, '_eb4tec_attendee_ids', wp_json_encode( array_merge( $existing_ids, $attendee_ids ) ) ); update_post_meta( $order_id, '_eb4tec_qr_codes', wp_json_encode( array_merge( $existing_codes, $qr_codes ) ) ); update_post_meta( $order_id, '_eb4tec_event_post_id', $post_id ); if ( ! empty( $attendee_ids ) ) { $order->add_order_note( sprintf( __( 'EB4TEC: Registered %d attendee(s) in Eventbrite (IDs: %s).', 'eb4tec' ), count( $attendee_ids ), implode( ', ', $attendee_ids ) ) ); } } } public function on_order_refunded( int $order_id ): void { $attendee_ids = json_decode( get_post_meta( $order_id, '_eb4tec_attendee_ids', true ) ?: '[]', true ); if ( empty( $attendee_ids ) ) { return; } // Eventbrite's API does not support cancelling free-class attendees via API. // Log a note for manual follow-up. $order = wc_get_order( $order_id ); if ( $order ) { $order->add_order_note( sprintf( /* translators: list of Eventbrite attendee IDs */ __( 'EB4TEC: Order refunded/cancelled. Please manually cancel these Eventbrite attendees: %s', 'eb4tec' ), implode( ', ', array_map( 'esc_html', $attendee_ids ) ) ) ); } } private function register_attendee( int $order_id, WC_Order $order, string $eb_event_id, string $ticket_class_id, int $index ): string|WP_Error { $first_name = sanitize_text_field( $order->get_billing_first_name() ); $last_name = sanitize_text_field( $order->get_billing_last_name() ); $email = sanitize_email( $order->get_billing_email() ); // Append ticket number if ordering multiple. $name = trim( "$first_name $last_name" ); if ( $index > 0 ) { $name .= ' (' . ( $index + 1 ) . ')'; } $result = $this->api->create_attendee( $eb_event_id, [ 'attendee' => [ 'ticket_class_id' => $ticket_class_id, 'profile' => [ 'name' => $name, 'email' => $email, ], 'answers' => [], ], ] ); if ( is_wp_error( $result ) ) { return $result; } $attendee_id = $result['id'] ?? ''; return $attendee_id ?: new WP_Error( 'eb4tec_no_attendee_id', __( 'Eventbrite did not return an attendee ID.', 'eb4tec' ) ); } // --------------------------------------------------------------------------- // Meta boxes // --------------------------------------------------------------------------- public function add_meta_boxes(): void { // Order meta box. add_meta_box( 'eb4tec_order_meta', __( 'Eventbrite Attendees', 'eb4tec' ), [ $this, 'render_order_meta_box' ], wc_get_page_screen_id( 'shop-order' ), 'side', 'default' ); // Product meta box. add_meta_box( 'eb4tec_product_meta', __( 'Linked Eventbrite Event', 'eb4tec' ), [ $this, 'render_product_meta_box' ], 'product', 'side', 'default' ); } public function render_order_meta_box( WP_Post $post ): void { $attendee_ids = json_decode( get_post_meta( $post->ID, '_eb4tec_attendee_ids', true ) ?: '[]', true ); $post_id = (int) get_post_meta( $post->ID, '_eb4tec_event_post_id', true ); $eb_event_id = $post_id ? (string) get_post_meta( $post_id, '_eb4tec_event_id', true ) : ''; if ( empty( $attendee_ids ) ) { echo '

' . esc_html__( 'No Eventbrite attendees registered for this order.', 'eb4tec' ) . '

'; return; } echo ''; } public function render_product_meta_box( WP_Post $post ): void { wp_nonce_field( 'eb4tec_product_meta_save', '_eb4tec_product_meta_nonce' ); $event_post_id = (int) get_post_meta( $post->ID, '_eb4tec_event_post_id', true ); $is_ticket = get_post_meta( $post->ID, '_eb4tec_is_event_ticket', true ) === 'yes'; if ( $event_post_id ) { $event_title = get_the_title( $event_post_id ); $edit_url = get_edit_post_link( $event_post_id ); echo '

'; printf( esc_html__( 'Ticket for: %s', 'eb4tec' ) . ' %s', esc_html( $event_title ), esc_url( $edit_url ), esc_html__( 'Edit event', 'eb4tec' ) ); echo '

'; } else { echo '

' . esc_html__( 'Not linked to a TEC event.', 'eb4tec' ) . '

'; } echo '

'; } public function save_product_meta_box( int $post_id ): void { if ( get_post_type( $post_id ) !== 'product' ) { return; } if ( ! wp_verify_nonce( $_POST['_eb4tec_product_meta_nonce'] ?? '', 'eb4tec_product_meta_save' ) ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } $is_ticket = ( $_POST['eb4tec_is_event_ticket'] ?? '' ) === 'yes' ? 'yes' : 'no'; update_post_meta( $post_id, '_eb4tec_is_event_ticket', $is_ticket ); } // --------------------------------------------------------------------------- // QR display: thank-you page and email // --------------------------------------------------------------------------- public function show_qr_on_thankyou( int $order_id ): void { if ( ! get_option( 'eb4tec_qr_on_order_page' ) ) { return; } $qr_codes = json_decode( get_post_meta( $order_id, '_eb4tec_qr_codes', true ) ?: '[]', true ); if ( empty( $qr_codes ) ) { return; } echo '
'; echo '

' . esc_html__( 'Your Event Tickets', 'eb4tec' ) . '

'; echo '

' . esc_html__( 'Present these QR codes at the event for check-in.', 'eb4tec' ) . '

'; foreach ( $qr_codes as $index => $entry ) { $attendee_id = $entry['attendee_id'] ?? ''; $qr_url = $entry['qr_url'] ?? ''; if ( ! $qr_url ) { continue; } echo '
'; echo $this->qr->render_qr_html( $qr_url, $attendee_id ); echo '' . sprintf( esc_html__( 'Ticket %d', 'eb4tec' ), $index + 1 ) . ''; echo '
'; } echo '
'; } public function inject_qr_into_email( WC_Order $order, bool $sent_to_admin, bool $plain_text, WC_Email $email ): void { if ( $sent_to_admin || $plain_text ) { return; } if ( ! get_option( 'eb4tec_qr_in_email' ) ) { return; } $qr_codes = json_decode( get_post_meta( $order->get_id(), '_eb4tec_qr_codes', true ) ?: '[]', true ); if ( empty( $qr_codes ) ) { return; } echo '

' . esc_html__( 'Your Event Tickets', 'eb4tec' ) . '

'; echo '

' . esc_html__( 'Present these QR codes at the event for check-in.', 'eb4tec' ) . '

'; foreach ( $qr_codes as $index => $entry ) { $qr_url = $entry['qr_url'] ?? ''; $attendee_id = $entry['attendee_id'] ?? ''; if ( ! $qr_url ) { continue; } echo '
'; echo '' . sprintf( esc_html__( 'Ticket %d', 'eb4tec' ), $index + 1 ) . '
'; echo $this->qr->render_qr_html( $qr_url, $attendee_id ); echo '
'; } } }