0 && $action=='refund') ticketdrum_purchase_refund(); elseif ($id>0 && $action=='resend_emails') ticketdrum_purchases_resend_emails(); elseif ($action=='edit' && $id>0) ticketdrum_purchase_edit(); elseif ($id>0 || $action=='add') ticketdrum_purchases_view(); else ticketdrum_purchases_list(); } function ticketdrum_export_purchases() { if (sanitize_key(wp_unslash($_GET['action'] ?? ''))=='ticketdrum_export_purchases') { if (!current_user_can('manage_options')) return; check_admin_referer('ticketdrum_export'); global $wpdb; $return_array = []; $all_custom_fields = $event_custom_fields = []; $custom_field_rows = $wpdb->get_results($wpdb->prepare("SELECT custom_fields FROM %i",TICKETDRUM_PURCHASES_TABLE),ARRAY_A); foreach ($custom_field_rows as $custom_field_row) { $custom_fields = json_decode($custom_field_row['custom_fields'],true); #echo('
'.print_r($custom_fields,true).'
'); foreach ($custom_fields as $custom_field_key=>$custom_field_value) if (strpos($custom_field_key,'event')===0) foreach ($custom_field_value as $custom_field_sub_key=>$custom_field_sub_value) $event_custom_fields[] = $custom_field_sub_key; if (isset($custom_fields['checkout'])) foreach ($custom_fields['checkout'] as $cf_row_key=>$cf_row_val) $all_custom_fields[] = $cf_row_key; } $event_custom_fields = array_values(array_unique($event_custom_fields)); $all_custom_fields = array_values(array_unique($all_custom_fields)); $purchases = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE status!='pending' ORDER BY added ASC",TICKETDRUM_PURCHASES_TABLE),ARRAY_A); foreach ($purchases AS $purchase) { $ticket_data = json_decode($purchase['ticket_data'],true); $custom_fields = json_decode($purchase['custom_fields'],true); $tickets_description = ''; $attendee_count = 0; foreach ($ticket_data['tickets'] as $ticket_data_row) { $ticket_details = $wpdb->get_row($wpdb->prepare("SELECT id,title,price,attendee_count FROM %i WHERE active=1 AND id=%d",TICKETDRUM_TICKETS_TABLE,(int)$ticket_data_row['id']),ARRAY_A ); if ($ticket_data_row['quantity']>0) { $event_details = $wpdb->get_row($wpdb->prepare("SELECT id,title FROM %i WHERE id=%d",TICKETDRUM_EVENTS_TABLE,(int)$ticket_data_row['event_id']),ARRAY_A ); $tickets_description.=$ticket_data_row['description']." (".$event_details['title']."), "; } $attendee_count+= $ticket_data_row['quantity']*$ticket_details['attendee_count']; } $tickets_description = trim($tickets_description,', '); $row = []; $row[__('Purchase ID','ticketdrum')] = sprintf('%04d', $purchase['id']); $row[__('Date','ticketdrum')] = $purchase['added']; $row[__('Name','ticketdrum')] = $purchase['name']; $row[__('Email','ticketdrum')] = $purchase['email']; $row[__('Subtotal','ticketdrum')] = $purchase['subtotal']; $row[get_option('ticketdrum_tax_title',__('Tax','ticketdrum'))] = $purchase['tax']; $row[__('Total','ticketdrum')] = $purchase['total']; $row[__('Status','ticketdrum')] = $purchase['status']; $row[__('Tickets','ticketdrum')] = $tickets_description; $row[__('Attendee Count','ticketdrum')] = $attendee_count; foreach ($all_custom_fields as $custom_field_row) if (isset($custom_fields['checkout'][$custom_field_row])) $row[$custom_field_row] = stripslashes($custom_fields['checkout'][$custom_field_row]); else $row[$custom_field_row] = ''; #echo('
'.print_r($custom_fields,true).'
'); foreach ($event_custom_fields as $custom_field_row) foreach ($custom_fields as $cf_key=>$custom_field) if (strpos($cf_key,'event')===0) { if (isset($custom_field[$custom_field_row])) $row[$custom_field_row] = stripslashes($custom_field[$custom_field_row]); else $row[$custom_field_row] = ''; } $return_array[] = $row; } #die('
'.print_r($return_array,true).'
'); //The buffer is opened and closed within these few lines, before a single header is sent, //so it cannot be left open for anything else to trip over: fputcsv() writes to //php://output, ob_get_clean() takes that back as a string, and the download is echoed //afterwards from the variable. ob_start(); $df = fopen("php://output", 'w'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming CSV to php://output fputcsv($df, array_keys(end($return_array))); foreach ($return_array as $row) fputcsv($df, $row); fclose($df); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- streaming CSV to php://output $csv_output = ob_get_clean(); header("Expires: Tue, 03 Jul 2001 06:00:00 GMT"); header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Content-Type: application/force-download"); header("Content-Type: application/octet-stream"); header("Content-Type: application/download"); header("Content-Disposition: attachment;filename=purchases_export_".gmdate("Ymd").".csv"); header("Content-Transfer-Encoding: binary"); echo $csv_output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV file download, not HTML exit; } } function ticketdrum_export_tickets() { if (sanitize_key(wp_unslash($_GET['action'] ?? ''))=='ticketdrum_export_tickets') { if (!current_user_can('manage_options')) return; check_admin_referer('ticketdrum_export'); global $wpdb; $return_array = []; $all_custom_fields = []; $custom_fields = $wpdb->get_results($wpdb->prepare("SELECT custom_fields FROM %i",TICKETDRUM_PURCHASES_TABLE),ARRAY_A); foreach ($custom_fields as $cf) { $cf_decoded = json_decode($cf['custom_fields'],true); foreach ($cf_decoded as $cf_decoded_type) foreach ($cf_decoded_type as $cf_row_key=>$cf_row_val) { if (is_array(($cf_row_val))) { foreach ($cf_row_val as $cftk=>$cftv) $all_custom_fields[] = $cftk; } else $all_custom_fields[] = $cf_row_key; } } $all_custom_fields = array_unique($all_custom_fields); sort($all_custom_fields); $purchases = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE status!='pending' ORDER BY added ASC",TICKETDRUM_PURCHASES_TABLE),ARRAY_A); foreach ($purchases AS $purchase) { $ticket_data = json_decode($purchase['ticket_data'],true); $custom_fields = json_decode($purchase['custom_fields'],true); foreach ($ticket_data['tickets'] as $ticket) { for ($ticket_index=0;$ticket_index<$ticket['quantity']*$ticket['attendee_count'];$ticket_index++) { $row = []; $row[__('Purchase ID','ticketdrum')] = sprintf('%04d', $purchase['id']); $row[__('Date','ticketdrum')] = $purchase['added']; $row[__('Name','ticketdrum')] = $purchase['name']; $row[__('Email','ticketdrum')] = $purchase['email']; $row[__('Ticket','ticketdrum')] = $ticket['title']; foreach ($all_custom_fields as $custom_field) { if (isset($custom_fields['ticket'.$ticket['id']][$ticket_index][$custom_field])) $row[$custom_field] = $custom_fields['ticket'.$ticket['id']][$ticket_index][$custom_field]; elseif (isset($custom_fields['checkout'][$custom_field])) $row[$custom_field] = $custom_fields['checkout'][$custom_field]; else $row[$custom_field] = ""; } $return_array[] = $row; } } } //The buffer is opened and closed within these few lines, before a single header is sent, //so it cannot be left open for anything else to trip over: fputcsv() writes to //php://output, ob_get_clean() takes that back as a string, and the download is echoed //afterwards from the variable. ob_start(); $df = fopen("php://output", 'w'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming CSV to php://output fputcsv($df, array_keys(end($return_array))); foreach ($return_array as $row) fputcsv($df, $row); fclose($df); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- streaming CSV to php://output $csv_output = ob_get_clean(); header("Expires: Tue, 03 Jul 2001 06:00:00 GMT"); header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Content-Type: application/force-download"); header("Content-Type: application/octet-stream"); header("Content-Type: application/download"); header("Content-Disposition: attachment;filename=purchases_export_".gmdate("Ymd").".csv"); header("Content-Transfer-Encoding: binary"); echo $csv_output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV file download, not HTML exit; } } function ticketdrum_purchase_refund() { ticketdrum_require_admin(); global $wpdb,$ticketdrum_payment_data,$ticketdrum_stripe_mode; $id = absint(wp_unslash($_GET['id'] ?? 0)); if (!check_admin_referer('ticketdrum_refund_'.$id)) return; $purchase = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d AND status='paid' LIMIT 1",TICKETDRUM_PURCHASES_TABLE,$id),ARRAY_A); if (!$purchase || $purchase['payment_intent']=='') { echo '

'.esc_html__('Purchase not found or not refundable.','ticketdrum').'

'; ticketdrum_purchases_view(); return; } $refundable = round($purchase['total']-$purchase['refunded_amount'],2); $refund_amount = round((float)sanitize_text_field(wp_unslash($_POST['refund_amount'] ?? $refundable)),2); if ($refund_amount<=0 || $refund_amount>$refundable) { /* translators: %s: maximum refundable amount, with currency sign */ echo '

'.wp_kses_post(sprintf(__('Invalid refund amount - up to %s can be refunded on this order.','ticketdrum'),ticketdrum_currency_format($refundable))).'

'; ticketdrum_purchases_view(); return; } $purchase_meta = json_decode($purchase['payment_data'],true); $mode = (isset($purchase_meta['mode']) && $purchase_meta['mode']=='live')?'live':(isset($purchase_meta['livemode']) && $purchase_meta['livemode']?'live':'test'); if (($ticketdrum_payment_data[$mode.'_stripe_access_token'] ?? '')=='') $mode = $ticketdrum_stripe_mode; try { $TicketDrumStripe = new \TicketDrumStripe\TicketDrumStripeClient($ticketdrum_payment_data[$mode.'_stripe_access_token']); $refund = $TicketDrumStripe->refunds->create(['payment_intent'=>$purchase['payment_intent'],'amount'=>(int)round($refund_amount*100)]); } catch (Exception $e) { /* translators: %s: error message from Stripe */ echo '

'.esc_html(sprintf(__('Refund failed: %s','ticketdrum'),$e->getMessage())).'

'; ticketdrum_purchases_view(); return; } $new_refunded = round($purchase['refunded_amount']+$refund_amount,2); $fully_refunded = $new_refunded>=round($purchase['total'],2)-0.005; $wpdb->update(TICKETDRUM_PURCHASES_TABLE,['refunded_amount'=>$new_refunded,'status'=>$fully_refunded?'refunded':'paid'],['id'=>$id]); //Spaces just came free - tell the waitlist if ($fully_refunded) foreach (array_filter(explode(',',$purchase['event_ids'])) as $freed_event_id) ticketdrum_waitlist_notify((int)$freed_event_id); $refund_message = $fully_refunded /* translators: 1: refund amount with currency sign, 2: buyer name */ ? __('Full refund of %1$s issued to %2$s. The buyer will see it in a few working days.','ticketdrum') /* translators: 1: refund amount with currency sign, 2: buyer name */ : __('Partial refund of %1$s issued to %2$s. The buyer will see it in a few working days.','ticketdrum'); echo '

'.wp_kses_post(sprintf($refund_message,ticketdrum_currency_format($refund_amount),esc_html(stripslashes($purchase['name'])))).'

'; ticketdrum_purchases_view(); } function ticketdrum_purchases_list() { global $wpdb; $pagenum = isset( $_GET['pagenum'] ) ? absint( $_GET['pagenum'] ) : 1; //Three fixed query shapes rather than a query assembled from pieces. Every statement below //is a string literal handed straight to $wpdb->prepare(), so there is no path by which //anything from the request reaches the SQL text. Pending rows are in-progress or abandoned //checkouts rather than orders, so they are excluded throughout. $limit = 50; // number of rows in page $offset = ($pagenum - 1) * $limit; $search_string = trim(sanitize_text_field(wp_unslash($_GET['s'] ?? ''))); $search_like = '%'.$wpdb->esc_like($search_string).'%'; $search_like_ids = '%,'.$wpdb->esc_like($search_string).',%'; if ($search_string=='') { $total = (int)$wpdb->get_var($wpdb->prepare( "SELECT COUNT(id) FROM %i WHERE status!='pending'", TICKETDRUM_PURCHASES_TABLE)); } elseif (strlen($search_string)>2) { $total = (int)$wpdb->get_var($wpdb->prepare( "SELECT COUNT(id) FROM %i WHERE status!='pending' AND (name LIKE %s OR email LIKE %s OR custom_fields LIKE %s OR event_ids LIKE %s OR ticket_ids LIKE %s)", TICKETDRUM_PURCHASES_TABLE,$search_like,$search_like,$search_like,$search_like_ids,$search_like_ids)); } else { //Very short searches only match whole ids, never free text, so they stay useful $total = (int)$wpdb->get_var($wpdb->prepare( "SELECT COUNT(id) FROM %i WHERE status!='pending' AND (event_ids LIKE %s OR ticket_ids LIKE %s)", TICKETDRUM_PURCHASES_TABLE,$search_like_ids,$search_like_ids)); } $num_of_pages = ceil( $total / $limit ); $page_links = paginate_links( array( 'base' => add_query_arg( 'pagenum', '%#%' ), 'format' => '', 'prev_text' => __( '«', 'ticketdrum' ), 'next_text' => __( '»', 'ticketdrum' ), 'total' => $num_of_pages, 'current' => $pagenum ) ); ?>

()


 
get_results($wpdb->prepare( "SELECT * FROM %i WHERE status!='pending' ORDER BY added DESC LIMIT %d, %d", TICKETDRUM_PURCHASES_TABLE,$offset,$limit),ARRAY_A); elseif (strlen($search_string)>2) $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM %i WHERE status!='pending' AND (name LIKE %s OR email LIKE %s OR custom_fields LIKE %s OR event_ids LIKE %s OR ticket_ids LIKE %s) ORDER BY added DESC LIMIT %d, %d", TICKETDRUM_PURCHASES_TABLE,$search_like,$search_like,$search_like,$search_like_ids,$search_like_ids,$offset,$limit),ARRAY_A); else $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM %i WHERE status!='pending' AND (event_ids LIKE %s OR ticket_ids LIKE %s) ORDER BY added DESC LIMIT %d, %d", TICKETDRUM_PURCHASES_TABLE,$search_like_ids,$search_like_ids,$offset,$limit),ARRAY_A); ?> get_row($wpdb->prepare("SELECT id,title,price,attendee_count FROM %i WHERE active=1 AND id=%d",TICKETDRUM_TICKETS_TABLE,(int)$ticket_data_row['id']),ARRAY_A ); if ($ticket_data_row['quantity']>0) { $event_details = $wpdb->get_row($wpdb->prepare("SELECT id,title FROM %i WHERE id=%d",TICKETDRUM_EVENTS_TABLE,(int)$ticket_data_row['event_id']),ARRAY_A ); if ($event_details && !in_array(stripslashes($event_details['title']),$event_names)) $event_names[] = stripslashes($event_details['title']); $tickets_description.=$ticket_data_row['description'].", "; } $attendee_count+= $ticket_data_row['quantity']*$ticket_details['attendee_count']; } $tickets_description = trim($tickets_description,', '); ?>
' . wp_kses_post($page_links) . '
'; ?>



get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d",TICKETDRUM_PURCHASES_TABLE,$id),ARRAY_A ); $ticket_data = json_decode($purchase['ticket_data'],true); $custom_fields = json_decode($purchase['custom_fields'],true); $attendees = json_decode($purchase['attendees'],true); $payment_data = json_decode($purchase['payment_data'],true); $tickets_description = ''; $attendee_count = 0; foreach ($ticket_data['tickets'] as $ticket_data_row) { $ticket_details = $wpdb->get_row($wpdb->prepare("SELECT id,title,price,attendee_count FROM %i WHERE active=1 AND id=%d",TICKETDRUM_TICKETS_TABLE,(int)$ticket_data_row['id']),ARRAY_A ); if ($ticket_data_row['quantity']>0) { $event_details = $wpdb->get_row($wpdb->prepare("SELECT id,title FROM %i WHERE id=%d",TICKETDRUM_EVENTS_TABLE,(int)$ticket_data_row['event_id']),ARRAY_A ); $tickets_description.=$ticket_data_row['description']." (".$event_details['title']."), "; } $attendee_count+= $ticket_data_row['quantity']*$ticket_details['attendee_count']; } $tickets_description = trim($tickets_description,', '); $event_custom_fields = []; foreach ($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 ); $event_custom_fields[] = [ 'title' => $event_details['title'], 'fields' => $custom_field_value ]; } $discount = (float)($ticket_data['discount'] ?? 0); $promo_code = $ticket_data['promo_code'] ?? ''; $buyer_paid_fee = ((float)$purchase['fee']>0 && round($purchase['total'],2)>round($purchase['subtotal']+$purchase['tax']-$discount,2)); ?>

__('paid','ticketdrum'),'pending'=>__('pending','ticketdrum'),'refunded'=>__('refunded','ticketdrum')]; echo esc_html($status_labels[$purchase['status']] ?? __('unknown','ticketdrum'));?>

0): ?> 0): ?> 0): ?> 0): ?>
$ticket_custom_fields) { ?>
$value) echo '
'.esc_html($title).': '.esc_html($value); ?>
-
-
0) { ?>

$value) echo '
'.esc_html($title).': '.esc_html($value); ?>

$formfield) { ?>
0): ?>

0): ?>

«

get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d",TICKETDRUM_PURCHASES_TABLE,$id),ARRAY_A ); $custom_fields = json_decode($purchase['custom_fields'],true); $ticket_data = json_decode($purchase['ticket_data'],true); // Update basic purchase info $custom_fields['checkout'] = map_deep(wp_unslash($_POST['custom_fields'] ?? []),'sanitize_text_field'); // Update tickets if provided if (isset($_POST['tickets'])) { // Clear old ticket data and rebuild $new_tickets = []; $new_subtotal = 0; $new_tax = 0; //Ticket IDs and quantities only, so absint on every posted value is the whole of //the validation this needs; anything non-numeric becomes 0 and is skipped below. $posted_tickets = map_deep(wp_unslash($_POST['tickets'] ?? []),'absint'); foreach ((array)$posted_tickets as $ticket_index => $ticket_info) { if (!is_array($ticket_info)) continue; if ((int)($ticket_info['quantity'] ?? 0) > 0) { // Get ticket details from database $ticket_details = $wpdb->get_row($wpdb->prepare("SELECT t.*, e.id as event_id FROM %i t LEFT JOIN %i e ON t.event_id = e.id WHERE t.id=%d",TICKETDRUM_TICKETS_TABLE,TICKETDRUM_EVENTS_TABLE,(int)($ticket_info['ticket_id'] ?? 0)), ARRAY_A); if ($ticket_details) { $quantity = (int)$ticket_info['quantity']; $ticket_price = (float)$ticket_details['price']; $line_subtotal = $quantity * $ticket_price; // Calculate tax for this ticket $tax_rate = $ticket_details['tax'] !== null ? (float)$ticket_details['tax'] : (float)get_option('ticketdrum_tax_rate', 0); $line_tax = 0; if (get_option('ticketdrum_tax_mode','none') != 'none' && $tax_rate > 0) { if (get_option('ticketdrum_tax_mode','none') == 'excluded') { $line_tax = $line_subtotal * ($tax_rate / 100); } elseif (get_option('ticketdrum_tax_mode','none') == 'included') { $line_tax = $line_subtotal - ($line_subtotal / (1 + $tax_rate / 100)); } } $new_tickets[] = [ 'id' => $ticket_details['id'], 'quantity' => $quantity, 'title' => $ticket_details['title'], 'description' => $ticket_details['title'], 'event_id' => $ticket_details['event_id'], 'attendee_count' => $ticket_details['attendee_count'] ]; $new_subtotal += $line_subtotal; $new_tax += $line_tax; } } } $ticket_data['tickets'] = $new_tickets; // Calculate new total $new_total = $new_subtotal; if (get_option('ticketdrum_tax_mode','none') == 'excluded') { $new_total = $new_subtotal + $new_tax; } // Update purchase totals $purchase_data_updates = [ 'subtotal' => round($new_subtotal, 2), 'tax' => round($new_tax, 2), 'total' => round($new_total, 2) ]; } else { $purchase_data_updates = []; } $custom_fields = json_encode($custom_fields); $ticket_data = json_encode($ticket_data); // Prepare update data $update_data = array( 'name' => sanitize_text_field(wp_unslash($_POST['name'] ?? '')), 'email' => sanitize_text_field(wp_unslash($_POST['email'] ?? '')), 'custom_fields' => sanitize_text_field($custom_fields), 'ticket_data' => sanitize_text_field($ticket_data) ); // Add recalculated totals if tickets were updated if (!empty($purchase_data_updates)) { $update_data = array_merge($update_data, $purchase_data_updates); } //echo '
'.print_r($_POST,true).'
';die(); $wpdb->update( TICKETDRUM_PURCHASES_TABLE, //table $update_data, //data array('ID' => $id) //where ); } else {//selecting value to update $purchase = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE id=%d",TICKETDRUM_PURCHASES_TABLE,$id),ARRAY_A ); $ticket_data = json_decode($purchase['ticket_data'],true); $custom_fields = json_decode($purchase['custom_fields'],true); $attendees = json_decode($purchase['attendees'],true); $payment_data = json_decode($purchase['payment_data'],true); //echo '
'.print_r($ticket_data,true).'
'; } ?>

«
$formfield) echo ''; ?>
get_results($wpdb->prepare("SELECT t.id, t.title, t.price, e.title as event_title FROM %i t LEFT JOIN %i e ON t.event_id = e.id WHERE t.active = 1 AND t.event_id = %d ORDER BY t.title",TICKETDRUM_TICKETS_TABLE,TICKETDRUM_EVENTS_TABLE,(int)$ticket['event_id']), ARRAY_A); echo '
'; /* translators: %d: ticket number within this order line */ echo '' . esc_html(sprintf(__('Ticket %d','ticketdrum'), (int)($ticket_index + 1))) . '
'; // Show event name if (!empty($event_tickets)) { /* translators: %s: event title */ echo '' . esc_html(sprintf(__('Event: %s','ticketdrum'), $event_tickets[0]['event_title'])) . '

'; } // Ticket type dropdown (limited to same event) echo esc_html__('Ticket Type:','ticketdrum') . '

'; // Quantity input echo esc_html__('Quantity:','ticketdrum') . ' '; echo '
'; $ticket_index++; } ?>
'.esc_html($formkey).'
' class='button-primary'>