Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try caching get_remaining_tickets() in APCu to allow tickets to be 'reserved' during checkout, avoiding race condition #1296

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions public_html/wp-content/plugins/camptix/camptix.php
Original file line number Diff line number Diff line change
Expand Up @@ -6838,6 +6838,21 @@ function get_ticket_title( $post_id ) {
*/
function get_remaining_tickets( $post_id, $via_reservation = false ) {
$remaining = 0;
$apcu_key = 'remaining_tickets:' . get_current_blog_id() . ':' . $post_id;
if ( function_exists( 'apcu_enabled' ) && apcu_enabled() && ! $via_reservation ) {
do {
if ( isset( $fetch_success ) ) {
// If we failed to fetch the value, wait 50ms before trying again.
usleep( 50000 );
}
$remaining = (int) apcu_fetch( $apcu_key, $fetch_success );
} while ( ! $fetch_success && ! apcu_add( $apcu_key . ':lock', 1, 5 ) );

if ( $fetch_success ) {
return $remaining;
}
}

if ( $this->is_ticket_valid_for_display( $post_id ) ) {
$quantity = intval( get_post_meta( $post_id, 'tix_quantity', true ) );
$remaining = $quantity - $this->get_purchased_tickets_count( $post_id );
Expand All @@ -6856,9 +6871,23 @@ function get_remaining_tickets( $post_id, $via_reservation = false ) {
$remaining -= $reserved_tickets;
}

return apply_filters( 'camptix_get_remaining_tickets', $remaining, $post_id, $via_reservation, $quantity, $reservations );
// Can't have less than 0 remaining tickets.
$remaining = max( $remaining, 0 );

if ( function_exists( 'apcu_enabled' ) && apcu_enabled() && ! $via_reservation ) {
apcu_store( $apcu_key, $remaining, 10 * MINUTE_IN_SECONDS );
}

return $remaining;
}

/**
* Fetch the number of purchased tickets for a ticket.
*
* @param int $post_id The ticket post ID
* @param string $via_reservation Optional. The reservation token.
* @return int
*/
function get_purchased_tickets_count( $post_id, $via_reservation = false ) {
$purchased = 0;

Expand Down Expand Up @@ -7172,13 +7201,13 @@ function form_checkout() {
if ( ! is_email( $receipt_email ) )
$this->error_flags['no_receipt_email'] = true;

$this->verify_order( $this->order, true /* reserve tickets */ );

// If there's at least one error, don't proceed with checkout.
if ( $this->error_flags ) {
return $this->form_attendee_info();
}

$this->verify_order( $this->order );

$reservation_quantity = 0;
if ( isset( $this->reservation ) && $this->reservation )
$reservation_quantity = $this->reservation['quantity'];
Expand Down Expand Up @@ -7274,6 +7303,7 @@ function form_checkout() {
*/
$result = $payment_method_obj->payment_checkout( $payment_token );
if ( self::PAYMENT_STATUS_FAILED == $result ) {
// TODO: Release ticket? Otherwise this will just be reserved until the cache timeout.
return $this->form_attendee_info();
}

Expand All @@ -7287,7 +7317,7 @@ function form_checkout() {
/**
* Verify an order
*/
function verify_order( &$order = array() ) {
function verify_order( &$order = array(), $reserve_tickets = false ) {
$tickets_objects = get_posts( array(
'post_type' => 'tix_ticket',
'post_status' => 'publish',
Expand Down Expand Up @@ -7387,6 +7417,23 @@ function verify_order( &$order = array() ) {
$this->error_flag( 'tickets_excess' );
}

// Triple check - make sure we actually have a ticket to sell them.
if (
function_exists( 'apcu_enabled' ) && apcu_enabled() &&
$reserve_tickets &&
empty( $this->error_flags[ 'tickets_excess' ] ) &&
$item['quantity'] &&
! $via_reservation
) {
// The key used in get_remaining_tickets() above.
$apcu_key = 'remaining_tickets:' . get_current_blog_id() . ':' . $ticket->ID;
$ticket->tix_remaining = apcu_dec( $apcu_key, $item['quantity'], $success );
if ( ! $success || $ticket->tix_remaining < 0 ) {
$item['quantity'] = max( 0, min( $ticket->tix_remaining, $ticket->tix_remaining ) );
$this->error_flag( 'tickets_excess' );
}
}

// Track coupons usage quantity.
if ( $ticket->tix_coupon_applied ) {
$coupon_used += $item['quantity'];
Expand Down
Loading