[],'promo'=>'','pending'=>0]; $raw = sanitize_text_field(wp_unslash($_COOKIE['ticketdrum_basket'] ?? '')); if ($raw!='' && strpos($raw,'.')!==false) { list($payload,$sig) = explode('.',$raw,2); if (hash_equals(substr(hash_hmac('sha256',$payload,wp_salt('auth')),0,20),$sig)) { $data = json_decode(base64_decode($payload),true); if (is_array($data)) { foreach (($data['tickets'] ?? []) as $tid=>$qty) if ((int)$tid>0 && (int)$qty>0) $basket['tickets'][(int)$tid] = min(100,(int)$qty); $basket['promo'] = substr(preg_replace('/[^A-Z0-9\-_]/','',strtoupper($data['promo'] ?? '')),0,50); $basket['pending'] = (int)($data['pending'] ?? 0); } } } return $basket; } function ticketdrum_basket_save($basket) { global $ticketdrum_basket; $ticketdrum_basket = $basket; if (headers_sent()) return; //Read-only contexts must not try to persist $payload = base64_encode(wp_json_encode($basket)); $sig = substr(hash_hmac('sha256',$payload,wp_salt('auth')),0,20); setcookie('ticketdrum_basket',$payload.'.'.$sig,[ 'expires' => time()+172800, 'path' => '/', 'secure' => is_ssl(), 'httponly' => true, 'samesite' => 'Lax', ]); $_COOKIE['ticketdrum_basket'] = $payload.'.'.$sig; } //Customisable interface labels: option name is ticketdrum_label_{key}; missing option = default function ticketdrum_labels() { return [ 'view_tickets' => [__('View available tickets button','ticketdrum'),__('View Tickets','ticketdrum')], 'ticket_type' => [__('Ticket table header','ticketdrum'),__('Ticket Type','ticketdrum')], 'checkout_button' => [__('Proceed to checkout button','ticketdrum'),__('Proceed to Checkout >','ticketdrum')], 'sold_out' => [__('Sold out message','ticketdrum'),__('Sold Out','ticketdrum')], 'booking_fee' => [__('Booking fee line at checkout','ticketdrum'),__('Booking fee','ticketdrum')], 'order_thanks' => [__('Order complete message','ticketdrum'),__('Thank you, your order has been received and you have been sent a confirmation email.','ticketdrum')], 'waitlist_intro' => [__('Waitlist invitation','ticketdrum'),__('Join the waitlist and we\'ll email you if tickets become available:','ticketdrum')], 'waitlist_button' => [__('Join waitlist button','ticketdrum'),__('Join waitlist','ticketdrum')], ]; } function ticketdrum_label($key) { $labels = ticketdrum_labels(); $saved = trim((string)get_option('ticketdrum_label_'.$key,'')); return $saved!=''?$saved:$labels[$key][1]; } function ticketdrum_currency_sign() { if (get_option('ticketdrum_currency','usd')=='usd') return '$'; elseif (get_option('ticketdrum_currency','usd')=='eur') return '€'; elseif (get_option('ticketdrum_currency','usd')=='gbp') return '£'; return ''; } function ticketdrum_currency_format($amount) { return ticketdrum_currency_sign().number_format($amount,2); } function ticketdrum_datetime_format($datetime,$length='default') { if ($length=='block') return ''.gmdate("j",strtotime($datetime)).' '.gmdate("M",strtotime($datetime)).''.gmdate("Y",strtotime($datetime)).''; else $format = "D jS M Y H:i"; return gmdate($format,strtotime($datetime)); } function ticketdrum_calculate_spaces_taken($event_id) { global $wpdb; $spaces_taken = 0; $purchases = $wpdb->get_results($wpdb->prepare("SELECT ticket_data FROM %i WHERE event_ids LIKE %s",TICKETDRUM_PURCHASES_TABLE,'%,'.$wpdb->esc_like((string)(int)$event_id).',%')); $event_tickets = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE event_id=%d",TICKETDRUM_TICKETS_TABLE,$event_id)); foreach ($purchases as $purchase) { $ticket_data = json_decode($purchase->ticket_data,true); foreach ($ticket_data['tickets'] as $ticket) { if ($ticket['quantity']==0) continue; foreach ($event_tickets as $event_ticket) if ($event_ticket->id == $ticket['id']) $spaces_taken+= $event_ticket->attendee_count*$ticket['quantity']; } } return (int)$spaces_taken; } function ticketdrum_calculate_basket_totals() { global $wpdb,$ticketdrum_basket; $tickets = []; $tickets_description = ''; $subtotal = $tax = $total = 0; $event_ids = []; $ticket_ids = []; $events = []; $default_tax_rate = get_option('ticketdrum_tax_rate'); $tax_mode = get_option('ticketdrum_tax_mode'); foreach ($ticketdrum_basket['tickets'] as $ticket_id=>$quantity) { $ticket_details = $wpdb->get_row($wpdb->prepare("SELECT t.id,t.event_id,t.title,t.price,t.attendee_count,t.max_purchase_quantity,t.tax,t.custom_fields,e.custom_fields event_custom_fields FROM %i t LEFT JOIN %i e ON e.id=t.event_id WHERE t.active=1 AND t.id=%d",TICKETDRUM_TICKETS_TABLE,TICKETDRUM_EVENTS_TABLE,$ticket_id),ARRAY_A ); $event_details = $wpdb->get_row($wpdb->prepare("SELECT * from %i where id=%d",TICKETDRUM_EVENTS_TABLE,$ticket_details['event_id']),ARRAY_A); $ticket_tax_rate = (isset($ticket_details['tax']) && is_numeric($ticket_details['tax']))?$ticket_details['tax']:$default_tax_rate; $ticket_tax = 0; if ($tax_mode=='included') $ticket_tax = round($ticket_details['price'] - ($ticket_details['price']/(1+$ticket_tax_rate/100)),2); elseif ($tax_mode=='excluded') $ticket_tax = round($ticket_details['price'] * ($ticket_tax_rate/100),2); $tickets[] = [ 'id' => $ticket_details['id'], 'event_id' => $ticket_details['event_id'], 'title' => $ticket_details['title'], 'price' => $ticket_details['price'], 'tax' => $ticket_tax, 'attendee_count' => $ticket_details['attendee_count'], 'custom_fields' => (array)json_decode($ticket_details['custom_fields'],true), //'event_custom_fields' => (array)json_decode($ticket_details['event_custom_fields'],true), 'price_formatted' => number_format($ticket_details['price'],2), 'quantity' => $quantity, 'max_purchase_quantity' => $ticket_details['max_purchase_quantity'], 'line_total' => $ticket_details['price']*$quantity, /* translators: 1: quantity, 2: ticket type name, 3: unit price, e.g. "2x Adult @£5.00" */ 'description' => $quantity>0?sprintf(__('%1$sx %2$s @%3$s','ticketdrum'),$quantity,$ticket_details['title'],ticketdrum_currency_format($ticket_details['price'])):'', ]; $events[$event_details['id']] = [ 'title' => $event_details['title'], 'custom_fields' => (array)json_decode($ticket_details['event_custom_fields'],true), ]; $subtotal+= $quantity*$ticket_details['price']; $tax+= $quantity*$ticket_tax; if ($quantity>0) { $event_ids[] = $ticket_details['event_id']; $ticket_ids[] = $ticket_details['id']; } } foreach ($tickets as $ticket) if ($ticket['description']!='') $tickets_description.=$ticket['description'].", "; $tickets_description = trim($tickets_description,', '); $subtotal = round($subtotal,2); $total = $subtotal; if ($tax_mode=='excluded') $total = $subtotal + $tax; //Promo code discount (validated again here so expiry/limits always hold) $discount = 0; $promo_code = ''; if (($ticketdrum_basket['promo'] ?? '')!='') { $promo_check = ticketdrum_promocode_validate($ticketdrum_basket['promo'],array_unique($event_ids)); if ($promo_check['result']) { $promo = $promo_check['promo']; $promo_code = $promo['code']; //Event-specific codes only discount that event's lines $applicable_total = 0; foreach ($tickets as $t) if ($promo['event_id']==0 || (int)$t['event_id']==(int)$promo['event_id']) $applicable_total+= $t['line_total'] + ($tax_mode=='excluded'?$t['tax']*$t['quantity']:0); if ($promo['type']=='percent') $discount = round($applicable_total*($promo['amount']/100),2); else $discount = min($applicable_total,(float)$promo['amount']); $discount = min($discount,$total); $total = round($total-$discount,2); } } return [ 'discount' => $discount, 'discount_formatted' => ticketdrum_currency_format($discount), 'promo_code' => $promo_code, 'tickets' => $tickets, 'events' => $events, 'tickets_description' => $tickets_description, 'subtotal' => $subtotal, 'subtotal_formatted' => ticketdrum_currency_sign().number_format($subtotal,2), 'tax' => $tax, 'tax_formatted' => ticketdrum_currency_sign().number_format($tax,2), 'total' => $total, 'total_formatted' => ticketdrum_currency_sign().number_format($total,2), 'event_ids' => array_unique($event_ids), 'ticket_ids' => array_unique($ticket_ids), ]; } function ticketdrum_check_ticket_availability($ticket_id,$quantity_wanted) { global $wpdb; $max_available = (int)$wpdb->get_var($wpdb->prepare("SELECT max_available FROM %i WHERE id=%d",TICKETDRUM_TICKETS_TABLE,$ticket_id)); if ($max_available<=0) //0 = unlimited return ['result'=>true,'remaining'=>-1]; //Pending purchases count as holds so two buyers can't both take the last tickets global $ticketdrum_basket; $exclude_purchase_id = (int)($ticketdrum_basket['pending'] ?? 0); $sold = 0; $purchases = $wpdb->get_results($wpdb->prepare("SELECT id,ticket_data FROM %i WHERE ticket_ids LIKE %s AND status IN ('pending','paid') AND id!=%d",TICKETDRUM_PURCHASES_TABLE,'%,'.$wpdb->esc_like((string)(int)$ticket_id).',%',$exclude_purchase_id)); foreach ($purchases as $purchase) { $ticket_data = json_decode($purchase->ticket_data,true); if (!isset($ticket_data['tickets'])) continue; foreach ($ticket_data['tickets'] as $ticket) if ((int)$ticket['id']==(int)$ticket_id) $sold+= (int)$ticket['quantity']; } $remaining = max(0,$max_available-$sold); return ['result'=>($remaining>=$quantity_wanted),'remaining'=>$remaining]; } function ticketdrum_fee_config() { global $ticketdrum_payment_data; //Rates come from the TicketDrum API (per-site, remotely tunable); fall back to published pricing return [ 'fee_percent' => isset($ticketdrum_payment_data['fee_percent'])?(float)$ticketdrum_payment_data['fee_percent']:4.90, 'fee_fixed' => isset($ticketdrum_payment_data['fee_fixed'])?(float)$ticketdrum_payment_data['fee_fixed']:0.30, 'stripe_percent' => isset($ticketdrum_payment_data['stripe_percent'])?(float)$ticketdrum_payment_data['stripe_percent']:1.50, 'stripe_fixed' => isset($ticketdrum_payment_data['stripe_fixed'])?(float)$ticketdrum_payment_data['stripe_fixed']:0.20, 'paid_by' => get_option('ticketdrum_fee_paid_by','buyer')=='organiser'?'organiser':'buyer', ]; } function ticketdrum_calculate_fees($basket_data) { $config = ticketdrum_fee_config(); $paid_ticket_count = 0; foreach ($basket_data['tickets'] as $ticket) if ($ticket['price']>0) $paid_ticket_count+= (int)$ticket['quantity']; //Advertised all-in fee: fee_percent% of the basket + fee_fixed per paid ticket. Free tickets are free. $fee = 0; if ($basket_data['total']>0) $fee = round($basket_data['total']*($config['fee_percent']/100) + $paid_ticket_count*$config['fee_fixed'],2); $charge_total = $basket_data['total']; if ($config['paid_by']=='buyer') $charge_total = round($basket_data['total'] + $fee,2); //This is a direct charge, so Stripe bills its processing fee to the organiser's account. Net our //application fee down by Stripe's standard rate so the organiser's all-in cost equals the advertised fee. $stripe_cost = round($charge_total*($config['stripe_percent']/100) + $config['stripe_fixed'],2); $application_fee = max(0,round($fee - $stripe_cost,2)); return [ 'fee' => $fee, 'fee_formatted' => ticketdrum_currency_format($fee), 'fee_paid_by' => $config['paid_by'], 'paid_ticket_count' => $paid_ticket_count, 'charge_total' => $charge_total, 'charge_total_formatted' => ticketdrum_currency_format($charge_total), 'application_fee_cents' => (int)round($application_fee*100), ]; } //All plugin email is sent by WordPress itself, exactly like any other plugin's email. Nothing //about a buyer, an order or a ticket is ever sent to ticketdrum.com. Sites that need better //deliverability than their host's default mail can install any SMTP plugin: wp_mail honours it //automatically, with no configuration here. function ticketdrum_mail($to_email,$to_name,$subject,$html,$attachment_paths=[]) { return wp_mail($to_email,$subject,$html,['Content-Type: text/html; charset=UTF-8'],$attachment_paths); } function ticketdrum_send_purchase_emails($purchase_id) { global $wpdb; $purchase_data = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d LIMIT 1",TICKETDRUM_PURCHASES_TABLE,$purchase_id),ARRAY_A); $purchase_data['ticket_data'] = json_decode($purchase_data['ticket_data'],true); $purchase_data['custom_fields'] = json_decode($purchase_data['custom_fields'],true); $purchase_data['event_custom_fields'] = []; foreach ($purchase_data['custom_fields'] as $custom_field_key=>$custom_field_value) if (strpos($custom_field_key,'event')===0) { $event_details = $wpdb->get_row($wpdb->prepare("SELECT id,title FROM %i WHERE id=%d",TICKETDRUM_EVENTS_TABLE,(int)str_replace('event','',$custom_field_key)),ARRAY_A ); $purchase_data['event_custom_fields'][] = [ 'title' => stripslashes($event_details['title']), 'fields' => stripslashes($custom_field_value) ]; } //Fetch event-level notification emails $event_notification_emails = []; foreach ($purchase_data['ticket_data']['tickets'] as $ticket) { if (!$ticket['quantity']) continue; $event_data = $wpdb->get_row($wpdb->prepare("SELECT notification_emails FROM %i WHERE id=%d LIMIT 1",TICKETDRUM_EVENTS_TABLE,$ticket['event_id']),ARRAY_A); $event_notification_emails = array_merge($event_notification_emails,explode(',',$event_data['notification_emails'])); } $event_notification_emails = array_unique($event_notification_emails); $event_notification_emails = array_map('trim', $event_notification_emails); $order_details = '
'.esc_html__('Name:','ticketdrum').' '.stripslashes($purchase_data['name']).'
'.esc_html__('Email:','ticketdrum').' '.$purchase_data['email'].'
';
if (isset($purchase_data['custom_fields']['checkout']))
foreach ($purchase_data['custom_fields']['checkout'] as $title=>$value)
$order_details.= ''.stripslashes($title).': '.stripslashes($value).'
';
$order_details.= '
| '.esc_html__('Quantity','ticketdrum').' | '.esc_html__('Total','ticketdrum').' | |
|---|---|---|
| '.stripslashes($event_details['title'] ?? '').' '.stripslashes($ticket['title']); //Per-ticket custom fields are only present when the ticket type defines them $ticket_custom_field_sets = $purchase_data['custom_fields']['ticket'.$ticket['id']] ?? []; if (!is_array($ticket_custom_field_sets)) $ticket_custom_field_sets = []; foreach ($ticket_custom_field_sets as $ticket_custom_field_index=>$ticket_custom_fields) { if (!is_array($ticket_custom_fields)) continue; /* translators: %s: ticket number within the order */ $order_details.=' '.esc_html(sprintf(__('Ticket %s','ticketdrum'),number_format_i18n($ticket_custom_field_index+1))).'';
foreach ($ticket_custom_fields as $title=>$value)
$order_details.=' |
'.$ticket['quantity'].' | '.ticketdrum_currency_format($ticket_total).' |
| ';
foreach ($purchase_data['event_custom_fields'] as $event_custom_field)
{
$order_details.=' '.stripslashes($event_custom_field['title']).'';
foreach ($event_custom_field['fields'] as $title=>$value)
$order_details.=' |
||
| '.esc_html__('Subtotal','ticketdrum').' | '.ticketdrum_currency_format($purchase_data['subtotal']).' | |
| '.get_option('ticketdrum_tax_title',__('Tax','ticketdrum')).' | '.ticketdrum_currency_format($purchase_data['tax']).' | |
| '.esc_html__('Booking fee','ticketdrum').' | '.ticketdrum_currency_format($purchase_data['fee']).' | |
| '.esc_html__('Total','ticketdrum').' | '.ticketdrum_currency_format($purchase_data['total']).' |
'.sprintf( /* translators: 1: tickets page URL, 2: receipt page URL */ __('Your tickets are attached to this email as a PDF. You can also view and print your tickets or view your receipt at any time.','ticketdrum'), esc_url(home_url('/?ticketdrum_tickets='.$purchase_token)),esc_url(home_url('/?ticketdrum_invoice='.$purchase_token))).'
'; //Add-to-calendar link for the first event on the order $first_event_id = 0; foreach ($purchase_data['ticket_data']['tickets'] as $t) if ($t['quantity'] && !$first_event_id) $first_event_id = (int)$t['event_id']; if ($first_event_id) { $cal_event = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d",TICKETDRUM_EVENTS_TABLE,$first_event_id),ARRAY_A); if ($cal_event && strtotime($cal_event['start_datetime'])>0) /* translators: %s: Google Calendar link URL */ $emailconfirmation_user.= ''.sprintf(__('Add to Google Calendar · a calendar file (.ics) is attached for Apple/Outlook.','ticketdrum'),esc_url(ticketdrum_google_calendar_link($cal_event))).'
'; } //Apple Wallet buttons (only when pass signing is configured) if (function_exists('ticketdrum_wallet_available') && ticketdrum_wallet_available()) { $wallet_purchase = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d",TICKETDRUM_PURCHASES_TABLE,$purchase_id),ARRAY_A); $wallet_token = ticketdrum_purchase_token($wallet_purchase); $wallet_links = []; foreach (ticketdrum_build_ticket_units($wallet_purchase) as $wu) $wallet_links[] = ''.esc_html($wu['ticket_title']).''; if (count($wallet_links)>0) /* translators: %s: list of links, one per ticket */ $emailconfirmation_user.= ''.sprintf(__('Add to Apple Wallet: %s','ticketdrum'),implode(' · ',$wallet_links)).'
'; } //Marketing footer: every buyer email is a chance to reach an event organiser $emailconfirmation_user.=''.sprintf( /* translators: %s: link to the TicketDrum website */ __('Ticket management by %s','ticketdrum'),'TicketDrum').'
'; $emailconfirmation_user = ticketdrum_neaten_email_html($emailconfirmation_user); //Blank entries are easy to leave behind in a comma-separated settings field, and each one //would otherwise cost a wasted wp_mail call to nobody $notification_emails = explode(',',get_option('ticketdrum_emailconfirmation_emails',get_bloginfo('admin_email'))); $notification_emails = array_merge($notification_emails,$event_notification_emails); $notification_emails = array_map('trim', $notification_emails); $notification_emails = array_unique(array_filter($notification_emails,'is_email')); foreach ($notification_emails as $notification_email) ticketdrum_mail($notification_email,'',$subject,$emailconfirmation_admin); $send_result = ticketdrum_mail($purchase_data['email'],stripslashes($purchase_data['name']),$subject,$emailconfirmation_user,$attachments); foreach ($attachments as $attachment) @wp_delete_file($attachment); } function ticketdrum_display_custom_fields($custom_fields,$custom_field_group='custom',$custom_field_index=false) { foreach ($custom_fields AS $formkey=>$formfield) { $title_display = str_replace('{counter}',$custom_field_index+1,$formfield['title']); $title_display = str_replace('{counter_word}',ticketdrum_number_to_ordinal_word($custom_field_index+1),$title_display); echo ''.esc_html(ticketdrum_label('sold_out')).'
'.ticketdrum_waitlist_form((int)$event_id).'