<?php
/**
* Add to Cart data.
*
* @category Core
* @package My Tickets
* @author Joe Dolson
* @license GPLv2 or later
* @link https://www.joedolson.com/my-tickets/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
} // Exit if accessed directly.
add_filter( 'mc_after_event', 'mt_registration_form', 5, 4 );
add_filter( 'the_content', 'mt_registration_form_post', 20, 1 ); // after wpautop.
/**
* Appends a registration form to post content for posts with defined event data.
*
* @uses function mt_registration_form();
* @param string $content Post Content.
*
* @return string
*/
function mt_registration_form_post( $content ) {
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
global $post;
if ( in_array( get_post_type( $post ), $options['mt_post_types'], true ) ) {
$event = $post->ID;
if ( get_post_meta( $event, '_mc_event_data', true ) ) {
$content = mt_registration_form( $content, $event );
}
}
return $content;
}
/**
* Test whether a price set for an event has any tickets available for purchase.
*
* @param array $pricing Events pricing array.
*
* @return bool
*/
function mt_has_tickets( $pricing ) {
$return = false;
if ( is_array( $pricing ) ) {
foreach ( $pricing as $options ) {
$tickets = (int) $options['tickets'];
if ( $tickets > 0 ) {
$return = true;
}
}
return $return;
}
return false;
}
/**
* Generates event registration form.
*
* @param string $content Page content.
* @param mixed/bool/int/object $event If boolean, exit.
* @param string $view Type of view for context.
* @param string $time Time view being displayed.
* @param boolean $override Don't display.
*
* @return string
*/
function mt_registration_form( $content, $event = false, $view = 'calendar', $time = 'month', $override = false ) {
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
$purchase_page = $options['mt_purchase_page'];
$receipt_page = $options['mt_purchase_page'];
$tickets_page = $options['mt_tickets_page'];
if ( is_page( $purchase_page ) || is_page( $receipt_page ) || is_page( $tickets_page ) ) {
return $content;
}
if ( ! $event ) {
return $content;
}
$form = '';
$cart_data = '';
$sold_out = '';
$has_tickets = '';
$output = '';
$event_id = ( is_object( $event ) ) ? $event->event_post : $event;
if ( 'mc-events' === get_post_type( $event_id ) ) {
$sell = get_post_meta( $event_id, '_mt_sell_tickets', true );
if ( 'false' === $sell ) {
return $content;
}
}
if ( 'true' === get_post_meta( $event_id, '_mt_hide_registration_form', true ) && false === $override ) {
return $content;
}
$registration = get_post_meta( $event_id, '_mt_registration_options', true );
// if no 'total' is set at all, this is not an event with tickets.
if ( empty( $registration['prices'] ) ) {
return $content;
}
// if total is set to inherit, but any ticket class has no defined number of tickets available, return. '0' is a valid number of tickets, '' is not.
if ( ( isset( $registration['total'] ) && 'inherit' === $registration['total'] ) && 'general' !== $registration['counting_method'] && ! mt_has_tickets( $registration['prices'] ) ) {
return $content;
}
// if total number of tickets is set but is an empty string or is not set; return.
if ( ( isset( $registration['total'] ) && '' === trim( $registration['total'] ) ) || ! isset( $registration['total'] ) ) {
return $content;
}
$expired = mt_expired( $event_id, true );
$no_postal = mt_no_postal( $event_id );
if ( $no_postal && 1 === count( $options['mt_ticketing'] ) && in_array( 'postal', $options['mt_ticketing'], true ) && ! ( current_user_can( 'mt-order-expired' ) || current_user_can( 'manage_options' ) ) ) {
$expired = true;
}
$handling_notice = '';
if ( ! $expired ) {
if ( is_array( $registration ) ) {
$pricing = $registration['prices'];
$nonce = wp_nonce_field( 'mt-cart-nonce', '_wpnonce', true, false );
if ( is_array( mt_in_cart( $event_id ) ) ) {
$cart_data = mt_in_cart( $event_id );
}
if ( isset( $_GET['mc_id'] ) ) {
$mc_id = (int) $_GET['mc_id'];
$permalink = add_query_arg( 'mc_id', $mc_id, get_the_permalink() );
} else {
$permalink = get_the_permalink();
}
if ( is_array( $pricing ) ) {
/**
* Filter default number of tickets considered available under General Admission. General Admission uses a floating value of available tickets, so there's no real limit; this value does limit the number of tickets that can be purchased at once, however.
*
* @hook mt_default_available
*
* @param {int} $default_available Number of tickets available.
* @param {array} $registration Ticketing rules for this event.
*
* @return {int}
*/
$default_available = apply_filters( 'mt_default_available', 100, $registration );
$available = ( 'general' === $registration['counting_method'] ) ? $default_available : $registration['total'];
// if multiple != true, use checkboxes.
$input_type = ( isset( $registration['multiple'] ) && 'true' === $registration['multiple'] ) ? 'number' : 'checkbox';
// Figure out handling for radio input type.
$tickets_data = mt_tickets_left( $pricing, $available );
$tickets_remaining = ( 'general' === $registration['counting_method'] ) ? $default_available : $tickets_data['remain'];
$tickets_sold = $tickets_data['sold'];
$class = 'mt-available';
/**
* Filter when online ticket sales should close based on availability. Default 0; sales close when sold out.
*
* @hook mt_tickets_close_value
*
* @param {int} $tickets_close_value Number of tickets remaining that triggers sold out condition.
* @param {int} $event_id Event ID.
* @param {array} $tickets_data Data about sold and available tickets.
*
* @return {int}
*/
if ( $tickets_remaining && $tickets_remaining > apply_filters( 'mt_tickets_close_value', 0, $event_id, $tickets_data ) ) {
$sold_out = false;
$total_order = 0;
foreach ( $pricing as $type => $settings ) {
/**
* Filter value data about a specific type of ticket.
*
* @hook mt_ticket_settings
*
* @param {array} $settings Price and availability for a ticket type.
* @param {array} $pricing Full pricing array being iterated.
* @param {int} $event_id Event ID.
* @param {strnig} $type Current ticket type.
*/
$settings = apply_filters( 'mt_ticket_settings', $settings, $pricing, $event_id, $type );
if ( ! mt_can_order( $type ) ) {
continue;
}
$extra_label = '';
if ( mt_admin_only( $type ) ) {
$extra_label = ' <span class="mt-admin-only">(' . __( 'Administrators only', 'my-tickets' ) . ')</span>';
}
/**
* Add additional labeling appended to ticket type information.
*
* @hook mt_extra_label
*
* @param {string} $extra_label Default empty string; 'Administrators only' for complimentary tickets.
* @param {int|object} $event Event post object or event ID.
* @param {string} $type Ticket type.
*
* @return {string}
*/
$extra_label = apply_filters( 'mt_extra_label', $extra_label, $event, $type );
if ( $type ) {
if ( ! isset( $settings['price'] ) ) {
continue;
}
$price = mt_calculate_discount( $settings['price'], $event_id );
$price = mt_handling_price( $price, $event, $type );
$price = apply_filters( 'mt_money_format', $price );
/**
* Filter ticket handling price.
*
* @hook mt_ticket_handling_price
*
* @param {string} $ticket_handling Handling price from settings.
* @param {int}object} $event Event ID or event post object.
* @param {string} $type Ticket type.
*/
$ticket_handling = apply_filters( 'mt_ticket_handling_price', $options['mt_ticket_handling'], $event, $type );
$handling_notice = mt_handling_notice();
$ticket_price_label = apply_filters( 'mt_ticket_price_label', $price, $settings['price'], $ticket_handling );
$value = ( is_array( $cart_data ) && isset( $cart_data[ $type ] ) ) ? $cart_data[ $type ] : apply_filters( 'mt_cart_default_value', '0', $type );
$value = ( '' === $value ) ? 0 : (int) $value;
$order_value = $value;
$attributes = '';
$close = ( isset( $settings['close'] ) && ! empty( $settings['close'] ) ) ? $settings['close'] : '';
if ( $close && $close < time() ) {
// If this ticket type is no longer available, skip.
continue;
}
if ( 'checkbox' === $input_type || 'radio' === $input_type ) {
if ( 1 === $value ) {
$attributes = " checked='checked'";
}
$value = 1;
$order_value = 0;
}
if ( 'inherit' === $available ) {
$tickets = absint( $settings['tickets'] );
$sold = absint( $settings['sold'] );
$remaining = ( $tickets - $sold );
/**
* Filter maximum sale per event. Limits number of tickets that can be purchased at a time.
*
* @hook mt_max_sale_per_event
*
* @param {bool} $max_limit Default false.
*
* @return {bool|int} Number of tickets that can be purchased at once or false.
*/
$max_limit = apply_filters( 'mt_max_sale_per_event', false );
if ( $max_limit ) {
$max = ( $max_limit > $remaining ) ? $remaining : $max_limit;
} else {
$max = $remaining;
}
$disable = ( $remaining < 1 ) ? ' disabled="disabled"' : '';
if ( '' === $attributes ) {
$attributes = " min='0' max='$max' inputmode='numeric' pattern='[0-9]*'";
if ( 0 === $remaining ) {
$attributes .= ' readonly="readonly"';
$class = 'mt-sold-out';
} else {
$class = 'mt-available';
}
}
$form .= "<div class='mt-ticket-field mt-ticket-$type $class'><label for='mt_tickets_$type" . '_' . "$event_id' id='mt_tickets_label_$type" . '_' . "$event_id'>" . esc_attr( $settings['label'] ) . $extra_label . '</label>';
$form .= apply_filters(
'mt_add_to_cart_input',
"<input type='$input_type' name='mt_tickets[$type]' id='mt_tickets_$type" . '_' . "$event_id' class='tickets_field' value='$value' $attributes aria-labelledby='mt_tickets_label_$type" . '_' . $event_id . " mt_tickets_data_$type'$disable />",
$input_type,
$type,
$value,
$attributes,
$disable,
$max,
$available
);
$hide_remaining = mt_hide_remaining( $tickets_remaining );
// Translators: Ticket price label, number remaining.
$form .= "<span id='mt_tickets_data_$type' class='ticket-pricing$hide_remaining'>" . sprintf( apply_filters( 'mt_tickets_remaining_discrete_text', __( '(%1$s, %2$s remaining%3$s)', 'my-tickets' ), $ticket_price_label, $remaining, $tickets ), $ticket_price_label . '<span class="tickets-remaining">', "<span class='value remaining-tickets'>" . $remaining . "</span>/<span class='ticket-count'>" . $tickets . '</span>', '</span>' ) . '</span>';
$form .= "<span class='mt-error-notice' aria-live='assertive'></span></div>";
$total_order = $total_order + $order_value;
} else {
$remaining = $tickets_remaining;
if ( '' === $attributes ) {
$attributes = " min='0' max='$remaining'";
if ( 0 === $remaining ) {
$attributes .= ' readonly="readonly"';
$class = 'mt-sold-out';
}
}
/**
* Filter whether the price should be shown in the label or as an aria-described field after the input.
*
* @hook mt_price_in_label
*
* @param {false} $price_in_label Default false.
* @param {int} $event_id Event ID.
*
* @return {bool}
*/
$price_in_label = apply_filters( 'mt_price_in_label', false, $event_id );
$price_class = ( $price_in_label ) ? 'mt-price-in-label' : '';
$price = "<span id='mt_tickets_data_$type'>$ticket_price_label</span>";
$label_price = ( $price_in_label ) ? ' <span class="mt-label-price">' . strip_tags( $price ) . '</span>' : '';
$post_price = ( ! $price_in_label ) ? $price : '';
$form .= "<div class='mt-ticket-field mt-ticket-$type $class $price_class'><label for='mt_tickets_$type" . '_' . "$event_id' id='mt_tickets_label_$type" . '_' . "$event_id'>" . esc_attr( $settings['label'] ) . $extra_label . $label_price . '</label>';
$form .= apply_filters(
'mt_add_to_cart_input',
"<input type='$input_type' name='mt_tickets[$type]' $attributes id='mt_tickets_$type" . '_' . "$event_id' class='tickets_field' value='$value' aria-labelledby='mt_tickets_label_$type" . '_' . $event_id . " mt_tickets_data_$type' />",
$input_type,
$type,
$value,
$attributes,
'',
$remaining,
$available
);
$form .= $post_price . "<span class='mt-error-notice' aria-live='assertive'></span></div>";
$total_order = $total_order + $value;
}
$has_tickets = true;
}
}
} else {
if ( 0 >= $tickets_remaining ) {
$sold_out = true;
} else {
$output = '<p>' . mt_tickets_remaining( $tickets_data, $event_id ) . '</p>';
}
}
}
if ( 'inherit' !== $available ) {
// If this event is general admission, then never show number of tickets remaining or status.
$data = get_post_meta( $event_id, '_mc_event_data', true );
if ( isset( $data['general_admission'] ) && 'on' === $data['general_admission'] ) {
$hide_remaining = ' hiding';
} else {
$hide_remaining = mt_hide_remaining( $tickets_remaining );
}
// Translators: tickets remaining.
$remaining_notice = '<p class="tickets-remaining' . $hide_remaining . '">' . sprintf( apply_filters( 'mt_tickets_remaining_continuous_text', __( '%s tickets remaining.', 'my-tickets' ) ), "<span class='value'>" . $tickets_remaining . '</span>' ) . '</p>';
} else {
$remaining_notice = '';
}
if ( true === $has_tickets ) {
$closing_time = mt_sales_close( $event_id, $registration['reg_expires'] );
$no_post = ( $no_postal && in_array( 'postal', array_keys( $options['mt_ticketing'] ), true ) ) ? "<p class='mt-no-post'>" . apply_filters( 'mt_cannot_send_by_email_text', __( 'Tickets for this event cannot be sent by mail.', 'my-tickets' ) ) . '</p>' : '';
$legend = ( 'registration' === $registration['sales_type'] ) ? __( 'Register', 'my-tickets' ) : __( 'Buy Tickets', 'my-tickets' );
$legend = apply_filters( 'mt_button_legend_text', $legend, $registration );
$disabled = ( $total_order > $tickets_remaining ) ? " disabled='disabled'" : '';
/**
* Filter hidden fields added to My Tickets add to cart form.
*
* @hook mt_add_to_cart_hidden_fields
*
* @param {string} $hidden HTML output. Default empty.
* @param {int} $event_id Event ID.
*
* @return {string}
*/
$hidden = apply_filters( 'mt_add_to_cart_hidden_fields', '', $event_id );
/**
* Filter visible fields added to My Tickets add to cart form. Inserted between standard fields and submit button.
*
* @hook mt_add_to_cart_fields
*
* @param {string} $fields HTML output. Default empty.
* @param {int} $event_id Event ID.
*
* @return {string}
*/
$fields = apply_filters( 'mt_add_to_cart_fields', '', $event_id );
$output = "
<div class='mt-order'>
<div class='mt-response' id='mt-response-$event_id' aria-live='assertive'></div>
$no_post
$closing_time
$handling_notice
<form action='" . esc_url( $permalink ) . "' method='POST' class='ticket-orders' id='order-tickets' tabindex='-1'>
<div>
$nonce
<input type='hidden' name='mt_event_id' value='$event_id' />" . $hidden . "
</div>
<fieldset>
<legend>$legend</legend>
$remaining_notice
<p>$form</p>" . $fields . "<p>
<button type='submit' name='mt_add_to_cart'" . $disabled . '>' . apply_filters( 'mt_add_to_cart_text', __( 'Add to Cart', 'my-tickets' ) ) . "<span class='mt-processing'><img src='" . admin_url( 'images/spinner-2x.gif' ) . "' alt='" . __( 'Working', 'my-tickets' ) . "' /></span></button>
<input type='hidden' name='my-tickets' value='true' />
</p>
</fieldset>
</form>
</div>";
}
}
} else {
$registration = get_post_meta( $event_id, '_mt_registration_options', true );
$available = $registration['total'];
$pricing = $registration['prices'];
$tickets_remaining = mt_tickets_left( $pricing, $available );
$tickets_remain_text = mt_tickets_remaining( $tickets_remaining, $event_id );
$sales_closed = ( 'registration' === $registration['sales_type'] ) ? __( 'Online registration for this event is closed', 'my-tickets' ) : __( 'Online ticket sales for this event are closed.', 'my-tickets' );
$output = "<div class='mt-order mt-closed'><p>" . apply_filters( 'mt_sales_closed', $sales_closed ) . "$tickets_remain_text</p></div>";
}
if ( true === $sold_out && $tickets_sold > 0 ) {
$tickets_soldout = ( 'registration' === $registration['sales_type'] ) ? __( 'Registration for this event is full', 'my-tickets' ) : __( 'Tickets for this event are sold out.', 'my-tickets' );
$output = "<div class='mt-order mt-soldout'><p>" . apply_filters( 'mt_tickets_soldout', $tickets_soldout ) . '</p></div>';
/**
* Append additional content to the tickets sold out notification.
*
* @hook mt_tickets_soldout_content
*
* @param {string} $output HTML output; default empty string.
* @param {int} $event_id
* @param {array} $registration Registration data for event.
*
* @return {string}
*/
$output .= apply_filters( 'mt_tickets_soldout_content', '', $event_id, $registration );
$soldout = get_post_meta( $event_id, '_mt_event_soldout', true );
if ( 'true' !== $soldout ) {
update_post_meta( $event_id, '_mt_event_soldout', 'true' );
do_action( 'mt_event_sold_out', $event_id, $registration, 'soldout' );
}
}
return $content . $output;
}
/**
* Get current status of an event.
*
* @param int $event_id Event ID.
*
* @return string
*/
function mt_event_status( $event_id = false ) {
// Exit conditions.
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
$purchase_page = $options['mt_purchase_page'];
$receipt_page = $options['mt_purchase_page'];
$tickets_page = $options['mt_tickets_page'];
if ( is_page( $purchase_page ) || is_page( $receipt_page ) || is_page( $tickets_page ) ) {
return '';
}
if ( ! $event_id ) {
return '';
}
if ( 'mc-events' === get_post_type( $event_id ) ) {
$sell = get_post_meta( $event_id, '_mt_sell_tickets', true );
if ( 'false' === $sell ) {
return '';
}
}
if ( 'true' === get_post_meta( $event_id, '_mt_hide_registration_form', true ) ) {
return '';
}
$registration = get_post_meta( $event_id, '_mt_registration_options', true );
// if no 'total' is set at all, this is not an event with tickets.
if ( empty( $registration['prices'] ) ) {
return '';
}
// if total is set to inherit, but any ticket class has no defined number of tickets available, return. '0' is a valid number of tickets, '' is not.
if ( ( isset( $registration['total'] ) && 'inherit' === $registration['total'] ) && ! mt_has_tickets( $registration['prices'] ) ) {
return '';
}
// if total number of tickets is set but is an empty string or is not set; return.
if ( ( isset( $registration['total'] ) && '' === trim( $registration['total'] ) ) || ! isset( $registration['total'] ) ) {
return '';
}
// If this event is general admission, then never show number of tickets remaining or status.
$data = get_post_meta( $event_id, '_mc_event_data', true );
if ( isset( $data['general_admission'] ) && 'on' === $data['general_admission'] ) {
return '';
}
$expired = ( mt_expired( $event_id ) ) ? __( 'Sales closed', 'my-tickets' ) : '';
$registration = get_post_meta( $event_id, '_mt_registration_options', true );
$available = $registration['total'];
$pricing = $registration['prices'];
$tickets_remaining = mt_tickets_left( $pricing, $available );
// Translators: Number of tickets remaining.
$remaining = ( 0 >= $tickets_remaining['remain'] ) ? $expired : sprintf( __( '%s tickets remaining', 'my-tickets' ), '<strong>' . $tickets_remaining['remain'] . '</strong>' );
$sold_out = ( 0 >= $tickets_remaining['remain'] ) ? __( 'Sold out', 'my-tickets' ) : $remaining;
return $sold_out;
}
/**
* Figure whether tickets should be hidden.
*
* @param int $tickets_remaining Number of tickets remaining.
*
* @return string
*/
function mt_hide_remaining( $tickets_remaining ) {
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
// If hide remaining is enabled, set as hidden.
$hide_remaining = ( isset( $options['mt_hide_remaining'] ) && 'true' === $options['mt_hide_remaining'] ) ? ' hiding' : '';
// Hide tickets if there are more than x tickets available if limit is set.
$hide_limiting = ( isset( $options['mt_hide_remaining_limit'] ) && ( $tickets_remaining > absint( $options['mt_hide_remaining_limit'] ) ) ) ? ' hiding' : '';
return ( isset( $options['mt_hide_remaining_limit'] ) ) ? $hide_limiting : $hide_remaining;
}
/**
* Test whether the current ticket type is admin-only
*
* @param string $type Type of ticket being used.
*
* @return boolean
*/
function mt_admin_only( $type ) {
if ( ( 'complementary' === $type || 'complimentary' === $type ) ) {
return true;
}
/**
* Set a ticket type as only available to administrators. Return true to hide ticket types from the public.
*
* @hook mt_admin_only_ticket
*
* @param {false} $admin_only False to indicate a ticket type is available to the public.
* @param {string} $type Ticket type key.
*
* @return {bool}
*/
return apply_filters( 'mt_admin_only_ticket', false, $type );
}
/**
* Test whether the current user can order the current ticket type.
*
* @param string $type Type of ticket being handled.
*
* @return bool
*/
function mt_can_order( $type ) {
if ( ! mt_admin_only( $type ) ) {
return true;
}
$comps = ( current_user_can( 'mt-order-comps' ) || current_user_can( 'manage_options' ) ) ? true : false;
if ( mt_admin_only( $type ) && $comps ) {
return true;
}
return false;
}
/**
* Produce notice about tickets remaining after sales are closed.
*
* @param array $tickets_data array of ticket sales data.
* @param integer $event_id Event ID.
*
* @return string Notice
*/
function mt_tickets_remaining( $tickets_data, $event_id ) {
$tickets_remaining = $tickets_data['remain'];
if ( $tickets_remaining && $tickets_remaining > apply_filters( 'mt_tickets_close_value', 0, $event_id, $tickets_data ) ) {
$tickets_remain_text = '';
} else {
if ( $tickets_remaining > 0 ) {
// Translators: number of tickets available.
$tickets_remain_text = ' ' . sprintf( apply_filters( 'mt_tickets_still_remaining_text', _n( 'Online sales are closed, but there is still %d ticket available at the box office!', 'Online sales are closed, but there are still %d tickets available at the box office!', $tickets_remaining, 'my-tickets' ) ), $tickets_remaining );
} else {
$tickets_remain_text = '';
}
}
return $tickets_remain_text;
}
add_filter( 'mt_tickets_close_value', 'mt_close_ticket_sales', 10, 3 );
/**
* Customize when events will close for ticket sales, to reserve some tickets for door sales.
*
* @param integer $limit Point where ticket sales will close. Default: 0.
* @param integer $event_id Event ID, in case somebody wanted some further customization.
* @param array $remaining remaining, sold, and total tickets available.
*
* @return integer new value where ticket sales are closed for an event.
*/
function mt_close_ticket_sales( $limit, $event_id, $remaining ) {
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
$tickets_close_at = ( isset( $options['mt_tickets_close_value'] ) && is_numeric( $options['mt_tickets_close_value'] ) ) ? $options['mt_tickets_close_value'] : 0;
$tickets_close_type = ( isset( $options['mt_tickets_close_type'] ) ) ? $options['mt_tickets_close_type'] : 'integer';
switch ( $tickets_close_type ) {
case 'integer':
$limit = $tickets_close_at;
break;
case 'percent':
$limit = round( ( $tickets_close_at / 100 ) * $remaining['total'] );
break;
}
return apply_filters( 'mt_custom_event_limit', $limit, $event_id, $remaining );
}
/**
* Produce price if a per-ticket handling charge is being applied.
*
* @param float $price Original price without handling.
* @param integer $event Event ID.
* @param string $type Public or admin ticket type.
*
* @return float new price
*/
function mt_handling_price( $price, $event, $type = 'standard' ) {
// correction for an early mipselling.
if ( mt_admin_only( $type ) ) {
return $price; // no handling on complimentary tickets.
}
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
if ( isset( $options['mt_ticket_handling'] ) && is_numeric( $options['mt_ticket_handling'] ) ) {
$price = $price + apply_filters( 'mt_ticket_handling_price', $options['mt_ticket_handling'], $event );
}
return $price;
}
/**
* Produce price if a per-ticket handling charge is being applied.
*
* @return string handling notice
*/
function mt_handling_notice() {
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
if ( isset( $options['mt_ticket_handling'] ) && is_numeric( $options['mt_ticket_handling'] ) && $options['mt_ticket_handling'] > 0 ) {
// Translators: amount of ticket handling charge.
$handling_notice = "<div class='mt-ticket-handling'>" . apply_filters( 'mt_ticket_handling_notice', sprintf( __( 'Tickets include a %s ticket handling charge.', 'my-tickets' ), apply_filters( 'mt_money_format', $options['mt_ticket_handling'] ) ) ) . '</div>';
} else {
$handling_notice = '';
}
return $handling_notice;
}
/**
* Get closing date/time for event
*
* @param integer $event_id Event ID.
* @param string $expires Expiration time.
*
* @return string
*/
function mt_sales_close( $event_id, $expires ) {
$event = get_post_meta( $event_id, '_mc_event_data', true );
if ( $event && is_array( $event ) ) {
if ( isset( $event['general_admission'] ) && 'on' === $event['general_admission'] ) {
// Don't display closing information on General Admission events.
return '';
}
if ( isset( $event['event_begin'] ) && isset( $event['event_time'] ) ) {
$expiration = $expires * 60 * 60;
$begin = strtotime( $event['event_begin'] . ' ' . $event['event_time'] ) - $expiration;
if ( mt_date( 'Y-m-d', $begin ) === mt_date( 'Y-m-d', mt_current_time() ) ) {
// Translators: time that ticket sales close today.
return '<p>' . sprintf( apply_filters( 'mt_ticket_sales_close_text', __( 'Ticket sales close at %s today', 'my-tickets' ), $event ), '<strong>' . date_i18n( get_option( 'time_format' ), $begin ) . '</strong>' ) . '</p>';
}
}
}
return '';
}
/**
* Test whether event can currently allow tickets to be shipped, given provided time frame for shipping in relation to event date/time.
*
* @param integer $event_id Event ID.
*
* @return bool
*/
function mt_no_postal( $event_id ) {
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
$shipping_time = $options['mt_shipping_time'];
$event = get_post_meta( $event_id, '_mc_event_data', true );
$no_postal = false;
if ( $event && is_array( $event ) ) {
$date = ( isset( $event['event_begin'] ) ) ? $event['event_begin'] : false;
$time = ( isset( $event['event_time'] ) ) ? $event['event_time'] : false;
if ( is_numeric( $date ) && is_numeric( $time ) ) {
$event_date = strtotime( absint( $date . ' ' . $time ) );
$no_postal = ( $event_date <= ( mt_current_time() + ( 60 * 60 * 24 * $shipping_time ) ) ) ? true : false;
return $no_postal;
}
}
return $no_postal;
}
/**
* Determine how many tickets have been sold for a given pricing set.
*
* @param array $pricing Pricing array.
* @param string|integer $available Available tickets.
*
* @return array|bool
*/
function mt_tickets_left( $pricing, $available ) {
$total = 0;
$sold = 0;
foreach ( $pricing as $options ) {
if ( 'inherit' !== $available ) {
$sold = $sold + intval( $options['sold'] );
} else {
$tickets = intval( $options['tickets'] ) - intval( $options['sold'] );
$total = $total + $tickets;
$sold = $sold + intval( $options['sold'] );
}
}
if ( 'inherit' !== $available && is_numeric( trim( $available ) ) ) {
$total = $available - $sold;
}
return array(
'remain' => $total,
'sold' => $sold,
'total' => $sold + $total,
);
}
add_action( 'init', 'mt_add_to_cart' );
/**
* Add event tickets to cart. (Non AJAX).
*
* @uses function mt_register_message.
*/
function mt_add_to_cart() {
if ( ! isset( $_REQUEST['mt_add_to_cart'] ) ) {
return;
} else {
if ( isset( $_POST['mt_add_to_cart'] ) ) {
$nonce = $_POST['_wpnonce'];
if ( ! wp_verify_nonce( $nonce, 'mt-cart-nonce' ) ) {
return;
}
}
if ( isset( $_GET['mt_add_to_cart'] ) ) {
$data = mt_get_data( 'cart' );
$event_id = intval( $_GET['event_id'] );
// todo: figure out how to set this up for mt_admin_only ticket types.
$type = ( isset( $_GET['ticket_type'] ) && ( 'complementary' !== $_GET['ticket_type'] && 'complimentary' !== $_GET['ticket_type'] ) ) ? sanitize_key( $_GET['ticket_type'] ) : false;
$count = isset( $_GET['count'] ) ? intval( $_GET['count'] ) : 1;
$options = ( $type ) ? array( $type => $count ) : false;
if ( $data ) {
$event_options = isset( $data[ $event_id ] ) && is_array( $data[ $event_id ] ) ? $data[ $event_id ] : array();
$options = array_merge( $event_options, $options );
}
} else {
$event_id = ( isset( $_POST['mt_event_id'] ) ) ? intval( $_POST['mt_event_id'] ) : false;
$options = ( isset( $_POST['mt_tickets'] ) ) ? array_map( 'absint', $_POST['mt_tickets'] ) : false;
}
$saved = ( false !== $options ) ? mt_save_data(
array(
'event_id' => $event_id,
'options' => $options,
)
) : false;
}
if ( $saved ) {
mt_register_message( 'add_to_cart', 'success' );
} else {
mt_register_message( 'add_to_cart', 'error' );
}
}
/**
* Register a message to be displayed following a cart or button action.
*
* @param string $context Current message context.
* @param string $type Message type.
* @param mixed boolean|integer $payment_id Payment ID.
*
* @return void
*/
function mt_register_message( $context, $type, $payment_id = false ) {
global $mt_message;
$mt_message = mt_get_message( $context, $type, $payment_id );
}
/**
* Fetch a message to be displayed following a cart or button action.
*
* @param string $context Current message context.
* @param string $type Message type.
* @param integer $payment_id Payment ID.
*
* @return string
*/
function mt_get_message( $context, $type, $payment_id ) {
$context = esc_attr( $context );
$options = array_merge( mt_default_settings(), get_option( 'mt_settings', array() ) );
$type = esc_attr( $type );
if ( 'add_to_cart' === $context ) {
$cart_url = get_permalink( $options['mt_purchase_page'] );
switch ( $type ) {
case 'success':
// Translators: cart URL.
$return = sprintf( __( 'Event successfully added to <a href="%s">your cart</a>.', 'my-tickets' ), $cart_url );
break;
case 'error':
$return = __( 'That event could not be added to your cart.', 'my-tickets' );
break;
case 'error-expired':
$return = __( 'Online ticket sales are no longer available for this event.', 'my-tickets' );
break;
default:
$return = '';
}
} elseif ( 'update_cart' === $context ) {
switch ( $type ) {
case 'success':
$return = __( 'Cart Updated.', 'my-tickets' );
break;
case 'error':
$return = __( 'Could not update your cart.', 'my-tickets' );
break;
case 'error-expired':
$return = __( 'The event is no longer available for online ticket sales.', 'my-tickets' );
break;
default:
$return = '';
}
} elseif ( 'payment_due' === $context ) {
switch ( $type ) {
case 'success':
if ( $payment_id ) {
$gateway = get_post_meta( $payment_id, '_gateway', true );
if ( 'offline' === $gateway ) {
wp_publish_post( $payment_id );
}
}
$return = __( 'Your ticket order has been submitted! Any payment due will be collected when you arrive at the event.', 'my-tickets' );
break;
default:
$return = '';
}
} elseif ( 'cart_submitted' === $context ) {
switch ( $type ) {
case 'success':
$return = __( 'Cart Submitted.', 'my-tickets' );
break;
case 'error':
$return = __( 'Could not submit your cart.', 'my-tickets' );
break;
default:
$return = '';
}
}
return apply_filters( 'mt_get_message_text', "<div class='$context $type mt-message'><p>$return</p></div>", $context, $type );
}
add_filter( 'the_content', 'mt_display_message' );
/**
* Get registered message and display at top of content.
*
* @param string $content Post content.
*
* @return string
*/
function mt_display_message( $content ) {
if ( isset( $_POST['my-tickets'] ) && is_main_query() ) {
global $mt_message;
return $mt_message . $content;
}
return $content;
}
/**
* Abstract function for saving user data (cookie or meta). Saves as cookie is not logged in, as user meta if is.
*
* @param array $passed Data passed to save.
* @param string $type Type of data to save.
* @param bool $override Whether to override this.
*
* @return bool
*/
function mt_save_data( $passed, $type = 'cart', $override = false ) {
$type = sanitize_title( $type );
if ( true === $override ) {
$save = $passed;
} else {
switch ( $type ) {
case 'cart':
$save = mt_get_cart();
$options = $passed['options'];
$event_id = $passed['event_id'];
$save[ $event_id ] = $options;
break;
case 'payment':
$save = $passed;
break;
default:
$save = $passed;
}
}
$current_user = wp_get_current_user();
mt_refresh_cache();
if ( is_user_logged_in() ) {
update_user_meta( $current_user->ID, "_mt_user_$type", $save );
return true;
} else {
$unique_id = ( isset( $_COOKIE['mt_unique_id'] ) ) ? sanitize_text_field( $_COOKIE['mt_unique_id'] ) : false;
if ( get_transient( 'mt_' . $unique_id . '_' . $type ) ) {
delete_transient( 'mt_' . $unique_id . '_' . $type );
}
set_transient( 'mt_' . $unique_id . '_' . $type, $save, mt_current_time() + WEEK_IN_SECONDS );
return true;
}
}
add_action( 'init', 'mt_set_user_unique_id' );
/**
* Note: if sitecookiepath doesn't match the site's render location, this won't work.
* It'll also create a secondary issue where AJAX actions read the sitecookiepath cookie.
*/
function mt_set_user_unique_id() {
if ( ! defined( 'DOING_CRON' ) ) {
$unique_id = ( isset( $_COOKIE['mt_unique_id'] ) ) ? sanitize_text_field( $_COOKIE['mt_unique_id'] ) : false;
if ( ! $unique_id ) {
$unique_id = mt_generate_unique_id();
if ( version_compare( PHP_VERSION, '7.3.0', '>' ) ) {
// Fix syntax.
$options = array(
'expires' => time() + 60 * 60 * 24 * 7,
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => false,
'httponly' => true,
'samesite' => 'Lax',
);
setcookie( 'mt_unique_id', $unique_id, $options );
} else {
setcookie( 'mt_unique_id', $unique_id, time() + 60 * 60 * 24 * 7, COOKIEPATH, COOKIE_DOMAIN, false, true );
}
}
}
}
/**
* Generate a unique ID to track the current cart process.
*
* @return string
*/
function mt_generate_unique_id() {
$length = 16;
$characters = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz-_';
$string = '';
for ( $p = 0; $p < $length; $p++ ) {
$string .= $characters[ mt_rand( 0, strlen( $characters ) - 1 ) ];
}
return $string;
}
/**
* Abstract function to retrieve data for current user/public user.
*
* @param string $type Type of data.
* @param bool|integer $user_ID User ID or false if not logged in.
*
* @return array|mixed
*/
function mt_get_data( $type, $user_ID = false ) {
if ( $user_ID ) {
$data = get_user_meta( $user_ID, "_mt_user_$type", true );
} else {
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
$data = get_user_meta( $current_user->ID, "_mt_user_$type", true );
} else {
$unique_id = ( isset( $_COOKIE['mt_unique_id'] ) ) ? sanitize_text_field( $_COOKIE['mt_unique_id'] ) : false;
if ( $unique_id ) {
$data = get_transient( 'mt_' . $unique_id . '_' . $type );
} else {
$data = '[]';
}
if ( $data ) {
if ( '' !== $data && ! is_numeric( $data ) && ! is_array( $data ) ) {
// Data could be JSON and needs to be decoded.
$decoded = json_decode( $data );
// If it was valid JSON, use the decoded value. Otherwise, use the original.
if ( JSON_ERROR_NONE === json_last_error() ) {
$data = $decoded;
}
}
} else {
$data = false;
}
}
}
return $data;
}
/**
* Update cart data. Special case application of mt_save_data.
*
* @param array $post Posted data.
*
* @return array
*/
function mt_update_cart( $post = array() ) {
$cart = mt_get_cart();
if ( ! $cart ) {
$event_id = ( isset( $post['mt_event_id'] ) ) ? $post['mt_event_id'] : false;
$options = ( isset( $post['mt_tickets'] ) ) ? $post['mt_tickets'] : false;
$cart = array(
'event_id' => $event_id,
'options' => $options,
);
$updated = mt_save_data( $cart );
} else {
foreach ( $post as $id => $item ) {
if ( is_numeric( $id ) ) {
$cart_item = isset( $cart[ $id ] ) ? $cart[ $id ] : array();
$post_item = array();
foreach ( $item as $type => $count ) {
$post_item[ $type ] = absint( $count['count'] );
}
$post_item = array_merge( $cart_item, $post_item );
if ( ! isset( $cart[ $id ] ) || ( $cart[ $id ] !== $post_item ) ) {
$cart[ $id ] = $post_item;
}
}
}
$has_contents = false;
// If any ticket type has a count, keep event in cart.
foreach ( $cart as $id => $type ) {
if ( is_array( $type ) ) {
foreach ( $type as $counted ) {
if ( 0 < (int) $counted ) {
$has_contents = true;
}
}
}
if ( ! $has_contents ) {
unset( $cart[ $id ] );
}
}
$updated = mt_save_data( $cart, 'cart', true );
}
return array(
'success' => $updated,
'cart' => $cart,
);
}
/**
* Checks whether a given event is currently represented in user's cart.
*
* @param Integer $event_id event ID.
* @param Integer $user_ID user ID.
*
* @return bool
*/
function mt_in_cart( $event_id, $user_ID = false ) {
$cart = mt_get_cart( $user_ID );
if ( isset( $cart[ $event_id ] ) ) {
return $cart[ $event_id ];
}
return false;
}