Current File : /home/digitaw/www/wp-content/plugins/event-tickets/src/Tickets/Seating/Orders/Attendee.php
<?php
/**
 * Manage seat selection data for attendee.
 *
 * @since 5.16.0
 *
 * @package TEC/Tickets/Seating/Orders
 */

namespace TEC\Tickets\Seating\Orders;

use Tribe__Main as Common;
use Tribe__Tickets__Attendee_Repository as Attendee_Repository;
use Tribe__Utils__Array as Arr;
use Tribe__Template as Template;
use TEC\Tickets\Commerce\Attendee as Commerce_Attendee;
use TEC\Tickets\Seating\Service\Reservations;
use TEC\Tickets\Seating\Meta;
use WP_Query;
use WP_Post;
use Tribe__Tickets__Ticket_Object as Ticket_Object;
use Tribe__Tickets__RSVP as RSVP_Provider;

/**
 * Class Attendee
 *
 * @since 5.16.0
 *
 * @package TEC/Tickets/Seating/Orders
 */
class Attendee {
	/**
	 * Adds the attendee seat column to the attendee list.
	 *
	 * @since 5.16.0
	 *
	 * @param array<string,string> $columns The columns for the Attendees table.
	 * @param int                  $event_id The event ID.
	 *
	 * @return array<string,string> The filtered columns for the Attendees table.
	 */
	public function add_attendee_seat_column( array $columns, int $event_id ): array {
		$event_layout_id = get_post_meta( $event_id, Meta::META_KEY_LAYOUT_ID, true );

		if ( $event_id && empty( $event_layout_id ) ) {
			return $columns;
		}

		return Common::array_insert_after_key(
			'ticket',
			$columns,
			[ 'seat' => esc_html_x( 'Seat', 'attendee table seat column header', 'event-tickets' ) ]
		);
	}

	/**
	 * Renders the seat column for the attendee list.
	 *
	 * @since 5.16.0
	 *
	 * @param string              $value  Row item value.
	 * @param array<string,mixed> $item   Row item data.
	 * @param string              $column Column name.
	 *
	 * @return string The rendered value.
	 */
	public function render_seat_column( $value, $item, $column ) {
		if ( 'seat' !== $column ) {
			return $value;
		}

		if ( ! isset( $item['attendee_id'] ) ) {
			return '-';
		}

		$seat_label = get_post_meta( $item['attendee_id'], Meta::META_KEY_ATTENDEE_SEAT_LABEL, true );

		if ( ! empty( $seat_label ) ) {
			return $seat_label;
		}

		$ticket_id   = Arr::get( $item, 'product_id' );
		$slr_enabled = get_post_meta( $ticket_id, Meta::META_KEY_ENABLED, true );

		return $slr_enabled ? __( 'Unassigned', 'event-tickets' ) : '';
	}

	/**
	 * Include seats in sortable columns list.
	 *
	 * @param array<string,string> $columns The list of columns.
	 *
	 * @return array<string,string> The filtered columns.
	 */
	public function filter_sortable_columns( array $columns ): array {
		$columns['seat'] = 'seat';

		return $columns;
	}

	/**
	 * Handle seat column sorting.
	 *
	 * @since 5.16.0
	 *
	 * @param array<string,mixed> $query_args An array of the query arguments the query will be initialized with.
	 * @param WP_Query            $query The query object, the query arguments have not been parsed yet.
	 * @param Attendee_Repository $repository This repository instance.
	 *
	 * @return array<string,mixed> The query args.
	 */
	public function handle_sorting_seat_column( $query_args, $query, $repository ): array {
		$order_by = Arr::get( $query_args, 'orderby' );

		if ( 'seat' !== $order_by ) {
			return $query_args;
		}

		$order = Arr::get( $query_args, 'order', 'asc' );

		global $wpdb;

		$meta_alias     = 'seat_label';
		$meta_key       = Meta::META_KEY_ATTENDEE_SEAT_LABEL;
		$postmeta_table = "orderby_{$meta_alias}_meta";
		$filter_id      = 'order_by_seat_label';

		$repository->filter_query->join(
			"
			LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
				ON (
					{$postmeta_table}.post_id = {$wpdb->posts}.ID
					AND {$postmeta_table}.meta_key = '{$meta_key}'
				)
			",
			$filter_id,
			true
		);

		$repository->filter_query->orderby( [ $meta_alias => $order ], $filter_id, true, false );
		$repository->filter_query->fields( "{$postmeta_table}.meta_value AS {$meta_alias}", $filter_id, true );

		return $query_args;
	}

	/**
	 * Remove move row action from attendee list for seated tickets.
	 *
	 * @since 5.16.0
	 *
	 * @param array<string,mixed> $actions The list of actions.
	 * @param array<string,mixed> $item    The item being acted upon.
	 *
	 * @return array<string,mixed> The filtered actions.
	 */
	public function remove_move_row_action( $actions, $item ) {
		if ( ! isset( $actions['move-attendee'] ) ) {
			return $actions;
		}

		$ticket_id   = Arr::get( $item, 'product_id' );
		$slr_enabled = get_post_meta( $ticket_id, Meta::META_KEY_ENABLED, true );

		if ( $slr_enabled ) {
			unset( $actions['move-attendee'] );
		}

		return $actions;
	}

	/**
	 * Handle attendee delete.
	 *
	 * @param int          $attendee_id The Attendee ID.
	 * @param Reservations $reservations The Reservations object.
	 *
	 * @return int The attendee ID.
	 */
	public function handle_attendee_delete( int $attendee_id, Reservations $reservations ): int {
		$event_id = get_post_meta( $attendee_id, Commerce_Attendee::$event_relation_meta_key, true );
		if ( ! $event_id ) {
			return $attendee_id;
		}

		$event = get_post( $event_id );
		if ( ! $event instanceof WP_Post ) {
			// The event has been deleted, so we don't need to cancel the reservation.
			return $attendee_id;
		}

		$reservation_id = get_post_meta( $attendee_id, Meta::META_KEY_RESERVATION_ID, true );

		if ( ! $reservation_id ) {
			return $attendee_id;
		}

		$cancelled = $reservations->cancel( $event_id, [ $reservation_id ] );

		// Bail attendee deletion by returning 0, if the reservation was not cancelled.
		if ( ! $cancelled ) {
			return 0;
		}

		return $attendee_id;
	}

	/**
	 * Include seating data into the attendee object.
	 *
	 * @since 5.16.0
	 *
	 * @param WP_Post $post The attendee post object, decorated with a set of custom properties.
	 *
	 * @return WP_Post
	 */
	public function include_seating_data( WP_Post $post ): WP_Post {
		$seating_ticket = get_post_meta( $post->product_id, Meta::META_KEY_ENABLED, true );

		if ( ! $seating_ticket ) {
			return $post;
		}

		$seat_label = get_post_meta( $post->ID, Meta::META_KEY_ATTENDEE_SEAT_LABEL, true );

		if ( $seat_label ) {
			$post->seat_label = $seat_label;
		}

		$seat_type_id = get_post_meta( $post->ID, Meta::META_KEY_SEAT_TYPE, true );

		if ( $seat_type_id ) {
			$post->seat_type_id = $seat_type_id;
		}

		$layout_id = get_post_meta( $post->ID, Meta::META_KEY_LAYOUT_ID, true );

		if ( $layout_id ) {
			$post->layout_id = $layout_id;
		}

		return $post;
	}

	/**
	 * Include seat info in email.
	 *
	 * @since 5.16.0
	 *
	 * @param Template $template The email template instance.
	 *
	 * @return void
	 */
	public function include_seat_info_in_email( Template $template ): void {
		$context    = $template->get_local_values();
		$seat_label = Arr::get( $context, [ 'ticket', 'seat_label' ], false );

		if ( ! $seat_label ) {
			return;
		}

		echo wp_kses(
			sprintf(
				'<div class="tec-tickets__email-table-content-ticket-seat-label">%s</div>
				<div class="tec-tickets__email-table-content-ticket-seat-label-separator">|</div>',
				esc_html( $seat_label )
			),
			[
				'div' => [ 'class' => [] ],
			]
		);
	}

	/**
	 * Inject seating label with ticket name on My Tickets page.
	 *
	 * @since 5.16.0
	 *
	 * @param string   $html The HTML content of ticket information.
	 * @param Template $template The email template instance.
	 *
	 * @return string The HTML content of ticket information.
	 */
	public function inject_seat_info_in_my_tickets( string $html, Template $template ): string {
		$context    = $template->get_local_values();
		$seat_label = Arr::get( $context, [ 'attendee', 'seat_label' ], false );

		if ( ! $seat_label ) {
			$ticket_id   = Arr::get( $context, [ 'attendee', 'product_id' ] );
			$slr_enabled = get_post_meta( $ticket_id, Meta::META_KEY_ENABLED, true );
			$seat_label  = $slr_enabled ? __( 'No assigned seat', 'event-tickets' ) : '';
		}

		if ( empty( $seat_label ) ) {
			return $html;
		}

		$head_div = '<div class="tribe-ticket-information">';
		$label    = $head_div . sprintf( '<span class="tec-tickets__ticket-information__seat-label">%s</span>', esc_html( $seat_label ) );

		return str_replace( $head_div, $label, $html );
	}

	/**
	 * Formats a  set of Attendees to the format expected by the Seats Report AJAX request.
	 *
	 * @since 5.16.0
	 *
	 * @param array<array<string,mixed>> $attendees The Attendees to format.
	 *
	 * @return array<array<string,mixed>> The formatted Attendees.
	 */
	public function format_many( array $attendees ): array {
		$unknown_attendee_name = __( 'Unknown', 'event-tickets' );

		// Filter out attendees that are from the RSVP provider.
		$attendees = array_filter(
			$attendees,
			static function ( array $attendee ): bool {
				return RSVP_Provider::class !== $attendee['provider'];
			}
		);

		$associated_attendees = array_reduce(
			$attendees,
			static function ( array $carry, array $attendee ): array {
				if ( ! isset( $attendee['order_id'] ) ) {
					// Can't work out the associated attendees.
					return $carry;
				}

				$order_id = $attendee['order_id'];
				if ( isset( $carry[ $order_id ] ) ) {
					// Already processed this Order ID.
					return $carry;
				}

				$provider           = tribe_tickets_get_ticket_provider( $attendee['product_id'] );
				$carry[ $order_id ] = count( $provider->get_attendees_by_order_id( $order_id ) );

				return $carry;
			},
			[]
		);

		$formatted_attendees = [];
		foreach ( $attendees as $attendee ) {
			$id      = (int) $attendee['attendee_id'];
			$user_id = (int) ( $attendee['user_id'] ?? 0 );
			if ( $user_id > 0 ) {
				$user                       = get_user_by( 'id', $user_id );
				$attendee['purchaser_name'] = $user ? $user->display_name : $unknown_attendee_name;
			} else {
				$attendee['purchaser_name'] ??= $unknown_attendee_name;
			}

			$name = trim( $attendee['holder_name'] ?? '' );
			if ( ! $name ) {
				$name = $attendee['purchaser_name'];
			}
			$order_id = $attendee['order_id'] ?? false;

			$formatted_attendees[] = [
				'id'            => $id,
				'name'          => $name,
				'purchaser'     => [
					'id'                  => $order_id,
					'name'                => $attendee['purchaser_name'],
					'associatedAttendees' => $order_id ? $associated_attendees[ $order_id ] : 0,
				],
				'ticketId'      => $attendee['product_id'],
				'ticketName'    => $attendee['ticket_name'],
				'seatTypeId'    => get_post_meta( $id, Meta::META_KEY_SEAT_TYPE, true ),
				'seatLabel'     => get_post_meta( $id, Meta::META_KEY_ATTENDEE_SEAT_LABEL, true ),
				'reservationId' => get_post_meta( $id, Meta::META_KEY_RESERVATION_ID, true ),
			];
		}

		return $formatted_attendees;
	}

	/**
	 * Inject seating label with ticket name on Order success page.
	 *
	 * @since 5.16.0
	 *
	 * @param string   $html The HTML content of ticket information.
	 * @param Template $template The email template instance.
	 *
	 * @return string The HTML content of ticket information.
	 */
	public function inject_seat_info_in_order_success_page( string $html, Template $template ): string {
		$context    = $template->get_local_values();
		$seat_label = Arr::get( $context, [ 'attendee', 'seat_label' ], false );

		if ( ! $seat_label ) {
			$ticket_id   = Arr::get( $context, [ 'attendee', 'product_id' ] );
			$slr_enabled = get_post_meta( $ticket_id, Meta::META_KEY_ENABLED, true );
			$seat_label  = $slr_enabled ? __( 'Unassigned', 'event-tickets' ) : '';
		}

		if ( empty( $seat_label ) ) {
			return $html;
		}

		$head_div = '<div class="tec-tickets__attendees-list-item-attendee-details-ticket">';
		$label    = $head_div . sprintf( '<span class="tec-tickets__ticket-information__seat-label">%s</span>', esc_html( $seat_label ) );

		return str_replace( $head_div, $label, $html );
	}

	/**
	 * Calculates the total number of available seats for a given set of tickets considering their seat type.
	 *
	 * @since 5.16.0
	 *
	 * @param array<string,mixed>  $render_context The render context for the attendee page.
	 * @param int                  $post_id The post ID.
	 * @param array<Ticket_Object> $tickets The tickets for the event.
	 *
	 * @return array<string,mixed> The updated render context.
	 */
	public function adjust_attendee_page_render_context_for_seating( array $render_context, int $post_id, array $tickets ): array {
		$available_by_seat_type = [];

		foreach ( $tickets as $ticket ) {
			$ticket_seat_type = get_post_meta( $ticket->ID, Meta::META_KEY_SEAT_TYPE, true );
			if ( isset( $available_by_seat_type[ $ticket_seat_type ] ) && $ticket->available() <= $available_by_seat_type[ $ticket_seat_type ] ) {
				continue;
			}

			$available_by_seat_type[ $ticket_seat_type ] = $ticket->available();
		}

		$render_context['ticket_totals']['available'] = array_sum( $available_by_seat_type );

		return $render_context;
	}
}