Tribe__Tickets__Tickets_Handler
Handles most actions related to a Ticket or Multiple ones
Source
File: src/Tribe/Tickets_Handler.php
class Tribe__Tickets__Tickets_Handler {
/**
* Post Meta key for the ticket header
*
* @since 4.6
*
* @var string
*/
public $key_image_header = '_tribe_ticket_header';
/**
* Post Meta key for event ecommerce provider
*
* @since 4.6
*
* @var string
*/
public $key_provider_field = '_tribe_default_ticket_provider';
/**
* Post meta key for the ticket capacty
*
* @since 4.6
*
* @var string
*/
public $key_capacity = '_tribe_ticket_capacity';
/**
* Post meta key for the ticket start date
*
* @since 4.6
*
* @var string
*/
public $key_start_date = '_ticket_start_date';
/**
* Post meta key for the ticket end date
*
* @since 4.6
*
* @var string
*/
public $key_end_date = '_ticket_end_date';
/**
* Post meta key for the manual updated meta keys
*
* @since 4.6
*
* @var string
*/
public $key_manual_updated = '_tribe_ticket_manual_updated';
/**
* Meta data key we store show_description under
*
* @since 4.6
*
* @var string
*/
public $key_show_description = '_tribe_ticket_show_description';
/**
* String to represent unlimited tickets
* translated in the constructor
*
* @since 4.6
*
* @var string
*/
public $unlimited_term = 'Unlimited';
/**
* Class constructor.
*/
public function __construct() {
$main = Tribe__Tickets__Main::instance();
$this->unlimited_term = __( 'Unlimited', 'event-tickets' );
foreach ( $main->post_types() as $post_type ) {
add_action( 'save_post_' . $post_type, array( $this, 'save_post' ) );
}
add_filter( 'get_post_metadata', array( $this, 'filter_capacity_support' ), 15, 4 );
add_filter( 'updated_postmeta', array( $this, 'update_shared_tickets_capacity' ), 15, 4 );
add_filter( 'updated_postmeta', array( $this, 'update_meta_date' ), 15, 4 );
add_action( 'wp_insert_post', array( $this, 'update_start_date' ), 15, 3 );
$this->path = trailingslashit( dirname( dirname( dirname( __FILE__ ) ) ) );
}
/**
* On updating a few meta keys we flag that it was manually updated so we can do
* fancy matching for the updating of the event start and end date
*
* @since 4.6
*
* @param int $meta_id MID
* @param int $object_id Which Post we are dealing with
* @param string $meta_key Which meta key we are fetching
* @param int $event_capacity To which value the event Capacity was update to
*
* @return int
*/
public function flag_manual_update( $meta_id, $object_id, $meta_key, $date ) {
$keys = array(
$this->key_start_date,
$this->key_end_date,
);
// Bail on not Date meta updates
if ( ! in_array( $meta_key, $keys ) ) {
return;
}
$updated = get_post_meta( $object_id, $this->key_manual_updated );
// Bail if it was ever manually updated
if ( in_array( $meta_key, $updated ) ) {
return;
}
// the updated metakey to the list
add_post_meta( $object_id, $this->key_manual_updated, $meta_key );
return;
}
/**
* Verify if we have Manual Changes for a given Meta Key
*
* @since 4.6
*
* @param int|WP_Post $ticket Which ticket/post we are dealing with here
* @param string|null $for If we are looking for one specific key or any
*
* @return boolean
*/
public function has_manual_update( $ticket, $for = null ) {
if ( ! $ticket instanceof WP_Post ) {
$ticket = get_post( $ticket );
}
if ( ! $ticket instanceof WP_Post ) {
return false;
}
$updated = get_post_meta( $ticket->ID, $this->key_manual_updated );
if ( is_null( $for ) ) {
return ! empty( $updated );
}
return in_array( $for, $updated );
}
/**
* Allow us to Toggle flaging the update of Date Meta
*
* @since 4.6
*
* @param boolean $toggle Should activate or not?
*
* @return void
*/
public function toggle_manual_update_flag( $toggle = true ) {
if ( true === (bool) $toggle ) {
add_filter( 'updated_postmeta', array( $this, 'flag_manual_update' ), 15, 4 );
} else {
remove_filter( 'updated_postmeta', array( $this, 'flag_manual_update' ), 15 );
}
}
/**
* On update of the Event End date we update the ticket end date
* if it wasn't manually updated
*
* @since 4.6
*
* @param int $meta_id MID
* @param int $object_id Which Post we are dealing with
* @param string $meta_key Which meta key we are fetching
* @param string $date Value save on the DB
*
* @return boolean
*/
public function update_meta_date( $meta_id, $object_id, $meta_key, $date ) {
$meta_map = array(
'_EventEndDate' => $this->key_end_date,
);
// Bail when it's not on the Map Meta
if ( ! isset( $meta_map[ $meta_key ] ) ) {
return false;
}
$event_types = Tribe__Tickets__Main::instance()->post_types();
$post_type = get_post_type( $object_id );
// Bail on non event like post type
if ( ! in_array( $post_type, $event_types ) ) {
return false;
}
$update_meta = $meta_map[ $meta_key ];
$tickets = $this->get_tickets_ids( $object_id );
foreach ( $tickets as $ticket ) {
// Skip tickets with manual updates to that meta
if ( $this->has_manual_update( $ticket, $update_meta ) ) {
continue;
}
update_post_meta( $ticket, $update_meta, $date );
}
return true;
}
/**
* Updates the Start date of all non-modified tickets when an Ticket supported Post is saved
*
* @since 4.6
*
* @param int $post_id Which post we are updating here
* @param WP_Post $post Object of the current post updating
* @param boolean $update If we are updating or creating a post
*
* @return boolean
*/
public function update_start_date( $post_id, $post, $update ) {
// Bail on Revision
if ( wp_is_post_revision( $post_id ) ) {
return false;
}
// Bail if the CPT doens't accept tickets
if ( ! tribe_tickets_post_type_enabled( $post->post_type ) ) {
return false;
}
$meta_key = $this->key_start_date;
$tickets = $this->get_tickets_ids( $post_id );
foreach ( $tickets as $ticket_id ) {
// Skip tickets with manual updates to that meta
if ( $this->has_manual_update( $ticket_id, $meta_key ) ) {
continue;
}
$current_date = get_post_meta( $ticket_id, $meta_key, true );
// Skip if the ticket has already a date
if ( ! empty( $current_date ) ) {
continue;
}
// 30 min
$round = 30;
if ( class_exists( 'Tribe__Events__Main' ) ) {
$round = (int) tribe( 'tec.admin.event-meta-box' )->get_timepicker_step( 'start' );
}
// Convert to seconds
$round *= MINUTE_IN_SECONDS;
$date = strtotime( $post->post_date );
$date = round( $date / $round ) * $round;
$date = date( Tribe__Date_Utils::DBDATETIMEFORMAT, $date );
update_post_meta( $ticket_id, $meta_key, $date );
}
return true;
}
/**
* Returns which possible connections an Object might have
*
* @since 4.6.2
*
* @return object
* {
* 'provider' => (mixed|null)
* 'event' => (int|null)
* 'product' => (int|null)
* 'order' => (int|string|null)
* 'order_item' => (int|null)
* }
*/
public function get_connections_template() {
// If you add any new Items here, update the Docblock
$connections = (object) array(
'provider' => null,
'event' => null,
'product' => null,
'order' => null,
'order_item' => null,
);
return $connections;
}
/**
* The simplest way to grab all the relationships from Any Ticket related objects
*
* On RSVPs Attendees and Orders are the same Post
*
* @since 4.6.2
*
* @see self::get_connections_template
*
* @param int|WP_Post $object Which object you are trying to figure out
*
* @return object
*/
public function get_object_connections( $object ) {
$connections = $this->get_connections_template();
if ( ! $object instanceof WP_Post ) {
$object = get_post( $object );
}
if ( ! $object instanceof WP_Post ) {
return $connections;
}
$provider_index = array(
'rsvp' => 'Tribe__Tickets__RSVP',
'tpp' => 'Tribe__Tickets__Commerce__PayPal__Main',
'woo' => 'Tribe__Tickets_Plus__Commerce__WooCommerce__Main',
'edd' => 'Tribe__Tickets_Plus__Commerce__EDD__Main',
);
$relationships = array(
'event' => array(
// RSVP
'_tribe_rsvp_event' => 'rsvp',
'_tribe_rsvp_for_event' => 'rsvp',
// PayPal tickets
'_tribe_tpp_event' => 'tpp',
'_tribe_tpp_for_event' => 'tpp',
// EDD
'_tribe_eddticket_event' => 'edd',
'_tribe_eddticket_for_event' => 'edd',
// Woo
'_tribe_wooticket_event' => 'woo',
'_tribe_wooticket_for_event' => 'woo',
),
'product' => array(
// RSVP
'_tribe_rsvp_product' => 'rsvp',
// PayPal tickets
'_tribe_tpp_product' => 'tpp',
// EDD
'_tribe_eddticket_product' => 'edd',
// Woo
'_tribe_wooticket_product' => 'woo',
),
'order' => array(
// RSVP
'_tribe_rsvp_order' => 'rsvp',
// PayPal tickets
'_tribe_tpp_order' => 'tpp',
// EDD
'_tribe_eddticket_order' => 'edd',
// Woo
'_tribe_wooticket_order' => 'woo',
),
'order_item' => array(
// PayPal tickets
'_tribe_tpp_order' => 'tpp',
// Woo
'_tribe_wooticket_order_item' => 'woo',
),
);
foreach ( $relationships as $what => $keys ) {
foreach ( $keys as $key => $provider ) {
// Skip any key that doens't exist
if ( ! metadata_exists( 'post', $object->ID, $key ) ) {
continue;
}
// When we don't have a provider yet we test and fetch it
if ( ! $connections->provider && isset( $provider_index[ $provider ] ) ) {
$connections->provider = $provider_index[ $provider ];
}
// Fetch it
$meta = get_post_meta( $object->ID, $key, true );
// Makes sure we have clean data
if ( empty( $meta ) ) {
$meta = null;
} elseif ( is_numeric( $meta ) ) {
$meta = (int) $meta;
}
// The meta value as a connection
$connections->{$what} = $meta;
}
}
// If we have a valid provider get it
if ( $connections->provider && class_exists( $connections->provider ) ) {
$connections->provider = call_user_func( array( $connections->provider, 'get_instance' ) );
}
return $connections;
}
/**
* Gets the Tickets from a Post
*
* @since 4.6
*
* @param int|WP_Post $post
* @return array
*/
public function get_tickets_ids( $post = null ) {
$modules = Tribe__Tickets__Tickets::modules();
$args = array(
'post_type' => array(),
'posts_per_page' => -1,
'fields' => 'ids',
'post_status' => 'publish',
'order_by' => 'menu_order',
'order' => 'ASC',
'meta_query' => array(
'relation' => 'OR',
),
);
foreach ( $modules as $provider_class => $name ) {
$provider = call_user_func( array( $provider_class, 'get_instance' ) );
$module_args = $provider->get_tickets_query_args( $post );
$args['post_type'] = array_merge( $args['post_type'], $module_args['post_type'] );
$args['meta_query'] = array_merge( $args['meta_query'], $module_args['meta_query'] );
}
$query = new WP_Query( $args );
return $query->posts;
}
/**
* On the Update of a Object (Event, Page, Post...) we need to do some other actions:
* - Object needs to have Shared Stock Enabled
* - Object needs a Shared Stock level to be set
* - Shared tickets have their capacity and stock updated
*
* @since 4.6
*
* @param int $meta_id MID
* @param int $object_id Which Post we are dealing with
* @param string $meta_key Which meta key we are fetching
* @param int $event_capacity To which value the event Capacity was update to
*
* @return boolean
*/
public function update_shared_tickets_capacity( $meta_id, $object_id, $meta_key, $event_capacity ) {
// Bail on non-capacity
if ( $this->key_capacity !== $meta_key ) {
return false;
}
$event_types = Tribe__Tickets__Main::instance()->post_types();
// Bail on non event like post type
if ( ! in_array( get_post_type( $object_id ), $event_types ) ) {
return false;
}
// We don't accept any non-numeric values here
if ( ! is_numeric( $event_capacity ) ) {
return false;
}
// Make sure we are updating the Shared Stock when we update it's capacity
$object_stock = new Tribe__Tickets__Global_Stock( $object_id );
// Make sure that we have stock enabled (backwards compatibility)
$object_stock->enable();
$completes = array();
// Get all Tickets
$tickets = $this->get_tickets_ids( $object_id );
foreach ( $tickets as $ticket ) {
$mode = get_post_meta( $ticket, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE, true );
// Skip any tickets that are not Shared
if (
Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE !== $mode
&& Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE !== $mode
) {
continue;
}
$capacity = tribe_tickets_get_capacity( $ticket );
// When Global Capacity is higher than local ticket one's we bail
if (
Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $mode
) {
$capped_capacity = $capacity;
if ( $event_capacity < $capacity ) {
$capped_capacity = $event_capacity;
}
// Otherwise we update tickets required
tribe_tickets_update_capacity( $ticket, $capped_capacity );
}
$totals = $this->get_ticket_totals( $ticket );
$completes[] = $complete = $totals['pending'] + $totals['sold'];
$stock = $event_capacity - $complete;
update_post_meta( $ticket, '_stock', $stock );
// Makes sure we mark it as in Stock for the status
if ( 0 !== $stock ) {
update_post_meta( $ticket, '_stock_status', 'instock' );
}
}
// Setup the Stock level
$new_object_stock = $event_capacity - array_sum( $completes );
$object_stock->set_stock_level( $new_object_stock );
return true;
}
/**
* Allows us to create capacity when none is defined for an older ticket
* It will define the new Capacity based on Stock + Tickets Pending + Tickets Sold
*
* Important to note that we cannot use `get_ticket()` or `new Ticket_Object` in here
* due to triggering of a Infinite loop
*
* @since 4.6
*
* @param mixed $value Previous value set
* @param int $object_id Which Post we are dealing with
* @param string $meta_key Which meta key we are fetching
*
* @return int
*/
public function filter_capacity_support( $value, $object_id, $meta_key, $single = true ) {
// Something has been already set
if ( ! is_null( $value ) ) {
return $value;
}
// We only care about Capacity Key
if ( $this->key_capacity !== $meta_key ) {
return $value;
}
// We remove the Check to allow a fair usage of `metadata_exists`
remove_filter( 'get_post_metadata', array( $this, 'filter_capacity_support' ), 15 );
// Bail when we already have the MetaKey saved
if ( metadata_exists( 'post', $object_id, $meta_key ) ) {
return get_post_meta( $object_id, $meta_key, $single );
}
// Do the migration
$capacity = $this->migrate_object_capacity( $object_id );
// Hook it back up
add_filter( 'get_post_metadata', array( $this, 'filter_capacity_support' ), 15, 4 );
// This prevents get_post_meta without single param to break
if ( ! $single ) {
$capacity = (array) $capacity;
}
return $capacity;
}
/**
* Migrates a given Post Object capacity from Legacy Version
*
* @since 4.6
*
* @param int|WP_Post $object Which Post ID
*
* @return bool|int
*/
public function migrate_object_capacity( $object ) {
if ( ! $object instanceof WP_Post ) {
$object = get_post( $object );
}
if ( ! $object instanceof WP_Post ) {
return false;
}
// Bail when we don't have a legacy version
if ( ! tribe( 'tickets.version' )->is_legacy( $object->ID ) ) {
return false;
}
// Defaults to null
$capacity = null;
if ( tribe_tickets_post_type_enabled( $object->post_type ) ) {
$event_stock_obj = new Tribe__Tickets__Global_Stock( $object->ID );
// Fetches the Current Stock Level
$capacity = $event_stock_obj->get_stock_level();
$tickets = $this->get_tickets_ids( $object->ID );
foreach ( $tickets as $ticket ) {
// Indy tickets don't get added to the Event
if ( ! $this->has_shared_capacity( $ticket ) ) {
continue;
}
$totals = $this->get_ticket_totals( $ticket );
$capacity += $totals['sold'] + $totals['pending'];
}
} else {
// In here we deal with Tickets migration from legacy
$mode = get_post_meta( $object->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE, true );
$totals = $this->get_ticket_totals( $object->ID );
$connections = $this->get_object_connections( $object );
// When migrating we might get Tickets/RSVP without a mode so we set it to Indy Ticket
if ( ! metadata_exists( 'post', $object->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE ) ) {
$mode = 'own';
}
if ( Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $mode ) {
$capacity = (int) trim( get_post_meta( $object->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_CAP, true ) );
$capacity += $totals['sold'] + $totals['pending'];
} elseif ( Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE === $mode ) {
// When using Global we don't set a ticket cap
$capacity = null;
} elseif ( Tribe__Tickets__Global_Stock::OWN_STOCK_MODE === $mode ) {
/**
* Due to a bug in version 4.5.6 of our code RSVP doesnt lower the Stock
* so when setting up the capacity we need to avoid counting solds
*/
if (
is_object( $connections->provider )
&& 'Tribe__Tickets__RSVP' === get_class( $connections->provider )
) {
$capacity = $totals['stock'];
} else {
$capacity = array_sum( $totals );
}
} else {
$capacity = -1;
}
// Fetch ticket event ID for Updating capacity on event
$event_id = tribe_events_get_ticket_event( $object->ID );
// Apply to the Event
if ( ! empty( $event_id ) ) {
$this->migrate_object_capacity( $event_id );
}
}
// Bail when we didn't have a capacity
if ( is_null( $capacity ) ) {
// Also still update the version, so we don't hit this method all the time
tribe( 'tickets.version' )->update( $object->ID );
return false;
}
$updated = update_post_meta( $object->ID, $this->key_capacity, $capacity );
// If we updated the Capacity for legacy update the version
if ( $updated ) {
tribe( 'tickets.version' )->update( $object->ID );
}
return $capacity;
}
/**
* Gets the Total of Stock, Sold and Pending for a given ticket
*
* @since 4.6
*
* @param int|WP_Post $ticket Which ticket
*
* @return array
*/
public function get_ticket_totals( $ticket ) {
if ( ! $ticket instanceof WP_Post ) {
$ticket = get_post( $ticket );
}
if ( ! $ticket instanceof WP_Post ) {
return false;
}
$provider = tribe_tickets_get_ticket_provider( $ticket->ID );
$totals = array(
'stock' => get_post_meta( $ticket->ID, '_stock', true ),
'sold' => 0,
'pending' => 0,
);
if ( $provider instanceof Tribe__Tickets_Plus__Commerce__EDD__Main ) {
$totals['sold'] = $provider->stock()->get_purchased_inventory( $ticket->ID, array( 'publish' ) );
$totals['pending'] = $provider->stock()->count_incomplete_order_items( $ticket->ID );
} elseif ( $provider instanceof Tribe__Tickets_Plus__Commerce__WooCommerce__Main ) {
$totals['sold'] = get_post_meta( $ticket->ID, 'total_sales', true );
$totals['pending'] = $provider->get_qty_pending( $ticket->ID, true );
} else {
$totals['sold'] = get_post_meta( $ticket->ID, 'total_sales', true );
}
$totals = array_map( 'intval', $totals );
// Remove Pending from total
$totals['sold'] -= $totals['pending'];
return $totals;
}
/**
* Gets the Total of Stock, Sold and Pending for a given Post
* And if there is any Unlimited
*
* @since 4.6.2
*
* @param int|WP_Post $post Which ticket
*
* @return array
*/
public function get_post_totals( $post ) {
if ( ! $post instanceof WP_Post ) {
$post = get_post( $post );
}
if ( ! $post instanceof WP_Post ) {
return false;
}
$tickets = Tribe__Tickets__Tickets::get_all_event_tickets( $post->ID );
$totals = array(
'has_unlimited' => false,
'tickets' => count( $tickets ),
'capacity' => $this->get_total_event_capacity( $post ),
'sold' => 0,
'pending' => 0,
'stock' => 0,
);
foreach ( $tickets as $ticket ) {
$ticket_totals = $this->get_ticket_totals( $ticket->ID );
$totals['sold'] += $ticket_totals['sold'];
$totals['pending'] += $ticket_totals['pending'];
$totals['stock'] += $ticket_totals['stock'];
// check if we have any unlimited tickets
if ( ! $totals['has_unlimited'] ) {
$totals['has_unlimited'] = -1 === tribe_tickets_get_capacity( $ticket->ID );
}
}
return $totals;
}
/**
* Returns whether a ticket has unlimited capacity
*
* @since 4.6
*
* @param int|WP_Post|object $ticket
*
* @return bool
*/
public function is_ticket_managing_stock( $ticket ) {
if ( ! $ticket instanceof WP_Post ) {
$ticket = get_post( $ticket );
}
if ( ! $ticket instanceof WP_Post ) {
return false;
}
// Defaults to managing Stock so we don't have Unlimited
$manage_stock = true;
// If it exists we use it
if ( metadata_exists( 'post', $ticket->ID, '_manage_stock' ) ) {
$manage_stock = get_post_meta( $ticket->ID, '_manage_stock', true );
}
return tribe_is_truthy( $manage_stock );
}
/**
* Returns whether a ticket has unlimited capacity
*
* @since 4.6
*
* @param int|WP_Post|object $ticket
*
* @return bool
*/
public function is_unlimited_ticket( $ticket ) {
return -1 === tribe_tickets_get_capacity( $ticket->ID );
}
/**
* Returns whether a ticket uses Shared Capacity
*
* @since 4.6
*
* @param int|WP_Post|object $ticket
*
* @return bool
*/
public function has_shared_capacity( $ticket ) {
if ( ! $ticket instanceof WP_Post ) {
$ticket = get_post( $ticket );
}
if ( ! $ticket instanceof WP_Post ) {
return false;
}
$mode = get_post_meta( $ticket->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE, true );
return Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $mode || Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE === $mode;
}
/**
* Returns whether a given object has the correct Provider for a Post or Ticket
*
* @since 4.6.2
*
* @param int|WP_Post $ticket
* @param mixed $provider
*
* @return bool
*/
public function is_correct_provider( $post, $provider ) {
if ( ! $post instanceof WP_Post ) {
$post = get_post( $post );
}
if ( ! $post instanceof WP_Post ) {
return false;
}
$provider_class = get_class( $provider );
if ( tribe_tickets_post_type_enabled( $post->post_type ) ) {
$default_provider = Tribe__Tickets__Tickets::get_event_ticket_provider( $post->ID );
} else {
$default_provider = tribe_tickets_get_ticket_provider( $post->ID );
}
if ( ! $default_provider ) {
$default_provider = class_exists( 'Tribe__Tickets_Plus__Main' )
? 'Tribe__Tickets_Plus__Commerce__WooCommerce__Main'
: 'Tribe__Tickets__RSVP';
}
if ( ! is_string( $default_provider ) ) {
$default_provider = get_class( $default_provider );
}
return $default_provider === $provider_class;
}
/**
* Checks if there are any unlimited tickets, optionally by stock mode or ticket type
*
* @since 4.6
*
* @param int|object (null) $post Post or Post ID tickets are attached to
* @param string (null) the stock mode we're concerned with
* can be one of the following:
* Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE ('global')
* Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE ('capped')
* Tribe__Tickets__Global_Stock::OWN_STOCK_MODE ('own')
* @param string (null) $provider_class the ticket provider class ex: Tribe__Tickets__RSVP
* @return boolean whether there is a ticket (within the provided parameters) with an unlimited stock
*/
public function has_unlimited_stock( $post = null, $stock_mode = null, $provider_class = null ) {
$post_id = Tribe__Main::post_id_helper( $post );
$tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
foreach ( $tickets as $index => $ticket ) {
// Eliminate tickets by stock mode
if ( ! is_null( $stock_mode ) && $ticket->global_stock_mode() !== $stock_mode ) {
unset( $tickets[ $ticket ] );
continue;
}
// Eliminate tickets by provider class
if ( ! is_null( $provider_class ) && $ticket->provider_class !== $provider_class ) {
unset( $tickets[ $ticket ] );
continue;
}
if ( $this->is_unlimited_ticket( $ticket ) ) {
return true;
}
}
return false;
}
/**
* Get the total event capacity.
*
* @since 4.6
*
* @param int|object (null) $post Post or Post ID tickets are attached to
*
* @return int|null
*/
public function get_total_event_capacity( $post = null ) {
$post_id = Tribe__Main::post_id_helper( $post );
$has_shared_tickets = 0 !== count( $this->get_event_shared_tickets( $post_id ) );
$total = 0;
if ( $has_shared_tickets ) {
$total = tribe_tickets_get_capacity( $post_id );
}
// short circuit unlimited stock
if ( -1 === $total ) {
return $total;
}
$tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
// Bail when we don't have Tickets
if ( empty( $tickets ) ) {
return $total;
}
foreach ( $tickets as $ticket ) {
// Skip shared cap Tickets as it's added when we fetch the total
if (
Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $ticket->global_stock_mode()
|| Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE === $ticket->global_stock_mode()
) {
continue;
}
$capacity = $ticket->capacity();
if ( -1 === $capacity || '' === $capacity ) {
$total = -1;
break;
}
$capacity = is_numeric( $capacity ) ? (int) $capacity : 0;
$total += $capacity;
}
return apply_filters( 'tribe_tickets_total_event_capacity', $total, $post_id );
}
/**
* Get an array list of unlimited tickets for an event.
*
* @since 4.6
*
* @param int|object (null) $post Post or Post ID tickets are attached to
*
* @return array list of tickets
*/
public function get_event_unlimited_tickets( $post = null ) {
$post_id = Tribe__Main::post_id_helper( $post );
$tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
$ticket_list = array();
if ( empty( $tickets ) ) {
return $ticket_list;
}
foreach ( $tickets as $ticket ) {
if ( ! $this->is_unlimited_ticket( $ticket ) ) {
continue;
}
$ticket_list[] = $ticket;
}
return $ticket_list;
}
/**
* Get an array list of independent tickets for an event.
*
* @since 4.6
*
* @param int|object (null) $post Post or Post ID tickets are attached to
*
* @return array list of tickets
*/
public function get_event_independent_tickets( $post = null ) {
$post_id = Tribe__Main::post_id_helper( $post );
$tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
$ticket_list = array();
if ( empty( $tickets ) ) {
return $ticket_list;
}
foreach ( $tickets as $ticket ) {
if ( Tribe__Tickets__Global_Stock::OWN_STOCK_MODE != $ticket->global_stock_mode() || 'Tribe__Tickets__RSVP' === $ticket->provider_class ) {
continue;
}
// Failsafe - should not include unlimited tickets
if ( $this->is_unlimited_ticket( $ticket ) ) {
continue;
}
$ticket_list[] = $ticket;
}
return $ticket_list;
}
/**
* Get an array list of RSVPs for an event.
*
* @since 4.6
*
* @param int|object (null) $post Post or Post ID tickets are attached to
*
* @return string list of tickets
*/
public function get_event_rsvp_tickets( $post = null ) {
$post_id = Tribe__Main::post_id_helper( $post );
$tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
$ticket_list = array();
if ( empty( $tickets ) ) {
return $ticket_list;
}
foreach ( $tickets as $ticket ) {
if ( 'Tribe__Tickets__RSVP' !== $ticket->provider_class ) {
continue;
}
$ticket_list[] = $ticket;
}
return $ticket_list;
}
/**
* Gets the Maximum Purchase number for a given ticket
*
* @since 4.8.1
*
* @param int|string $ticket_id Ticket to fetch purchase max from
*
* @return int
*/
public function get_ticket_max_purchase( $ticket_id ) {
$event_id = tribe_events_get_ticket_event( $ticket_id );
$provider = tribe_tickets_get_ticket_provider( $ticket_id );
$ticket = $provider->get_ticket( $event_id, $ticket_id );
$available = $ticket->available();
/**
* Allows filtering of the max input for purchase of this one ticket
*
* @since 4.8.1
*
* @param int $available Max Purchase number
* @param Tribe__Tickets__Ticket_Object $ticket Ticket Object
* @param int $event_id Event ID
* @param int $ticket_id Ticket Raw ID
*
*/
return apply_filters( 'tribe_tickets_get_ticket_max_purchase', $available, $ticket, $event_id, $ticket_id );
}
/**
* Get an array list of shared capacity tickets for an event.
*
* @since 4.6
*
* @param int|object (null) $post Post or Post ID tickets are attached to
*
* @return array list of tickets
*/
public function get_event_shared_tickets( $post = null ) {
$post_id = Tribe__Main::post_id_helper( $post );
$tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
$ticket_list = array();
if ( empty( $tickets ) ) {
return $ticket_list;
}
foreach ( $tickets as $ticket ) {
$stock_mode = $ticket->global_stock_mode();
if ( empty( $stock_mode ) || Tribe__Tickets__Global_Stock::OWN_STOCK_MODE === $stock_mode ) {
continue;
}
// Failsafe - should not include unlimited tickets
if ( $this->is_unlimited_ticket( $ticket ) ) {
continue;
}
$ticket_list[] = $ticket;
}
return $ticket_list;
}
/**
* Gets the Default mode in which tickets will be generated
*
* @since 4.6.2
*
* @return string
*/
public function get_default_capacity_mode() {
/**
* Filter Default Ticket Capacity Type
*
* @since 4.6
*
* @param string 'global'
*
* @return string ('global','capped','own','')
*
*/
return apply_filters( 'tribe_tickets_default_ticket_capacity_type', 'global' );
}
/**
* Saves the Ticket Editor related tickets on Save of the Parent Post
*
* Due to how we can have multiple Post Types where we can attach tickets we have one place where
* all panels will save, because `save_post_$post_type` requires a loop
*
* @since 4.6.2
*
* @param int $post Post that will be saved
*
* @return string
*/
public function save_post( $post ) {
// We're calling this during post save, so the save nonce has already been checked.
// don't do anything on autosave, auto-draft, or massupdates
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
return false;
}
if ( ! $post instanceof WP_Post ) {
$post = get_post( $post );
}
// Bail on Invalid post
if ( ! $post instanceof WP_Post ) {
return false;
}
$this->save_form_settings( $post );
$this->save_order( $post );
/**
* Allows us to Run any actions related to a Post that has Tickets
*
* @since 4.6.2
*
* @param WP_Post $post Which post we are saving
*/
do_action( 'tribe_tickets_save_post', $post );
}
/**
* Saves the Ticket Editor settings form
*
* @since 4.6.2
*
* @param int $post Post that will be saved
* @param array $data Params that will be used to save
*
* @return string
*/
public function save_form_settings( $post, $data = null ) {
// don't do anything on autosave, auto-draft, or massupdates
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
return false;
}
if ( ! $post instanceof WP_Post ) {
$post = get_post( $post );
}
// Bail on Invalid post
if ( ! $post instanceof WP_Post ) {
return false;
}
// If we didn't get any Ticket data we fetch from the $_POST
if ( is_null( $data ) ) {
$data = tribe_get_request_var( array( 'tribe-tickets', 'settings' ), null );
}
if ( empty( $data ) ) {
return false;
}
/**
* Allow other plugins to hook into this to add settings
*
* @since 4.6
*
* @param array $data the array of parameters to filter
*/
do_action( 'tribe_events_save_tickets_settings', $data );
if ( isset( $data['event_capacity'] ) && is_numeric( $data['event_capacity'] ) ) {
tribe_tickets_update_capacity( $post, $data['event_capacity'] );
}
if ( ! empty( $data['header_image_id'] ) ) {
update_post_meta( $post->ID, $this->key_image_header, $data['header_image_id'] );
} else {
delete_post_meta( $post->ID, $this->key_image_header );
}
// We reversed this logic on the back end
if ( class_exists( 'Tribe__Tickets_Plus__Attendees_List' ) ) {
update_post_meta( $post->ID, Tribe__Tickets_Plus__Attendees_List::HIDE_META_KEY, ! empty( $data['show_attendees'] ) );
}
// Change the default ticket provider
if ( ! empty( $data['default_provider'] ) ) {
update_post_meta( $post->ID, $this->key_provider_field, $data['default_provider'] );
} else {
delete_post_meta( $post->ID, $this->key_provider_field );
}
}
/**
* Save the the drag-n-drop ticket order
*
* @since 4.6
*
* @param int $post
*
*/
public function save_order( $post, $tickets = null ) {
// We're calling this during post save, so the save nonce has already been checked.
// don't do anything on autosave, auto-draft, or massupdates
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
return false;
}
if ( ! $post instanceof WP_Post ) {
$post = get_post( $post );
}
// Bail on Invalid post
if ( ! $post instanceof WP_Post ) {
return false;
}
// If we didn't get any Ticket data we fetch from the $_POST
if ( is_null( $tickets ) ) {
$tickets = tribe_get_request_var( array( 'tribe-tickets', 'list' ), null );
}
if ( empty( $tickets ) ) {
return false;
}
foreach ( $tickets as $id => $ticket ) {
if ( ! isset( $ticket['order'] ) ) {
continue;
}
$args = array(
'ID' => absint( $id ),
'menu_order' => (int) $ticket['order'],
);
$updated[] = wp_update_post( $args );
}
// Verify if any failed
return ! in_array( 0, $updated );
}
/**
* Sorts tickets according to stored menu_order
*
* @since 4.6
*
* @param object $a First Compare item
* @param object $b Second Compare item
*
* @return array
*/
protected function sort_by_menu_order( $a, $b ) {
return $a->menu_order - $b->menu_order;
}
/**
* Sorts tickets according to stored menu_order
*
* @since 4.6
*
* @param array $tickets array of ticket objects
*
* @return array - sorted array of ticket objects
*/
public function sort_tickets_by_menu_order( $tickets ) {
foreach ( $tickets as $key => $ticket ) {
// make sure they are ordered correctly
$orderpost = get_post( $ticket->ID );
$ticket->menu_order = $orderpost->menu_order;
}
usort( $tickets, array( $this, 'sort_by_menu_order' ) );
return $tickets;
}
/**
* Static Singleton Factory Method
*
* @return Tribe__Tickets__Tickets_Handler
*/
public static function instance() {
return tribe( 'tickets.handler' );
}
/************************
* *
* Deprecated Methods *
* *
************************/
// @codingStandardsIgnoreStart
/**
* Slug of the admin page for attendees
*
* @deprecated 4.6.2
*
* @var string
*/
public static $attendees_slug = 'tickets-attendees';
/**
* Save or delete the image header for tickets on an event
*
* @deprecated 4.6.2
*
* @param int $post_id
*/
public function save_image_header( $post_id ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.handler' )->save_settings()" );
}
/**
* Saves the event ticket settings via ajax
*
* @deprecated 4.6.2
*
* @since 4.6
*/
public function ajax_handler_save_settings() {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.metabox' )->ajax_settings()" );
return tribe( 'tickets.metabox' )->ajax_settings();
}
/**
* Includes the tickets metabox inside the Event edit screen
*
* @deprecated 4.6.2
*
* @param WP_Post $post
*
* @return string
*/
public function do_meta_box( $post ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.metabox' )->render( \$post )" );
return tribe( 'tickets.metabox' )->render( $post );
}
/**
* Returns the attachment ID for the header image for a event.
*
* @deprecated 4.6.2
*
* @param $event_id
*
* @return mixed
*/
public function get_header_image_id( $event_id ) {
_deprecated_function( __METHOD__, '4.6.2', "get_post_meta( \$event_id, tribe( 'tickets.handler' )->key_image_header, true );" );
return get_post_meta( $event_id, tribe( 'tickets.handler' )->key_image_header, true );
}
/**
* Render the ticket row into the ticket table
*
* @deprecated 4.6.2
*
* @since 4.6
*
* @param Tribe__Tickets__Ticket_Object $ticket
*/
public function render_ticket_row( $ticket ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.admin.views' )->template( array( 'editor', 'ticket-row' ) )" );
tribe( 'tickets.admin.views' )->template( array( 'editor', 'list-row' ), array( 'ticket' => $ticket ) );
}
/**
* Returns the markup for the History for a Given Ticket
*
* @deprecated 4.6.2
*
* @param int $ticket_id
*
* @return string
*/
public function get_history_content( $post_id, $ticket ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.admin.views' )->template( 'settings_admin_panel' )" );
return tribe( 'tickets.admin.views' )->template( 'tickets-history', array( 'post_id' => $post_id, 'ticket' => $ticket ), false );
}
/**
* Returns the markup for the Settings Panel for Tickets
*
* @deprecated 4.6.2
*
* @param int $post_id
*
* @return string
*/
public function get_settings_panel( $post_id ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.admin.views' )->template( 'settings_admin_panel' )" );
return tribe( 'tickets.admin.views' )->template( 'settings_admin_panel', array( 'post_id' => $post_id ), false );
}
/**
* Echoes the markup for the tickets list in the tickets metabox
*
* @deprecated 4.6.2
*
* @param int $deprecated event ID
* @param array $tickets
*/
public function ticket_list_markup( $deprecated, $tickets = array() ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.admin.views' )->template( 'list' )" );
tribe( 'tickets.admin.views' )->template( 'list', array( 'tickets' => $tickets ) );
}
/**
* Returns the markup for the tickets list in the tickets metabox
*
* @deprecated 4.6.2
*
* @param array $tickets
*
* @return string
*/
public function get_ticket_list_markup( $tickets = array() ) {
_deprecated_function( __METHOD__, '4.6.2', "tribe( 'tickets.admin.views' )->template( 'list' )" );
return tribe( 'tickets.admin.views' )->template( 'list', array( 'tickets' => $tickets ), false );
}
/**
* Whether the ticket handler should render the title in the attendees report.
*
* @deprecated 4.6.2
*
* @param bool $should_render_title
*/
public function should_render_title( $deprecated ) {
_deprecated_function( __METHOD__, '4.6.2', 'add_filter( \'tribe_tickets_attendees_show_title\', \'_return_false\' );' );
return true;
}
/**
* Returns the current post being handled.
*
* @deprecated 4.6.2
*
* @return array|bool|null|WP_Post
*/
public function get_post() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::get_post' );
return tribe( 'tickets.attendees' )->get_post();
}
/**
* Print Check In Totals at top of Column
*
* @deprecated 4.6.2
*
*/
public function print_checkedin_totals() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::print_checkedin_totals' );
tribe( 'tickets.attendees' )->print_checkedin_totals();
}
/**
* Returns the full URL to the attendees report page.
*
* @deprecated 4.6.2
*
* @param WP_Post $post
*
* @return string
*/
public function get_attendee_report_link( $post ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::get_report_link' );
return tribe( 'tickets.attendees' )->get_report_link( $post );
}
/**
* Adds the "attendees" link in the admin list row actions for each event.
*
* @deprecated 4.6.2
*
* @param $actions
*
* @return array
*/
public function attendees_row_action( $actions ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::filter_admin_row_actions' );
return tribe( 'tickets.attendees' )->filter_admin_row_actions( $actions );
}
/**
* Registers the Attendees admin page
*/
public function attendees_page_register() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::register_page' );
tribe( 'tickets.attendees' )->register_page();
}
/**
* Enqueues the JS and CSS for the attendees page in the admin
*
* @deprecated 4.6.2
*
* @param $hook
*/
public function attendees_page_load_css_js( $hook ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::enqueue_assets' );
tribe( 'tickets.attendees' )->enqueue_assets( $hook );
}
/**
* Loads the WP-Pointer for the Attendees screen
*
* @deprecated 4.6.2
*
* @param $hook
*/
public function attendees_page_load_pointers( $hook ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::load_pointers' );
tribe( 'tickets.attendees' )->load_pointers( $hook );
}
/**
* Sets up the Attendees screen data.
*
* @deprecated 4.6.2
*
*/
public function attendees_page_screen_setup() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::screen_setup' );
tribe( 'tickets.attendees' )->screen_setup();
}
/**
* @deprecated 4.6.2
*/
public function attendees_admin_body_class( $body_classes ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::filter_admin_body_class' );
tribe( 'tickets.attendees' )->filter_admin_body_class( $body_classes );
}
/**
* Sets the browser title for the Attendees admin page.
* Uses the event title.
*
* @deprecated 4.6.2
*
* @param $admin_title
* @param $unused_title
*
* @return string
*/
public function attendees_admin_title( $admin_title, $unused_title ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::filter_admin_title' );
tribe( 'tickets.attendees' )->filter_admin_title( $admin_title, $unused_title );
}
/**
* Renders the Attendees page
*
* @deprecated 4.6.2
*/
public function attendees_page_inside() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::render' );
tribe( 'tickets.attendees' )->render();
}
/**
* Generates a list of attendees taking into account the Screen Options.
* It's used both for the Email functionality, as for the CSV export.
*
* @deprecated 4.6.2
*
* @param $event_id
*
* @return array
*/
private function generate_filtered_attendees_list( $event_id ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::generate_filtered_list' );
tribe( 'tickets.attendees' )->generate_filtered_list( $event_id );
}
/**
* Checks if the user requested a CSV export from the attendees list.
* If so, generates the download and finishes the execution.
*
* @deprecated 4.6.2
*/
public function maybe_generate_attendees_csv() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::maybe_generate_csv' );
tribe( 'tickets.attendees' )->maybe_generate_csv();
}
/**
* Handles the "send to email" action for the attendees list.
*
* @deprecated 4.6.2
*/
public function send_attendee_mail_list() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::send_mail_list' );
tribe( 'tickets.attendees' )->send_mail_list();
}
/**
* Injects event post type
*
* @deprecated 4.6.2
*/
public function event_details_top() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::event_details_top' );
tribe( 'tickets.attendees' )->event_details_top();
}
/**
* Injects action links into the attendee screen.
*
* @deprecated 4.6.2
*/
public function event_action_links() {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::event_action_links' );
tribe( 'tickets.attendees' )->event_action_links();
}
/**
* Sets the content type for the attendees to email functionality.
* Allows for sending an HTML email.
*
* @deprecated 4.6.2
*
* @param $content_type
*
* @return string
*/
public function set_contenttype( $content_type ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::set_contenttype' );
}
/**
* Tests if the user has the specified capability in relation to whatever post type
* the ticket relates to.
*
* For example, if tickets are created for the banana post type, the generic capability
* "edit_posts" will be mapped to "edit_bananas" or whatever is appropriate.
*
* @deprecated 4.6.2
*
* @internal for internal plugin use only (in spite of having public visibility)
*
* @param string $generic_cap
* @param int $event_id
* @return boolean
*/
public function user_can( $generic_cap, $event_id ) {
_deprecated_function( __METHOD__, '4.6.2', 'Tribe__Tickets__Attendees::user_can' );
}
// @codingStandardsIgnoreEnd
}
Methods
- __construct — Class constructor.
- add_hooks — Add hooks for saving/meta.
- attendees_page_register — Registers the Attendees admin page
- filter_capacity_support — Allows us to create capacity when none is defined for an older ticket It will define the new Capacity based on Stock + Tickets Pending + Tickets Sold
- flag_manual_update — On updating a few meta keys we flag that it was manually updated so we can do fancy matching for the updating of the event start and end date
- get_connections_template — Returns which possible connections an Object might have
- get_default_capacity_mode — Gets the Default mode in which tickets will be generated
- get_event_independent_tickets — Get an array list of independent tickets for an event.
- get_event_rsvp_tickets — Get an array list of RSVPs for an event.
- get_event_shared_tickets — Get an array list of shared capacity tickets for an event.
- get_event_unlimited_tickets — Get an array list of unlimited tickets for an event.
- get_object_connections — The simplest way to grab all the relationships from Any Ticket related objects
- get_post_totals — Gets the Total of Stock, Sold and Pending for a given Post And if there is any Unlimited
- get_ticket_max_purchase — Gets the maximum quantity able to be purchased in a single Add to Cart action for a given ticket.
- get_ticket_totals — Gets the Total of Stock, Sold and Pending for a given ticket
- get_tickets_ids — Gets the Tickets from a Post
- get_total_event_capacity — Get the total event capacity.
- has_manual_update — Verify if we have Manual Changes for a given Meta Key
- has_shared_capacity — Returns whether a ticket uses Shared Capacity
- has_unlimited_stock — Checks if there are any unlimited tickets, optionally by stock mode or ticket type
- instance — Static Singleton Factory Method
- is_correct_provider — Returns whether a given object has the correct Provider for a Post or Ticket
- is_ticket_managing_stock — Returns whether a ticket has unlimited capacity
- is_ticket_readable — Determine whether the ticket is accessible to the current user.
- is_unlimited_ticket — Returns whether a ticket has unlimited capacity
- maybe_disable_email_resend — Maybe disable the email resend if the attendee has reached their max limit.
- maybe_increase_global_stock_data — Increment the global stock data for an Event if Shared stock is available.
- migrate_object_capacity — Migrates a given Post Object capacity from Legacy Version
- remove_hooks — Remove hooks for saving/meta.
- save_form_settings — Saves the Ticket Editor settings form
- save_order — Save the the drag-n-drop ticket order
- save_post — Saves the Ticket Editor related tickets on Save of the Parent Post
- sort_tickets_by_menu_order — Sorts tickets according to stored menu_order
- sync_shared_capacity — Sync shared capacity for the given Post/Event object.
- toggle_manual_update_flag — Allow us to Toggle flagging the update of Date Meta
- trigger_shared_cap_sync — Trigger shared cap sync on ticket updates.
- update_meta_date — On update of the event start date we update the ticket end date if it wasn't manually updated
- update_shared_tickets_capacity — On the Update of a Object (Event, Page, Post.
- update_start_date — Updates the Start date of all non-modified tickets when an Ticket supported Post is saved