# give/4.16.8.1/src/EventTickets/Repositories/EventTicketRepository.php

GiveWP – Donation Plugin and Fundraising Platform, version 4.16.8.1. 369 lines.

- Page: https://pluginprobe.com/plugins/give/4.16.8.1/code/src/EventTickets/Repositories/EventTicketRepository.php
- Raw: https://pluginprobe.com/plugins/give/4.16.8.1/raw/src/EventTickets/Repositories/EventTicketRepository.php
- Modified: 2026-09-10T20:19:52+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/give/4.16.8.1/code/src/EventTickets/Repositories/EventTicketRepository.php#L10-L20`.

```php
<?php

namespace Give\EventTickets\Repositories;

use Give\BetaFeatures\Facades\FeatureFlag;
use Give\Donations\Models\Donation;
use Give\Donations\ValueObjects\DonationMetaKeys;
use Give\EventTickets\Models\EventTicket;
use Give\Framework\Database\DB;
use Give\Framework\Exceptions\Primitives\Exception;
use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
use Give\Framework\Exceptions\Primitives\RuntimeException;
use Give\Framework\Models\ModelQueryBuilder;
use Give\Framework\Support\Facades\DateTime\Temporal;
use Give\Framework\Support\ValueObjects\Money;
use Give\Helpers\Hooks;
use Give\Helpers\Table;
use Give\Log\Log;

/**
 * @since 3.6.0
 */
class EventTicketRepository
{

    /**
     * @since 3.20.0 Add "amount" column to the properties array
     * @since 3.6.0
     *
     * @var string[]
     */
    private $requiredProperties = [
        'eventId',
        'ticketTypeId',
        'donationId',
        'amount',
    ];

    /**
     * @since 3.6.0
     */
    public function getById(int $id): ?EventTicket
    {
        if (!$this->isFeatureActive()) {
            return null;
        }

        return $this->prepareQuery()
            ->where('id', $id)
            ->get();
    }

    /**
     * @since 3.6.0
     */
    public function queryById(int $id): ModelQueryBuilder
    {
        return $this->prepareQuery()
            ->where('id', $id);
    }

    /**
     * @since 4.16.8.1 Enforce the ticket type's remaining capacity atomically with the insert (locking the ticket type row and re-counting under that lock), closing a race that let concurrent purchases jointly oversell it.
     * @since 3.20.0 Add "amount" column to the insert statement
     * @since 3.6.0
     *
     * @throws Exception|InvalidArgumentException|RuntimeException
     */
    public function insert(EventTicket $eventTicket)
    {
        if (!$this->isFeatureActive()) {
            throw new Exception('Event tickets feature is not active');
        }

        $this->validate($eventTicket);

        Hooks::doAction('givewp_events_event_ticket_creating', $eventTicket);

        $createdDateTime = Temporal::withoutMicroseconds($eventTicket->createdAt ?: Temporal::getCurrentDateTime());

        DB::query('START TRANSACTION');

        if (!$this->hasRemainingCapacity($eventTicket)) {
            DB::query('ROLLBACK');

            throw new RuntimeException('Ticket type has no remaining capacity');
        }

        try {
            DB::table('give_event_tickets')
                ->insert([
                    'event_id' => $eventTicket->eventId,
                    'ticket_type_id' => $eventTicket->ticketTypeId,
                    'donation_id' => $eventTicket->donationId,
                    'amount' => $eventTicket->amount->formatToMinorAmount(),
                    'created_at' => $createdDateTime->format('Y-m-d H:i:s'),
                    'updated_at' => $createdDateTime->format('Y-m-d H:i:s'),
                ]);

            $eventTicketId = DB::last_insert_id();
        } catch (Exception $exception) {
            DB::query('ROLLBACK');

            Log::error('Failed creating an event ticket', compact('eventTicket'));

            throw new $exception('Failed creating an event ticket');
        }

        $eventTicket->id = $eventTicketId;
        $eventTicket->createdAt = $createdDateTime;
        $eventTicket->updatedAt = $createdDateTime;

        DB::query('COMMIT');

        Hooks::doAction('givewp_events_event_ticket_created', $eventTicket);
    }

    /**
     * Locks the ticket type's row and checks its remaining capacity against a fresh ticket count taken
     * under that lock. Must only be called after DB::query('START TRANSACTION') — the lock it takes is
     * what makes the check-then-insert in insert() atomic across concurrent requests for the same
     * ticket type; called on its own, outside a transaction, it would just be another stale read.
     *
     * @since 4.16.8.1
     */
    private function hasRemainingCapacity(EventTicket $eventTicket): bool
    {
        global $wpdb;

        $capacity = DB::get_var(
            DB::prepare(
                "SELECT capacity FROM {$wpdb->give_event_ticket_types} WHERE id = %d FOR UPDATE",
                $eventTicket->ticketTypeId
            )
        );

        if ($capacity === null) {
            return false;
        }

        $ticketCount = (int)DB::get_var(
            DB::prepare(
                "SELECT COUNT(*) FROM {$wpdb->give_event_tickets} WHERE ticket_type_id = %d",
                $eventTicket->ticketTypeId
            )
        );

        return $ticketCount < (int)$capacity;
    }

    /**
     * @since 3.20.0 Add "amount" column to the update statement
     * @since 3.6.0
     *
     * @throws Exception|InvalidArgumentException
     */
    public function update(EventTicket $eventTicket)
    {
        if (!$this->isFeatureActive()) {
            throw new Exception('Event tickets feature is not active');
        }

        $this->validate($eventTicket);

        Hooks::doAction('givewp_events_event_ticket_updating', $eventTicket);

        $updatedDateTime = Temporal::withoutMicroseconds(Temporal::getCurrentDateTime());

        DB::query('START TRANSACTION');

        try {

            DB::table('give_event_tickets')
                ->where('id', $eventTicket->id)
                ->update([
                    'event_id' => $eventTicket->eventId,
                    'ticket_type_id' => $eventTicket->ticketTypeId,
                    'donation_id' => $eventTicket->donationId,
                    'amount' => $eventTicket->amount->formatToMinorAmount(),
                    'updated_at' => $updatedDateTime->format('Y-m-d H:i:s'),
                ]);
        } catch (Exception $exception) {
            DB::query('ROLLBACK');

            Log::error('Failed updating an event ticket', compact('eventTicket'));

            throw new $exception('Failed updating an event ticket');
        }

        $eventTicket->updatedAt = $updatedDateTime;

        DB::query('COMMIT');

        Hooks::doAction('givewp_events_event_ticket_updated', $eventTicket);
    }

    /**
     * @since 3.6.0
     *
     * @throws Exception
     */
    public function delete(EventTicket $eventTicket): bool
    {
        if (!$this->isFeatureActive()) {
            throw new Exception('Event tickets feature is not active');
        }

        DB::query('START TRANSACTION');

        Hooks::doAction('givewp_events_event_ticket_deleting', $eventTicket);

        try {
            DB::table('give_event_tickets')
                ->where('id', $eventTicket->id)
                ->delete();
        } catch (Exception $exception) {
            DB::query('ROLLBACK');

            Log::error('Failed deleting an event ticket', compact('eventTicket'));

            throw new $exception('Failed deleting an event ticket');
        }

        DB::query('COMMIT');

        Hooks::doAction('givewp_events_event_ticket_deleted', $eventTicket);

        return true;
    }

    /**
     * Check if the event tickets feature is active and table exists
     *
     * @since 4.6.0
     * @return bool
     */
    private function isFeatureActive(): bool
    {
        return FeatureFlag::eventTickets() && $this->tableExists('give_event_tickets');
    }

    /**
     * @since 3.6.0
     */
    private function validate(EventTicket $eventTicket): void
    {
        foreach ($this->requiredProperties as $key) {
            if (!isset($eventTicket->$key)) {
                throw new InvalidArgumentException("'$key' is required.");
            }
        }
    }

    /**
     * @since 4.6.0 Add support for feature flag when disabled and include donation currency
     * @since 3.20.0 Add "amount" column to the select statement
     * @since      3.6.0
     * @return ModelQueryBuilder<EventTicket>
     */
    public function prepareQuery(): ModelQueryBuilder
    {
        $builder = new ModelQueryBuilder(EventTicket::class);

        if (!$this->isFeatureActive()) {
            // Return a query builder that safely returns empty results
            // Use a subquery that will never return results but handles all possible column references
            return $builder->from(
                DB::raw('(SELECT NULL as id, NULL as event_id, NULL as ticket_type_id, NULL as donation_id, NULL as amount, NULL as created_at, NULL as updated_at, NULL as currency WHERE 1 = 0)'),
                'tickets'
            );
        }

        return $builder->from('give_event_tickets', 'tickets')
            ->select(
                ['tickets.id', 'id'],
                ['tickets.event_id', 'event_id'],
                ['tickets.ticket_type_id', 'ticket_type_id'],
                ['tickets.amount', 'amount'],
                ['tickets.created_at', 'created_at'],
                ['tickets.updated_at', 'updated_at'],
            )
            ->selectRaw("tickets.donation_id as donation_id")
            ->attachMeta(
                 'give_donationmeta',
                 'tickets.donation_id',
                 'donation_id',
                 [DonationMetaKeys::CURRENCY, 'currency']
             );
    }

    /**
     * Check if a database table exists
     *
     * @since 4.6.0
     */
    private function tableExists(string $tableName): bool
    {
        global $wpdb;

        $prefixedTableName = $wpdb->prefix . $tableName;
        $query = $wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($prefixedTableName));

        return (bool) $wpdb->get_var($query);
    }

    /**
     * @since 3.6.0
     */
    public function queryByEventId(int $eventId): ModelQueryBuilder
    {
        return $this->prepareQuery()
            ->where('tickets.event_id', $eventId);
    }

    /**
     * @since 3.6.0
     */
    public function queryByTicketTypeId(int $ticketTypeId): ModelQueryBuilder
    {
        return $this->prepareQuery()
            ->where('tickets.ticket_type_id', $ticketTypeId);
    }

    /**
     * @since 3.6.0
     *
     * @param int $donationId
     *
     * @return ModelQueryBuilder
     */
    public function queryByDonationId(int $donationId): ModelQueryBuilder
    {
        return $this->prepareQuery()
            ->where('tickets.donation_id', $donationId);
    }

    /**
     * @since 4.6.0 Ensure the currency is the same as the donation amount currency
     * @since 3.20.0 Refactored to use event ticket amount instead of ticket type price
     * @since 3.6.0
     */
    public function getTotalByDonation(Donation $donation): Money
    {
        $eventTickets = $this->queryByDonationId($donation->id)->getAll() ?? [];

        return array_reduce($eventTickets, static function (Money $carry, EventTicket $eventTicket) {
            return $carry->add($eventTicket->amount);
        }, new Money(0, $donation->amount->getCurrency()));
    }

    /**
     * @since 4.6.0
     */
    public function getEventTicketDetails(Donation $donation): array
    {
        $details = [];
        $eventTickets = $this->queryByDonationId($donation->id)->getAll() ?? [];

        foreach ($eventTickets as $eventTicket) {
            $details[] = array_merge($eventTicket->toArray(), [
                'event' => $eventTicket->event->toArray(),
                'ticketType' => $eventTicket->ticketType->toArray(),
            ]);
        }

        return $details;
    }
}

```
