Tribe__Tickets__Commerce__PayPal__Main
Logic for tribe commerce PayPal tickets
Source
File: src/Tribe/Commerce/PayPal/Main.php
class Tribe__Tickets__Commerce__PayPal__Main extends Tribe__Tickets__Tickets {
/**
* {@inheritdoc}
*/
public $orm_provider = 'tribe-commerce';
/**
* Name of the CPT that holds Attendees (tickets holders).
*
* @var string
*/
const ATTENDEE_OBJECT = 'tribe_tpp_attendees';
/**
* Name of the CPT that holds Orders
*/
const ORDER_OBJECT = 'tribe_tpp_orders';
/**
* Meta key that relates Attendees and Events.
*
* @var string
*/
const ATTENDEE_EVENT_KEY = '_tribe_tpp_event';
/**
* Meta key that relates Attendees and Products.
*
* @var string
*/
const ATTENDEE_PRODUCT_KEY = '_tribe_tpp_product';
/**
* Meta key that relates Attendees and Orders.
*
* @var string
*/
const ATTENDEE_ORDER_KEY = '_tribe_tpp_order';
/**
* Indicates if a ticket for this attendee was sent out via email.
*
* @var boolean
*/
public $attendee_ticket_sent = '_tribe_tpp_attendee_ticket_sent';
/**
* Meta key that if this attendee wants to show on the attendee list
*
* @var string
*/
public $attendee_optout_key = '_tribe_tpp_attendee_optout';
/**
* Meta key that if this attendee PayPal status
*
* @var string
*/
public $attendee_tpp_key = '_tribe_tpp_status';
/**
*Name of the CPT that holds Tickets
*
* @var string
*/
public $ticket_object = 'tribe_tpp_tickets';
/**
* Meta key that relates Products and Events
* @var string
*/
public $event_key = '_tribe_tpp_for_event';
/**
* Meta key that stores if an attendee has checked in to an event
* @var string
*/
public $checkin_key = '_tribe_tpp_checkedin';
/**
* Meta key that ties attendees together by order
* @var string
*/
public $order_key = '_tribe_tpp_order';
/**
* Meta key that ties attendees together by refunded order
* @var string
*/
public $refund_order_key = '_tribe_tpp_refund_order';
/**
* Meta key that holds the security code that's printed in the tickets
* @var string
*/
public $security_code = '_tribe_tpp_security_code';
/**
* Meta key that holds the full name of the tickets PayPal "buyer"
*
* @var string
*/
public $full_name = '_tribe_tpp_full_name';
/**
* Meta key that holds the email of the tickets PayPal "buyer"
*
* @var string
*/
public $email = '_tribe_tpp_email';
/**
* Meta key that holds the name of a ticket to be used in reports if the Product is deleted
* @var string
*/
public $deleted_product = '_tribe_deleted_product_name';
/**
* @var array An array cache to store pending attendees per ticket.
*/
public $pending_attendees_by_ticket = array();
/**
* @var bool Whether pending stock logic should be ignored or not no matter the Settings.
* This is an internal property. Use the `tribe_tickets_tpp_pending_stock_ignore`
* filter or the accessor method to manipulate this value from another class.
*/
protected $ignore_pending_stock_logic = false;
/**
* @var Tribe__Tickets__Commerce__PayPal__Attendance_Totals
*/
protected $attendance_totals;
/**
* Messages for submission
*/
protected static $messages = array();
/**
* @var Tribe__Tickets__Commerce__PayPal__Tickets_View
*/
protected $tickets_view;
/**
* A variable holder if PayPal is loaded
* @var boolean
*/
protected $is_loaded = false;
/**
* Get (and instantiate, if necessary) the instance of the class
*
* @since 4.7
*
* @static
* @return Tribe__Tickets__Commerce__PayPal__Main
*/
public static function get_instance() {
return tribe( 'tickets.commerce.paypal' );
}
/**
* Class constructor
*
* @since 4.7
*/
public function __construct() {
$main = Tribe__Tickets__Main::instance();
/* Set up some parent's vars */
$this->plugin_name = _x( 'Tribe Commerce', 'ticket provider', 'event-tickets' );
$this->plugin_path = $main->plugin_path;
$this->plugin_url = $main->plugin_url;
// mirror some properties from the class constants
$this->attendee_event_key = self::ATTENDEE_EVENT_KEY;
$this->attendee_product_key = self::ATTENDEE_PRODUCT_KEY;
$this->attendee_object = self::ATTENDEE_OBJECT;
parent::__construct();
$this->bind_implementations();
if ( ! $this->is_active() ) {
unset( parent::$active_modules['Tribe__Tickets__Commerce__PayPal__Main'] );
}
$this->tickets_view = tribe( 'tickets.commerce.paypal.view' );
$this->register_resources();
$this->hooks();
$this->is_loaded = true;
}
/**
* Whether PayPal tickets will be available as a provider or not.
*
* This will take into account the enable/disable option and the
* configuration status of the current payment handler (IPN or PDT).
*
* @since 4.7
*
* @return bool
*/
public function is_active() {
/**
* Filters the check for the active status of the PayPal tickets module.
*
* Returning a non `null` value in this filter will override the default checks.
*
* @since 4.7
*
* @param bool $is_active
* @param Tribe__Tickets__Commerce__PayPal__Main $this
*/
$is_active = apply_filters( 'tribe_tickets_commerce_paypal_is_active', null, $this );
if ( null !== $is_active ) {
return (bool) $is_active;
}
/** @var Tribe__Tickets__Commerce__PayPal__Gateway $gateway */
$gateway = tribe( 'tickets.commerce.paypal.gateway' );
/** @var Tribe__Tickets__Commerce__PayPal__Handler__Interface $handler */
$handler = $gateway->build_handler();
return tribe_is_truthy( tribe_get_option( 'ticket-paypal-enable', false ) )
&& 'complete' === $handler->get_config_status();
}
/**
* Registers the implementations in the container
*
* @since 4.7
*/
public function bind_implementations() {
// some classes will require an instance of this class as a dependency so we alias it here
tribe_singleton( 'Tribe__Tickets__Commerce__PayPal__Main', $this );
tribe_singleton( 'tickets.commerce.paypal.view', 'Tribe__Tickets__Commerce__PayPal__Tickets_View' );
tribe_singleton( 'tickets.commerce.paypal.handler.ipn', 'Tribe__Tickets__Commerce__PayPal__Handler__IPN', array( 'hook' ) );
tribe_singleton( 'tickets.commerce.paypal.handler.pdt', 'Tribe__Tickets__Commerce__PayPal__Handler__PDT', array( 'hook' ) );
tribe_singleton( 'tickets.commerce.paypal.gateway', 'Tribe__Tickets__Commerce__PayPal__Gateway', array( 'hook', 'build_handler' ) );
tribe_singleton( 'tickets.commerce.paypal.notices', 'Tribe__Tickets__Commerce__PayPal__Notices' );
tribe_singleton( 'tickets.commerce.paypal.endpoints', 'Tribe__Tickets__Commerce__PayPal__Endpoints', array( 'hook' ) );
tribe_singleton( 'tickets.commerce.paypal.endpoints.templates.success', 'Tribe__Tickets__Commerce__PayPal__Endpoints__Success_Template' );
tribe_singleton( 'tickets.commerce.paypal.orders.tabbed-view', 'Tribe__Tickets__Commerce__Orders_Tabbed_View' );
tribe_singleton( 'tickets.commerce.paypal.orders.report', 'Tribe__Tickets__Commerce__PayPal__Orders__Report' );
tribe_singleton( 'tickets.commerce.paypal.orders.sales', 'Tribe__Tickets__Commerce__PayPal__Orders__Sales' );
tribe_singleton( 'tickets.commerce.paypal.screen-options', 'Tribe__Tickets__Commerce__PayPal__Screen_Options', array( 'hook' ) );
tribe_singleton( 'tickets.commerce.paypal.stati', 'Tribe__Tickets__Commerce__PayPal__Stati' );
tribe_singleton( 'tickets.commerce.paypal.currency', 'Tribe__Tickets__Commerce__Currency', array( 'hook' ) );
tribe_singleton( 'tickets.commerce.paypal.links', 'Tribe__Tickets__Commerce__PayPal__Links' );
tribe_singleton( 'tickets.commerce.paypal.oversell.policies', 'Tribe__Tickets__Commerce__PayPal__Oversell__Policies' );
tribe_singleton( 'tickets.commerce.paypal.oversell.request', 'Tribe__Tickets__Commerce__PayPal__Oversell__Request' );
tribe_singleton( 'tickets.commerce.paypal.frontend.tickets-form', 'Tribe__Tickets__Commerce__PayPal__Frontend__Tickets_Form' );
tribe_register( 'tickets.commerce.paypal.cart', 'Tribe__Tickets__Commerce__PayPal__Cart__Unmanaged' );
tribe()->tag( array(
'tickets.commerce.paypal.shortcodes.tpp-success' => 'Tribe__Tickets__Commerce__PayPal__Shortcodes__Success',
), 'tpp-shortcodes' );
/** @var Tribe__Tickets__Commerce__PayPal__Shortcodes__Interface $shortcode */
foreach ( tribe()->tagged( 'tpp-shortcodes' ) as $shortcode ) {
add_shortcode( $shortcode->tag(), array( $shortcode, 'render' ) );
}
tribe( 'tickets.commerce.paypal.gateway' );
tribe( 'tickets.commerce.paypal.orders.report' );
tribe( 'tickets.commerce.paypal.screen-options' );
tribe( 'tickets.commerce.paypal.endpoints' );
tribe( 'tickets.commerce.paypal.currency' );
}
/**
* Registers all actions/filters
*
* @since 4.7
*/
public function hooks() {
// if the hooks have already been bound, don't do it again
if ( $this->is_loaded ) {
return false;
}
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_resources' ], 11 );
add_action( 'trashed_post', [ $this, 'maybe_redirect_to_attendees_report' ] );
add_filter( 'post_updated_messages', [ $this, 'updated_messages' ] );
add_action( 'tpp_checkin', [ $this, 'purge_attendees_transient' ] );
add_action( 'tpp_uncheckin', [ $this, 'purge_attendees_transient' ] );
add_action( 'tribe_events_tickets_attendees_event_details_top', [ $this, 'setup_attendance_totals' ] );
add_action( 'init', [ $this, 'init' ] );
add_action( 'init', tribe_callback( 'tickets.commerce.paypal.orders.report', 'hook' ) );
add_action( 'event_tickets_attendee_update', [ $this, 'update_attendee_data' ], 10, 3 );
add_action( 'event_tickets_after_attendees_update', [ $this, 'maybe_send_tickets_after_status_change' ] );
add_filter(
'event_tickets_attendees_tpp_checkin_stati',
[ $this, 'filter_event_tickets_attendees_tpp_checkin_stati' ]
);
add_action( 'admin_init', tribe_callback( 'tickets.commerce.paypal.notices', 'hook' ) );
add_action( 'tribe_tickets_attendees_page_inside', tribe_callback( 'tickets.commerce.paypal.orders.tabbed-view', 'render' ) );
add_action( 'tribe_events_tickets_metabox_edit_advanced', [ $this, 'do_metabox_advanced_options' ], 10, 2 );
add_filter( 'tribe_tickets_stock_message_available_quantity', tribe_callback( 'tickets.commerce.paypal.orders.sales', 'filter_available' ), 10, 4 );
add_action( 'admin_init', tribe_callback( 'tickets.commerce.paypal.oversell.request', 'handle' ) );
add_filter( 'tribe_tickets_get_default_module', [ $this, 'deprioritize_module' ], 5, 2 );
add_filter( 'tribe_tickets_tickets_in_cart', [ $this, 'get_tickets_in_cart' ], 10, 1 );
add_action( 'wp_loaded', [ $this, 'maybe_redirect_to_attendees_registration_screen' ], 1 );
add_action( 'wp_loaded', [ $this, 'maybe_delete_expired_products' ], 0 );
add_filter( 'tribe_attendee_registration_form_classes', [ $this, 'tribe_attendee_registration_form_class' ] );
add_action( 'tickets_tpp_ticket_deleted', [ $this, 'update_stock_after_deletion' ], 10, 3 );
}
/**
* Hooked to the init action
*
* @since 4.7
*/
public function init() {
$this->register_types();
}
/**
* registers resources
*
* @since 4.7
*/
public function register_resources() {
$main = Tribe__Tickets__Main::instance();
tribe_assets(
$main,
array(
array(
'event-tickets-tpp-css',
'tpp.css',
),
array(
'event-tickets-tpp-js',
'tpp.js',
array(
'jquery',
'jquery-ui-datepicker',
),
),
),
null,
array(
'localize' => array(
'event-tickets-tpp-js',
'tribe_tickets_tpp_strings',
array(
'attendee' => _x( 'Attendee %1$s', 'Attendee number', 'event-tickets' ),
),
),
)
);
// Admin assets
tribe_assets(
$main,
array(
array(
'event-tickets-tpp-admin-js',
'tpp-admin.js',
array(
'jquery',
'underscore',
),
),
),
'admin_enqueue_scripts',
array(
'conditionals' => 'is_admin',
'localize' => (object) array(
'name' => 'tribe_tickets_tpp_admin_strings',
'data' => array(
'complete' => tribe( 'tickets.commerce.paypal.handler.ipn' )->get_config_status( 'label', 'complete' ),
'incomplete' => tribe( 'tickets.commerce.paypal.handler.ipn' )->get_config_status( 'label', 'incomplete' ),
),
),
)
);
}
/**
* Enqueue the plugin admin stylesheet(s) and JS.
*
* @since 4.7
*/
public function enqueue_resources() {
$post_types = Tribe__Tickets__Main::instance()->post_types();
if ( ! is_singular( $post_types ) ) {
return;
}
wp_enqueue_style( 'event-tickets-tpp-css' );
wp_enqueue_script( 'event-tickets-tpp-js' );
// Check for override stylesheet
$user_stylesheet_url = Tribe__Templates::locate_stylesheet( 'tribe-events/tickets/tpp.css' );
// If override stylesheet exists, then enqueue it
if ( $user_stylesheet_url ) {
wp_enqueue_style( 'tribe-events-tickets-tpp-override-style', $user_stylesheet_url );
}
}
/**
* Register our custom post type
*
* @since 4.7
*/
public function register_types() {
$ticket_post_args = array(
'label' => __( 'Tickets', 'event-tickets' ),
'labels' => array(
'name' => __( 'Tribe Commerce Tickets', 'event-tickets' ),
'singular_name' => __( 'Tribe Commerce Ticket', 'event-tickets' ),
),
'public' => false,
'show_ui' => false,
'show_in_menu' => false,
'query_var' => false,
'rewrite' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
);
$attendee_post_args = array(
'label' => __( 'Attendees', 'event-tickets' ),
'public' => false,
'show_ui' => false,
'show_in_menu' => false,
'query_var' => false,
'rewrite' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
);
$order_post_args = array(
'label' => __( 'Orders', 'event-tickets' ),
'public' => false,
'show_ui' => false,
'show_in_menu' => false,
'query_var' => false,
'rewrite' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
);
/**
* Filter the arguments that craft the ticket post type.
*
* @since 4.7
*
* @see register_post_type
*
* @param array $ticket_post_args Post type arguments, passed to register_post_type()
*/
$ticket_post_args = apply_filters( 'tribe_tickets_register_ticket_post_type_args', $ticket_post_args );
register_post_type( $this->ticket_object, $ticket_post_args );
/**
* Filter the arguments that craft the attendee post type.
*
* @since 4.7
*
* @see register_post_type
*
* @param array $attendee_post_args Post type arguments, passed to register_post_type()
*/
$attendee_post_args = apply_filters( 'tribe_tickets_register_attendee_post_type_args', $attendee_post_args );
register_post_type( self::ATTENDEE_OBJECT, $attendee_post_args );
/**
* Filter the arguments that craft the order post type.
*
* @since 4.7
*
* @see register_post_type
*
* @param array $attendee_post_args Post type arguments, passed to register_post_type()
*/
$order_post_args = apply_filters( 'tribe_tickets_register_order_post_type_args', $order_post_args );
register_post_type( self::ORDER_OBJECT, $order_post_args );
Tribe__Tickets__Commerce__PayPal__Stati::register_order_stati();
}
/**
* Adds Tribe Commerce attendance totals to the summary box of the attendance
* screen.
*
* Expects to fire during 'tribe_tickets_attendees_page_inside', ie
* before the attendee screen is rendered.
*
* @since 4.7
*/
public function setup_attendance_totals() {
$this->attendance_totals()->integrate_with_attendee_screen();
}
/**
* @since 4.7
*
* @return Tribe__Tickets__Commerce__PayPal__Attendance_Totals
*/
public function attendance_totals() {
if ( empty( $this->attendance_totals ) ) {
$this->attendance_totals = new Tribe__Tickets__Commerce__PayPal__Attendance_Totals;
}
return $this->attendance_totals;
}
/**
* Update the PayPalTicket values for this user.
*
* Note that, within this method, $order_id refers to the attendee or ticket ID
* (it does not refer to an "order" in the sense of a transaction that may include
* multiple tickets, as is the case in some other methods for instance).
*
* @since 4.7
*
* @param array $data
* @param int $order_id
* @param int $event_id
*/
public function update_attendee_data( $data, $order_id, $event_id ) {
$user_id = get_current_user_id();
$ticket_orders = $this->tickets_view->get_post_ticket_attendees( $event_id, $user_id );
$ticket_order_ids = wp_list_pluck( $ticket_orders, 'order_id' );
// This makes sure we don't save attendees for orders that are not from this current user and event
if ( ! in_array( $order_id, $ticket_order_ids ) ) {
return;
}
$attendee = array();
// Get the Attendee Data, it's important for testing
foreach ( $ticket_orders as $test_attendee ) {
if ( $order_id !== $test_attendee['order_id'] ) {
continue;
}
$attendee = $test_attendee;
}
$attendee_email = empty( $data['email'] ) ? null : sanitize_email( $data['email'] );
$attendee_email = is_email( $attendee_email ) ? $attendee_email : null;
$attendee_full_name = empty( $data['full_name'] ) ? null : sanitize_text_field( $data['full_name'] );
$attendee_optout = empty( $data['optout'] ) ? false : (bool) $data['optout'];
$product_id = $attendee['product_id'];
update_post_meta( $order_id, $this->attendee_optout_key, (bool) $attendee_optout );
if ( ! is_null( $attendee_full_name ) ) {
update_post_meta( $order_id, $this->full_name, $attendee_full_name );
}
if ( ! is_null( $attendee_email ) ) {
update_post_meta( $order_id, $this->email, $attendee_email );
}
}
/**
* Triggers the sending of ticket emails after PayPal Ticket information is updated.
*
* This is useful if a user initially suggests they will not be attending
* an event (in which case we do not send tickets out) but where they
* incrementally amend the status of one or more of those tickets to
* attending, at which point we should send tickets out for any of those
* newly attending persons.
*
* @since 4.7
*
* @param $event_id
*/
public function maybe_send_tickets_after_status_change( $event_id ) {
$transaction_ids = array();
foreach ( $this->get_event_attendees( $event_id ) as $attendee ) {
$transaction = get_post_meta( $attendee[ 'attendee_id' ], $this->order_key, true );
if ( ! empty( $transaction ) ) {
$transaction_ids[ $transaction ] = $transaction;
}
}
foreach ( $transaction_ids as $transaction ) {
// This method takes care of intelligently sending out emails only when
// required, for attendees that have not yet received their tickets
$this->send_tickets_email( $transaction, $event_id );
}
}
/**
* Generate and store all the attendees information for a new order.
*
* @param string $payment_status The tickets payment status, defaults to completed.
* @param bool $redirect Whether the client should be redirected or not.
*
* @since 4.7
*/
public function generate_tickets( $payment_status = 'completed', $redirect = true ) {
/** @var Tribe__Tickets__Commerce__PayPal__Gateway $gateway */
$gateway = tribe( 'tickets.commerce.paypal.gateway' );
$transaction_data = $gateway->get_transaction_data();
/** @var Tribe__Tickets__Commerce__PayPal__Cart__Interface $cart */
$cart = tribe( 'tickets.commerce.paypal.cart' );
/**
* The `invoice` variable is a passthrough one; if passed when adding items to the cart
* then it should be returned to us from PayPal. If we have it in the transaction data
* we can assume the cart associated with the invoice, if any, can be removed.
*
* @link https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/formbasics/#variations-on-basic-variables
*/
if ( ! empty( $transaction_data['custom'] ) ) {
$decoded_custom = Tribe__Tickets__Commerce__PayPal__Custom_Argument::decode( $transaction_data['custom'], true );
if ( isset( $decoded_custom['invoice'] ) ) {
$cart->set_id( $decoded_custom['invoice'] );
$cart->clear();
}
}
$raw_transaction_data = $gateway->get_raw_transaction_data();
if ( empty( $transaction_data ) || empty( $transaction_data['items'] ) ) {
return;
}
$has_tickets = $post_id = false;
/**
* PayPal Ticket specific action fired just before a PayPalTicket-driven attendee tickets for an order are generated
*
* @since 4.7
*
* @param array $transaction_data PayPal payment data
*/
do_action( 'tribe_tickets_tpp_before_order_processing', $transaction_data );
$order_id = $transaction_data['txn_id'];
$is_refund = Tribe__Tickets__Commerce__PayPal__Stati::$refunded === $payment_status
|| 'refund' === Tribe__Utils__Array::get( $transaction_data, 'reason_code', '' );
if ( $is_refund ) {
$transaction_data['payment_status'] = $payment_status = Tribe__Tickets__Commerce__PayPal__Stati::$refunded;
$refund_order_id = $order_id;
$order_id = Tribe__Utils__Array::get( $transaction_data, 'parent_txn_id', $order_id );
$order = Tribe__Tickets__Commerce__PayPal__Order::from_order_id( $order_id );
$order->refund_with( $refund_order_id );
unset( $transaction_data['txn_id'], $transaction_data['parent_txn_id'] );
$order->hydrate_from_transaction_data( $transaction_data );
} else {
$order = Tribe__Tickets__Commerce__PayPal__Order::from_transaction_data( $transaction_data );
}
$order->set_meta( 'transaction_data', $raw_transaction_data );
$custom = Tribe__Tickets__Commerce__PayPal__Custom_Argument::decode( $transaction_data['custom'], true );
/*
* This method might run during a POST (IPN) PayPal request hence the
* purchasing user ID, if any, will be stored in a custom PayPal var.
* Let's fallback on the current user ID for GET requests (PDT); it will be always `0`
* during a PayPal POST (IPN) request.
*/
$attendee_user_id = ! isset( $custom['user_id'] ) ? get_current_user_id() : absint( $custom['user_id'] );
$attendee_full_name = empty( $transaction_data['first_name'] ) && empty( $transaction_data['last_name'] )
? ''
: sanitize_text_field( "{$transaction_data['first_name']} {$transaction_data['last_name']}" );
$attendee_email = empty( $transaction_data['payer_email'] ) ? null : sanitize_email( $transaction_data['payer_email'] );
$attendee_email = is_email( $attendee_email ) ? $attendee_email : null;
if ( ! empty( $attendee_user_id ) ) {
$attendee = get_user_by( 'id', $attendee_user_id );
// Check if the user was found.
if ( $attendee ) {
// Check if the user has an email address.
if ( $attendee->user_email ) {
$attendee_email = $attendee->user_email;
}
$user_full_name = trim( "{$attendee->first_name} {$attendee->last_name}" );
// Check if the user has first/last name.
if ( ! empty( $user_full_name ) ) {
$attendee_full_name = $user_full_name;
}
}
}
/**
* This is an array of tickets IDs for which the user decided to opt-out.
*
* @see \Tribe__Tickets_Plus__Commerce__PayPal__Attendees::register_optout_choice()
*/
$attendee_optouts = Tribe__Utils__Array::list_to_array( Tribe__Utils__Array::get( $custom, 'oo', array() ), ',' );
if ( ! $attendee_email || ! $attendee_full_name ) {
$this->redirect_after_error( 101, $redirect, $post_id );
return;
}
// Iterate over each product
foreach ( (array) $transaction_data['items'] as $item ) {
$order_attendee_id = 0;
if ( empty( $item['ticket'] ) ) {
continue;
}
/** @var \Tribe__Tickets__Ticket_Object $ticket_type */
$ticket_type = $item['ticket'];
$product_id = $ticket_type->ID;
// Get the event this tickets is for
$post = $ticket_type->get_event();
if ( empty( $post ) ) {
continue;
}
$post_id = $post->ID;
// if there were no PayPal tickets for the product added to the cart, continue
if ( empty( $item['quantity'] ) ) {
continue;
}
// get the PayPal status `decrease_stock_by` value
$status_stock_size = 1;
$ticket_qty = (int) $item['quantity'];
// to avoid tickets from not being created on a status stock size of 0
// let's take the status stock size into account and create a number of tickets
// at least equal to the number of tickets the user requested
$ticket_qty = $status_stock_size < 1 ? $ticket_qty : $status_stock_size * $ticket_qty;
$qty = max( $ticket_qty, 0 );
// Throw an error if Qty is bigger then Remaining
if ( $ticket_type->managing_stock() && $payment_status === Tribe__Tickets__Commerce__PayPal__Stati::$completed ) {
$this->ignore_pending_stock_logic( true );
$inventory = (int) $ticket_type->inventory();
$this->ignore_pending_stock_logic( false );
$inventory_is_not_unlimited = - 1 !== $inventory;
if ( $inventory_is_not_unlimited && $qty > $inventory ) {
if ( ! $order->was_pending() ) {
$this->redirect_after_error( 102, $redirect, $post_id );
return;
}
/** @var Tribe__Tickets__Commerce__PayPal__Oversell__Policies $oversell_policies */
$oversell_policies = tribe( 'tickets.commerce.paypal.oversell.policies' );
$oversell_policy = $oversell_policies->for_post_ticket_order( $post_id, $ticket_type->ID, $order_id );
$qty = $oversell_policy->modify_quantity( $qty, $inventory );
if ( ! $oversell_policy->allows_overselling() ) {
$oversold_attendees = $this->get_attendees_by_order_id( $order_id );
$oversell_policy->handle_oversold_attendees( $oversold_attendees );
$this->redirect_after_error( 102, $redirect, $post_id );
return;
}
}
}
if ( $qty === 0 ) {
$this->redirect_after_error( 103, $redirect, $post_id );
return;
}
$has_tickets = true;
/**
* PayPal specific action fired just before a PayPal-driven attendee ticket for an event is generated
*
* @since 4.7
*
* @param int $post_id ID of event
* @param string $ticket_type Ticket Type object for the product
* @param array $data Parsed PayPal transaction data
*/
do_action( 'tribe_tickets_tpp_before_attendee_ticket_creation', $post_id, $ticket_type, $transaction_data );
$existing_attendees = $this->get_attendees_by_order_id( $order_id );
$has_generated_new_tickets = false;
/** @var Tribe__Tickets__Commerce__Currency $currency */
$currency = tribe( 'tickets.commerce.currency' );
$currency_symbol = $currency->get_currency_symbol( $product_id, true );
// Iterate over all the amount of tickets purchased (for this product)
for ( $i = 0; $i < $qty; $i ++ ) {
$attendee_id = null;
$updating_attendee = false;
// check if we already have an attendee or not
$post_title = $attendee_full_name . ' | ' . ( $i + 1 );
$criteria = array( 'post_title' => $post_title, 'product_id' => $product_id, 'event_id' => $post_id );
$existing_attendee = wp_list_filter( $existing_attendees, $criteria );
if ( ! empty( $existing_attendee ) ) {
$existing_attendee = reset( $existing_attendee );
$updating_attendee = true;
$attendee_id = $existing_attendee['attendee_id'];
} else {
$attendee = array(
'post_status' => 'publish',
'post_title' => $post_title,
'post_type' => $this->attendee_object,
'ping_status' => 'closed',
);
// Insert individual ticket purchased
$attendee_id = wp_insert_post( $attendee );
// since we are creating at least one
$has_generated_new_tickets = true;
}
$global_stock = new Tribe__Tickets__Global_Stock( $post_id );
$shared_capacity = false;
if ( $global_stock->is_enabled() ) {
$shared_capacity = true;
}
if ( $status_stock_size > 0 ) {
switch ( $payment_status ) {
case Tribe__Tickets__Commerce__PayPal__Stati::$completed:
$this->increase_ticket_sales_by( $product_id, 1, $shared_capacity, $global_stock );
break;
case Tribe__Tickets__Commerce__PayPal__Stati::$refunded:
$this->decrease_ticket_sales_by( $product_id, 1, $shared_capacity, $global_stock );
break;
default:
break;
}
}
$attendee_order_status = trim( strtolower( $payment_status ) );
if ( ! $updating_attendee ) {
update_post_meta( $attendee_id, $this->attendee_product_key, $product_id );
update_post_meta( $attendee_id, $this->attendee_event_key, $post_id );
update_post_meta( $attendee_id, $this->security_code, $this->generate_security_code( $attendee_id ) );
update_post_meta( $attendee_id, $this->order_key, $order_id );
$attendee_optout = Tribe__Utils__Array::get( $attendee_optouts, $product_id, false );
update_post_meta( $attendee_id, $this->attendee_optout_key, (bool) $attendee_optout );
update_post_meta( $attendee_id, $this->email, $attendee_email );
update_post_meta( $attendee_id, $this->full_name, $attendee_full_name );
update_post_meta( $attendee_id, '_paid_price', get_post_meta( $product_id, '_price', true ) );
update_post_meta( $attendee_id, '_price_currency_symbol', $currency_symbol );
}
update_post_meta( $attendee_id, $this->attendee_tpp_key, $attendee_order_status );
if ( Tribe__Tickets__Commerce__PayPal__Stati::$refunded === $payment_status ) {
$refund_order_id = Tribe__Utils__Array::get( $transaction_data, 'txn_id', '' );
update_post_meta( $attendee_id, $this->refund_order_key, $refund_order_id );
}
if ( ! $updating_attendee ) {
/**
* Action fired when an PayPal attendee ticket is created
*
* @since 4.7
*
* @param int $attendee_id Attendee post ID
* @param int $order_id PayPal Order ID
* @param int $product_id PayPal ticket post ID
* @param int $order_attendee_id Attendee number in submitted order
* @param string $attendee_order_status The order status for the attendee.
*/
do_action( 'event_tickets_tpp_attendee_created', $attendee_id, $order_id, $product_id, $order_attendee_id, $attendee_order_status );
}
/**
* Action fired when an PayPal attendee ticket is updated.
*
* This action will fire both when the attendee is created and
* when the attendee is updated.
* Hook into the `event_tickets_tpp_attendee_created` action to
* only act on the attendee creation.
*
* @since 4.7
*
* @param int $attendee_id Attendee post ID
* @param int $order_id PayPal Order ID
* @param int $product_id PayPal ticket post ID
* @param int $order_attendee_id Attendee number in submitted order
* @param string $attendee_order_status The order status for the attendee.
*/
do_action( 'event_tickets_tpp_attendee_updated', $attendee_id, $order_id, $product_id, $order_attendee_id, $attendee_order_status );
$order->add_attendee( $attendee_id );
$this->record_attendee_user_id( $attendee_id, $attendee_user_id );
$order_attendee_id++;
if ( ! empty( $existing_attendee ) ) {
$existing_attendees = wp_list_filter( $existing_attendees, array( 'attendee_id' => $existing_attendee['attendee_id'] ), 'NOT' );
}
}
if ( ! ( empty( $existing_attendees ) || empty( $oversell_policy ) ) ) {
// an oversell policy applied: what to do with existing oversold attendees?
$oversell_policy->handle_oversold_attendees( $existing_attendees );
}
if ( $has_generated_new_tickets ) {
/**
* Action fired when a PayPal has had attendee tickets generated for it.
*
* @since 4.7
*
* @param int $product_id PayPal ticket post ID
* @param int $order_id ID of the PayPal order
* @param int $qty Quantity ordered
*/
do_action( 'event_tickets_tpp_tickets_generated_for_product', $product_id, $order_id, $qty );
}
/**
* Action fired when a PayPal has had attendee tickets updated for it.
*
* This will fire even when tickets are initially created; if you need to hook on the
* creation process only use the 'event_tickets_tpp_tickets_generated_for_product' action.
*
* @since 4.7
*
* @param int $product_id PayPal ticket post ID
* @param int $order_id ID of the PayPal order
* @param int $qty Quantity ordered
*/
do_action( 'event_tickets_tpp_tickets_generated_for_product', $product_id, $order_id, $qty );
// After Adding the Values we Update the Transient
Tribe__Post_Transient::instance()->delete( $post_id, Tribe__Tickets__Tickets::ATTENDEES_CACHE );
}
$order->update();
/**
* Fires when an PayPal attendee tickets have been generated.
*
* @since 4.7
*
* @param int $order_id ID of the PayPal order
* @param int $post_id ID of the post the order was placed for
*/
do_action( 'event_tickets_tpp_tickets_generated', $order_id, $post_id );
/**
* Filters whether a confirmation email should be sent or not for PayPal tickets.
*
* This applies to attendance and non attendance emails.
*
* @since 4.7
*
* @param bool $send_mail Defaults to `true`.
*/
$send_mail = apply_filters( 'tribe_tickets_tpp_send_mail', true );
if (
$send_mail
&& $has_tickets
&& $attendee_order_status === Tribe__Tickets__Commerce__PayPal__Stati::$completed
) {
$this->send_tickets_email( $order_id, $post_id );
}
// Redirect to the same page to prevent double purchase on refresh
if ( ! empty( $post_id ) ) {
/** @var \Tribe__Tickets__Commerce__PayPal__Endpoints $endpoints */
$endpoints = tribe( 'tickets.commerce.paypal.endpoints' );
$url = $endpoints->success_url( $order_id, $post_id );
if ( $redirect ) {
wp_redirect( esc_url_raw( $url ) );
}
tribe_exit();
}
}
/**
* Sends ticket email
*
* @since 4.7.6 added $post_id parameter
*
* @param int $order_id Order post ID
* @param int $post_id Parent post ID (optional)
*/
public function send_tickets_email( $order_id, $post_id = null ) {
$all_attendees = $this->get_attendees_by_order_id( $order_id );
$to_send = array();
if ( empty( $all_attendees ) ) {
return;
}
// Look at each attendee and check if a ticket was sent: in each case where a ticket
// has not yet been sent we should a) send the ticket out by email and b) record the
// fact it was sent
foreach ( $all_attendees as $single_attendee ) {
// Only add those attendees/tickets that haven't already been sent
if ( ! empty( $single_attendee[ 'ticket_sent' ] ) ) {
continue;
}
$to_send[] = $single_attendee;
update_post_meta( $single_attendee[ 'qr_ticket_id' ], $this->attendee_ticket_sent, true );
}
/**
* Controls the list of tickets which will be emailed out.
*
* @since 4.7
* @since 4.7.6 added new parameter $post_id
*
* @param array $to_send list of tickets to be sent out by email
* @param array $all_attendees list of all attendees/tickets, including those already sent out
* @param int $post_id
* @param int $order_id
*
*/
$to_send = (array) apply_filters( 'tribe_tickets_tpp_tickets_to_send', $to_send, $all_attendees, $post_id, $order_id );
if ( empty( $to_send ) ) {
return;
}
// For now all ticket holders in an order share the same email
$to = $all_attendees['0']['holder_email'];
if ( ! is_email( $to ) ) {
return;
}
/**
* Filters the Tribe Commerce tickets email content
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param string email content
* @param int $post_id
* @param int $order_id
*/
$content = apply_filters( 'tribe_tpp_email_content', $this->generate_tickets_email_content( $to_send ), $post_id, $order_id );
/**
* Filters the Tribe Commerce tickets email sender name
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param string email sender name
* @param int $post_id
* @param int $order_id
*/
$from = apply_filters( 'tribe_tpp_email_from_name', tribe_get_option( 'ticket-paypal-confirmation-email-sender-name', false ), $post_id, $order_id );
/**
* Filters the Tribe Commerce tickets email sender email
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param string email sender email
* @param int $post_id
* @param int $order_id
*/
$from_email = apply_filters( 'tribe_tpp_email_from_email', tribe_get_option( 'ticket-paypal-confirmation-email-sender-email', false ), $post_id, $order_id );
$headers = array( 'Content-type: text/html' );
if ( ! empty( $from ) && ! empty( $from_email ) ) {
$headers[] = sprintf( 'From: %s <%s>', filter_var( $from, FILTER_SANITIZE_STRING ), filter_var( $from_email, FILTER_SANITIZE_EMAIL ) );
}
/**
* Filters the Tribe Commerce tickets email headers
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param array email headers
* @param int $post_id
* @param int $order_id
*/
$headers = apply_filters( 'tribe_tpp_email_headers', $headers, $post_id, $order_id );
/**
* Filters the Tribe Commerce tickets email attachments
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param array attachments
* @param int $post_id
* @param int $order_id
*/
$attachments = apply_filters( 'tribe_tpp_email_attachments', array(), $post_id, $order_id );
/**
* Filters the Tribe Commerce tickets email recipient
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param string $to
* @param int $event_id
* @param int $order_id
*/
$to = apply_filters( 'tribe_tpp_email_recipient', $to, $post_id, $order_id );
$site_name = stripslashes_deep( html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
$default_subject = sprintf( __( 'Your tickets from %s', 'event-tickets' ), $site_name );
/**
* Filters the Tribe Commerce tickets email subject
*
* @since 4.7.6 added new parameters $post_id and $order_id
*
* @param string email subject
* @param int $post_id
* @param int $order_id
*/
$subject = apply_filters( 'tribe_tpp_email_subject', tribe_get_option( 'ticket-paypal-confirmation-email-subject', $default_subject ) );
wp_mail( $to, $subject, $content, $headers, $attachments );
}
/**
* Saves a ticket
*
* @since 4.7
*
* @param int $post_id
* @param Tribe__Tickets__Ticket_Object $ticket
* @param array $raw_data
*
* @return int The updated/created ticket post ID
*/
public function save_ticket( $post_id, $ticket, $raw_data = array() ) {
// assume we are updating until we find out otherwise
$save_type = 'update';
if ( empty( $ticket->ID ) ) {
$save_type = 'create';
/* Create main product post */
$args = array(
'post_status' => 'publish',
'post_type' => $this->ticket_object,
'post_author' => get_current_user_id(),
'post_excerpt' => $ticket->description,
'post_title' => $ticket->name,
'menu_order' => tribe_get_request_var( 'menu_order', -1 ),
);
$ticket->ID = wp_insert_post( $args );
// Relate event <---> ticket
add_post_meta( $ticket->ID, $this->event_key, $post_id );
} else {
$args = array(
'ID' => $ticket->ID,
'post_excerpt' => $ticket->description,
'post_title' => $ticket->name,
'menu_order' => $ticket->menu_order,
);
$ticket->ID = wp_update_post( $args );
}
if ( ! $ticket->ID ) {
return false;
}
// Updates if we should show Description
$ticket->show_description = isset( $ticket->show_description ) && tribe_is_truthy( $ticket->show_description ) ? 'yes' : 'no';
update_post_meta( $ticket->ID, tribe( 'tickets.handler' )->key_show_description, $ticket->show_description );
// let's make sure float price values are formatted to "0.xyz"
if ( is_numeric( $ticket->price ) ) {
$ticket->price = (string) (int) $ticket->price === $ticket->price
? (int) $ticket->price
: (float) $ticket->price;
}
update_post_meta( $ticket->ID, '_price', $ticket->price );
$ticket_data = Tribe__Utils__Array::get( $raw_data, 'tribe-ticket', array() );
$this->update_capacity( $ticket, $ticket_data, $save_type );
foreach ( array( 'start_date', 'start_time', 'end_date', 'end_time' ) as $time_key ) {
if ( isset( $ticket->{$time_key} ) ) {
update_post_meta( $ticket->ID, "_ticket_{$time_key}", $ticket->{$time_key} );
} else {
delete_post_meta( $ticket->ID, "_ticket_{$time_key}" );
}
}
/**
* Toggle filter to allow skipping the automatic SKU generation.
*
* @param bool $should_default_ticket_sku
*/
$should_default_ticket_sku = apply_filters( 'tribe_tickets_should_default_ticket_sku', true );
if ( $should_default_ticket_sku ) {
// make sure the SKU is set to the correct value
if ( ! empty( $raw_data['ticket_sku'] ) ) {
$sku = $raw_data['ticket_sku'];
} else {
$post_author = get_post( $ticket->ID )->post_author;
$str = $raw_data['ticket_name'];
$str = mb_strtoupper( $str, mb_detect_encoding( $str ) );
$sku = "{$ticket->ID}-{$post_author}-" . str_replace( ' ', '-', $str );
$raw_data['ticket_sku'] = $sku;
}
update_post_meta( $ticket->ID, '_sku', $sku );
}
// Fetches all Ticket Form data
$data = Tribe__Utils__Array::get( $raw_data, 'tribe-ticket', array() );
// Fetch the Global stock Instance for this Event
$event_stock = new Tribe__Tickets__Global_Stock( $post_id );
// Only need to do this if we haven't already set one - they shouldn't be able to edit it from here otherwise
if ( ! $event_stock->is_enabled() ) {
if ( isset( $data['event_capacity'] ) ) {
$data['event_capacity'] = trim( filter_var( $data['event_capacity'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH ) );
// If empty we need to modify to -1
if ( '' === $data['event_capacity'] ) {
$data['event_capacity'] = -1;
}
// Makes sure it's an Int after this point
$data['event_capacity'] = (int) $data['event_capacity'];
// We need to update event post meta - if we've set a global stock
$event_stock->enable();
$event_stock->set_stock_level( $data['event_capacity'] );
// Update Event capacity
update_post_meta( $post_id, tribe( 'tickets.handler' )->key_capacity, $data['event_capacity'] );
}
} else {
// If the Global Stock is configured we pull it from the Event
$data['event_capacity'] = tribe_tickets_get_capacity( $post_id );
}
// Default Capacity will be 0
$default_capacity = 0;
$is_capacity_passed = true;
// If we have Event Global stock we fetch that Stock
if ( $event_stock->is_enabled() ) {
$default_capacity = $data['event_capacity'];
}
// Fetch capacity field, if we don't have it use default (defined above)
$data['capacity'] = trim( Tribe__Utils__Array::get( $data, 'capacity', $default_capacity ) );
// If empty we need to modify to the default
if ( '' !== $data['capacity'] ) {
// Makes sure it's an Int after this point
$data['capacity'] = (int) $data['capacity'];
// The only available value lower than zero is -1 which is unlimited
if ( 0 > $data['capacity'] ) {
$data['capacity'] = -1;
}
$default_capacity = $data['capacity'];
}
// Fetch the stock if defined, otherwise use Capacity field
$data['stock'] = trim( Tribe__Utils__Array::get( $data, 'stock', $default_capacity ) );
// If empty we need to modify to what every capacity was
if ( '' === $data['stock'] ) {
$data['stock'] = $default_capacity;
}
// Makes sure it's an Int after this point
$data['stock'] = (int) $data['stock'];
$mode = isset( $data['mode'] ) ? $data['mode'] : 'own';
if ( '' !== $mode ) {
if ( 'update' === $save_type ) {
$totals = tribe( 'tickets.handler' )->get_ticket_totals( $ticket->ID );
$data['stock'] -= $totals['pending'] + $totals['sold'];
}
// In here is safe to check because we don't have unlimted = -1
$status = ( 0 < $data['stock'] ) ? 'instock' : 'outofstock';
update_post_meta( $ticket->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE, $mode );
update_post_meta( $ticket->ID, '_stock', $data['stock'] );
update_post_meta( $ticket->ID, '_stock_status', $status );
update_post_meta( $ticket->ID, '_backorders', 'no' );
update_post_meta( $ticket->ID, '_manage_stock', 'yes' );
// Prevent Ticket Capacity from going higher then Event Capacity
if (
$event_stock->is_enabled()
&& Tribe__Tickets__Global_Stock::OWN_STOCK_MODE !== $mode
&& '' !== $data['capacity']
&& $data['capacity'] > $data['event_capacity']
) {
$data['capacity'] = $data['event_capacity'];
}
} else {
// Unlimited Tickets
// Besides setting _manage_stock to "no" we should remove the associated stock fields if set previously
update_post_meta( $ticket->ID, '_manage_stock', 'no' );
delete_post_meta( $ticket->ID, '_stock_status' );
delete_post_meta( $ticket->ID, '_stock' );
delete_post_meta( $ticket->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_CAP );
delete_post_meta( $ticket->ID, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE );
// Set Capacity -1 when we don't have a stock mode, which means unlimited
$data['capacity'] = -1;
}
if ( '' !== $data['capacity'] ) {
// Update Ticket capacity
update_post_meta( $ticket->ID, tribe( 'tickets.handler' )->key_capacity, $data['capacity'] );
}
/**
* Generic action fired after saving a ticket (by type)
*
* @since 4.7
*
* @param int $post_id Post ID of post the ticket is tied to
* @param Tribe__Tickets__Ticket_Object $ticket Ticket that was just saved
* @param array $raw_data Ticket data
* @param string $class Commerce engine class
*/
do_action( 'event_tickets_after_' . $save_type . '_ticket', $post_id, $ticket, $raw_data, __CLASS__ );
/**
* Generic action fired after saving a ticket
*
* @since 4.7
*
* @param int $post_id Post ID of post the ticket is tied to
* @param Tribe__Tickets__Ticket_Object $ticket Ticket that was just saved
* @param array $raw_data Ticket data
* @param string $class Commerce engine class
*/
do_action( 'event_tickets_after_save_ticket', $post_id, $ticket, $raw_data, __CLASS__ );
return $ticket->ID;
}
/**
* Deletes a ticket
*
* @param $event_id
* @param $ticket_id
*
* @return bool
*/
public function delete_ticket( $event_id, $ticket_id ) {
// Ensure we know the event and product IDs (the event ID may not have been passed in)
if ( empty( $event_id ) ) {
$event_id = get_post_meta( $ticket_id, $this->attendee_event_key, true );
}
// Additional check (in case we were passed an invalid ticket ID and still can't determine the event)
if ( empty( $event_id ) ) {
return false;
}
$product_id = get_post_meta( $ticket_id, $this->attendee_product_key, true );
// @todo: should deleting an attendee replenish a ticket stock?
// Store name so we can still show it in the attendee list
$attendees = $this->get_attendees_by_post_id( $event_id );
$post_to_delete = get_post( $ticket_id );
foreach ( (array) $attendees as $attendee ) {
if ( $attendee['product_id'] == $ticket_id ) {
update_post_meta( $attendee['attendee_id'], $this->deleted_product,
esc_html( $post_to_delete->post_title ) );
}
}
// Try to kill the actual ticket/attendee post
$delete = wp_delete_post( $ticket_id, true );
if ( is_wp_error( $delete ) || ! isset( $delete->ID ) ) {
return false;
}
Tribe__Tickets__Attendance::instance( $event_id )->increment_deleted_attendees_count();
do_action( 'tickets_tpp_ticket_deleted', $ticket_id, $event_id, $product_id );
Tribe__Post_Transient::instance()->delete( $event_id, Tribe__Tickets__Tickets::ATTENDEES_CACHE );
return true;
}
/**
* Shows the tickets form in the front end
*
* @since 4.7
*
* @param $content
*
* @return void
*/
public function front_end_tickets_form( $content ) {
/** @var Tribe__Tickets__Commerce__PayPal__Frontend__Tickets_Form $form */
$form = tribe( 'tickets.commerce.paypal.frontend.tickets-form' );
$form->render( $content );
}
/**
* Indicates if we currently require users to be logged in before they can obtain
* tickets.
*
* @since 4.7
*
* @return bool
*/
public function login_required() {
$requirements = (array) tribe_get_option( 'ticket-authentication-requirements', array() );
return in_array( 'event-tickets_all', $requirements, true );
}
/**
* Gets an individual ticket
*
* @since 4.7
*
* @param $event_id
* @param $ticket_id
*
* @return null|Tribe__Tickets__Ticket_Object
*/
public function get_ticket( $event_id, $ticket_id ) {
$product = get_post( $ticket_id );
if ( ! $product ) {
return null;
}
$return = new Tribe__Tickets__Ticket_Object();
$qty_sold = get_post_meta( $ticket_id, 'total_sales', true );
$return->description = $product->post_excerpt;
$return->ID = $ticket_id;
$return->name = $product->post_title;
$return->post_type = $product->post_type;
$return->price = get_post_meta( $ticket_id, '_price', true );
$return->provider_class = get_class( $this );
$return->admin_link = '';
$return->show_description = $return->show_description();
$return->start_date = get_post_meta( $ticket_id, '_ticket_start_date', true );
$return->end_date = get_post_meta( $ticket_id, '_ticket_end_date', true );
$return->start_time = get_post_meta( $ticket_id, '_ticket_start_time', true );
$return->end_time = get_post_meta( $ticket_id, '_ticket_end_time', true );
$return->sku = get_post_meta( $ticket_id, '_sku', true );
// If the quantity sold wasn't set, default to zero
$qty_sold = $qty_sold ? $qty_sold : 0;
// Ticket stock is a simple reflection of remaining inventory for this item...
$stock = (int) get_post_meta( $ticket_id, '_stock', true );
// If we don't have a stock value, then stock should be considered 'unlimited'
if ( null === $stock ) {
$stock = - 1;
}
$return->manage_stock( 'yes' === get_post_meta( $ticket_id, '_manage_stock', true ) );
$return->stock( $stock );
$return->global_stock_mode( get_post_meta( $ticket_id, Tribe__Tickets__Global_Stock::TICKET_STOCK_MODE, true ) );
$capped = get_post_meta( $ticket_id, Tribe__Tickets__Global_Stock::TICKET_STOCK_CAP, true );
if ( '' !== $capped ) {
$return->global_stock_cap( $capped );
}
$return->qty_sold( $qty_sold );
$return->qty_cancelled( $this->get_cancelled( $ticket_id ) );
$pending = $this->get_qty_pending( $ticket_id );
$return->qty_pending( $pending );
/**
* Use this Filter to change any information you want about this ticket
*
* @since 4.7
*
* @param object $ticket
* @param int $post_id
* @param int $ticket_id
*/
$ticket = apply_filters( 'tribe_tickets_tpp_get_ticket', $return, $event_id, $ticket_id );
return $return;
}
/**
* Get attendees by id and associated post type
* or default to using $post_id
*
* @since 4.7
*
* @param $post_id
* @param null $post_type
*
* @return array|mixed
*/
public function get_attendees_by_id( $post_id, $post_type = null ) {
// PayPal Ticket Orders are a unique hash
if ( ! is_numeric( $post_id ) ) {
$post_type = 'tpp_order_hash';
}
if ( ! $post_type ) {
$post_type = get_post_type( $post_id );
}
switch ( $post_type ) {
case $this->attendee_object:
return $this->get_attendees_by_attendee_id( $post_id );
break;
case 'tpp_order_hash':
return $this->get_attendees_by_order_id( $post_id );
break;
case $this->ticket_object:
return $this->get_attendees_by_ticket_id( $post_id );
break;
default:
return $this->get_attendees_by_post_id( $post_id );
break;
}
}
/**
* {@inheritdoc}
*/
protected function get_attendees_by_order_id( $order_id, $ticket_id = null ) {
if ( ! is_numeric( $order_id ) ) {
return parent::get_attendees_by_order_id( $order_id, $ticket_id );
}
$find_by_args = [
'post_id' => $order_id,
'ticket_id' => [],
];
if ( $ticket_id ) {
$find_by_args['ticket_id'] = (array) $ticket_id;
}
$orders = Tribe__Tickets__Commerce__PayPal__Order::find_by( $find_by_args );
if ( ! $orders ) {
return [];
}
$order_id = current( $orders )->paypal_id();
return parent::get_attendees_by_order_id( $order_id, $ticket_id );
}
/**
* Retrieve only order related information
* Important: On PayPal Ticket the order is the Attendee Object
*
* order_id
* purchaser_name
* purchaser_email
* provider
* provider_slug
*
* @since 4.7
*
* @param int $order_id
* @return array
*/
public function get_order_data( $order_id ) {
$name = get_post_meta( $order_id, $this->full_name, true );
$email = get_post_meta( $order_id, $this->email, true );
$data = array(
'order_id' => $order_id,
'purchaser_name' => $name,
'purchaser_email' => $email,
'provider' => __CLASS__,
'provider_slug' => 'tpp',
'purchase_time' => get_post_time( Tribe__Date_Utils::DBDATETIMEFORMAT, false, $order_id ),
);
/**
* Allow users to filter the Order Data
*
* @since 4.7
*
* @param array $data An associative array with the Information of the Order
* @param string $provider What Provider is been used
* @param int $order_id Order ID
*
*/
$data = apply_filters( 'tribe_tickets_order_data', $data, 'tpp', $order_id );
return $data;
}
/**
* Links to sales report for all tickets for this event.
*
* @since 4.7
*
* @param int $event_id
* @param bool $url_only
*
* @return string
*/
public function get_event_reports_link( $event_id, $url_only = false ) {
$ticket_ids = (array) $this->get_tickets_ids( $event_id );
if ( empty( $ticket_ids ) ) {
return '';
}
$query = array(
'page' => 'tpp-orders',
'post_id' => $event_id,
);
$report_url = add_query_arg( $query, admin_url( 'admin.php' ) );
/**
* Filter the PayPal Ticket Orders (Sales) Report URL
*
* @var string $report_url Report URL
* @var int $event_id The post ID
* @var array $ticket_ids An array of ticket IDs
*
* @return string
*/
$report_url = apply_filters( 'tribe_tickets_paypal_report_url', $report_url, $event_id, $ticket_ids );
return $url_only
? $report_url
: '<small> <a href="' . esc_url( $report_url ) . '">' . esc_html__( 'Sales report', 'event-tickets' ) . '</a> </small>';
}
/**
* Links to the sales report for this product.
*
* @since 4.7
*
* @param $event_id
* @param $ticket_id
*
* @return string
*/
public function get_ticket_reports_link( $event_id, $ticket_id ) {
if ( empty( $ticket_id ) ) {
return '';
}
$query = array(
'page' => 'tpp-orders',
'product_ids' => $ticket_id,
'post_id' => $event_id,
);
$report_url = add_query_arg( $query, admin_url( 'admin.php' ) );
return '<span><a href="' . esc_url( $report_url ) . '">' . esc_html__( 'Report', 'event-tickets' ) . '</a></span>';
}
/**
* Add the sku field in the admin's new/edit ticket metabox
*
* @since 4.7
*
* @param $post_id int id of the event post
* @param int $ticket_id (null) id of the ticket
*
* @return void
*/
public function do_metabox_sku_options( $post_id, $ticket_id = null ) {
$sku = '';
$is_correct_provider = tribe( 'tickets.handler' )->is_correct_provider( $post_id, $this );
if ( ! empty( $ticket_id ) ) {
$ticket = $this->get_ticket( $post_id, $ticket_id );
$is_correct_provider = tribe( 'tickets.handler' )->is_correct_provider( $ticket_id, $this );
if ( ! empty( $ticket ) ) {
$sku = get_post_meta( $ticket_id, '_sku', true );
}
}
// Bail when we are not dealing with this provider
if ( ! $is_correct_provider ) {
return;
}
include $this->plugin_path . 'src/admin-views/tpp-metabox-sku.php';
}
/**
* Renders the advanced fields in the new/edit ticket form.
* Using the method, providers can add as many fields as
* they want, specific to their implementation.
*
* @since 4.7
*
* @param int $post_id
* @param int $ticket_id
*/
public function do_metabox_advanced_options( $post_id, $ticket_id ) {
$provider = __CLASS__;
echo '<div id="' . sanitize_html_class( $provider ) . '_advanced" class="tribe-dependent" data-depends="#' . sanitize_html_class( $provider ) . '_radio" data-condition-is-checked>';
if ( ! tribe_is_frontend() ) {
$this->do_metabox_sku_options( $post_id, $ticket_id );
}
/**
* Allows for the insertion of additional content into the ticket edit form - advanced section
*
* @since 4.6
*
* @param int Post ID
* @param string the provider class name
*/
do_action( 'tribe_events_tickets_metabox_edit_ajax_advanced', $post_id, $provider );
echo '</div>';
}
/**
* Gets ticket messages
*
* @since 4.7
*
* @return array
*/
public function get_messages() {
return self::$messages;
}
/**
* Adds a submission message
*
* @since 4.7
*
* @param $message
* @param string $type
*/
public function add_message( $message, $type = 'update' ) {
$message = apply_filters( 'tribe_tpp_submission_message', $message, $type );
self::$messages[] = (object) array( 'message' => $message, 'type' => $type );
}
/**
* If the post that was moved to the trash was an PayPal Ticket attendee post type, redirect to
* the Attendees Report rather than the PayPal Ticket attendees post list (because that's kind of
* confusing)
*
* @since 4.7
*
* @param int $post_id WP_Post ID
*/
public function maybe_redirect_to_attendees_report( $post_id ) {
$post = get_post( $post_id );
if ( $this->attendee_object !== $post->post_type ) {
return;
}
$args = array(
'post_type' => 'tribe_events',
'page' => Tribe__Tickets__Tickets_Handler::$attendees_slug,
'event_id' => get_post_meta( $post_id, $this->attendee_event_key, true ),
);
$url = add_query_arg( $args, admin_url( 'edit.php' ) );
$url = esc_url_raw( $url );
wp_redirect( $url );
tribe_exit();
}
/**
* Filters the post_updated_messages array for attendees
*
* @since 4.7
*
* @param array $messages Array of update messages
*
* @return array
*/
public function updated_messages( $messages ) {
$ticket_post = get_post();
if ( ! $ticket_post ) {
return $messages;
}
$post_type = get_post_type( $ticket_post );
if ( $this->attendee_object !== $post_type ) {
return $messages;
}
$event = $this->get_event_for_ticket( $ticket_post );
$attendees_report_url = add_query_arg(
array(
'post_type' => $event->post_type,
'page' => Tribe__Tickets__Tickets_Handler::$attendees_slug,
'event_id' => $event->ID,
),
admin_url( 'edit.php' )
);
$return_link = sprintf(
esc_html__( 'Return to the %1$sAttendees Report%2$s.', 'event-tickets' ),
"<a href='" . esc_url( $attendees_report_url ) . "'>",
'</a>'
);
$messages[ $this->attendee_object ] = $messages['post'];
$messages[ $this->attendee_object ][1] = sprintf(
esc_html__( 'Post updated. %1$s', 'event-tickets' ),
$return_link
);
$messages[ $this->attendee_object ][6] = sprintf(
esc_html__( 'Post published. %1$s', 'event-tickets' ),
$return_link
);
$messages[ $this->attendee_object ][8] = esc_html__( 'Post submitted.', 'event-tickets' );
$messages[ $this->attendee_object ][9] = esc_html__( 'Post scheduled.', 'event-tickets' );
$messages[ $this->attendee_object ][10] = esc_html__( 'Post draft updated.', 'event-tickets' );
return $messages;
}
/**
* Set the tickets view
*
* @since 4.7
*
* @param Tribe__Tickets__Commerce__PayPal__Tickets_View $tickets_view
*
* @internal Used for dependency injection.
*/
public function set_tickets_view( Tribe__Tickets__Commerce__PayPal__Tickets_View $tickets_view ) {
$this->tickets_view = $tickets_view;
}
/**
* Get's the product price html
*
* @since 4.7
*
* @param int|object $product
* @param array $attendee
*
* @return string
*/
public function get_price_html( $product, $attendee = false ) {
$product_id = $product;
if ( $product instanceof WP_Post ) {
$product_id = $product->ID;
} elseif ( is_numeric( $product_id ) ) {
$product = get_post( $product_id );
} else {
return '';
}
$price = get_post_meta( $product_id, '_price', true );
$price = tribe( 'tickets.commerce.paypal.currency' )->format_currency( $price, $product_id );
$price_html = '<span class="tribe-tickets-price-amount amount">' . esc_html( $price ) . '</span>';
/**
* Allow filtering of the Price HTML
*
* @since 4.7
*
* @param string $price_html
* @param mixed $product
* @param mixed $attendee
*
*/
return apply_filters( 'tribe_tickets_tpp_ticket_price_html', $price_html, $product, $attendee );
}
/**
* Filters the array of statuses that will mark an ticket attendee as eligible for check-in.
*
* @since 4.7
*
* @param array $statuses An array of statuses that should mark an ticket attendee as
* available for check-in.
*
* @return array The original array plus the 'yes' status.
*/
public function filter_event_tickets_attendees_tpp_checkin_stati( array $statuses = array() ) {
$statuses[] = 'completed';
return array_unique( $statuses );
}
/**
* Gets the cart URL
*
* @since 4.7
*
* @return string
*/
public function get_cart_url() {
return tribe( 'tickets.commerce.paypal.gateway' )->get_cart_url();
}
/**
* Gets a transaction URL
*
* @since 4.7
*
* @param $transaction
*
* @return string
*/
public function get_transaction_url( $transaction ) {
return tribe( 'tickets.commerce.paypal.gateway' )->get_transaction_url( $transaction );
}
/**
* Returns the value of a key defined by the class.
*
* @since 4.7
*
* @param string $key
*
* @return string The key value or an empty string if not defined.
*/
public static function get_key( $key ) {
$instance = self::get_instance();
$key = strtolower( $key );
$constant_map = array(
'attendee_event_key' => $instance->attendee_event_key,
'attendee_product_key' => $instance->attendee_product_key,
'attendee_order_key' => $instance->attendee_order_key,
'attendee_optout_key' => $instance->attendee_optout_key,
'attendee_tpp_key' => $instance->attendee_tpp_key,
'event_key' => $instance->event_key,
'checkin_key' => $instance->checkin_key,
'order_key' => $instance->order_key,
);
return Tribe__Utils__Array::get( $constant_map, $key, '' );
}
/**
* Returns the ID of the post associated with a PayPal order if any.
*
* @since 4.7
*
* @param string $order The alphanumeric order identification string.
*
* @return int|false Either the ID of the post associated with the order or `false` on failure.
*/
public function get_post_id_from_order( $order ) {
if ( empty( $order ) ) {
return false;
}
global $wpdb;
$post_id = $wpdb->get_var( $wpdb->prepare(
"SELECT m2.meta_value
FROM {$wpdb->postmeta} m1
JOIN {$wpdb->postmeta} m2
ON m1.post_id = m2.post_id
WHERE m1.meta_key = %s
AND m1.meta_value = %s
AND m2.meta_key = %s",
$this->order_key, $order, $this->attendee_event_key )
);
return empty( $post_id ) ? false : $post_id;
}
/**
* Whether the ticket is a PayPal one or not.
*
* @since 4.7
*
* @param Tribe__Tickets__Ticket_Object $ticket
*
* @return bool
*/
public function is_paypal_ticket( Tribe__Tickets__Ticket_Object $ticket ) {
return $ticket->provider_class === __CLASS__;
}
/**
* Returns a list of attendees grouped by order.
*
* @since 4.7
*
* @param int $post_id
* @param array $ticket_ids An optional array of ticket IDs to limit the orders by.
*
* @return array An associative array in the format [ <order_number> => <order_details> ]
*/
public function get_orders_by_post_id( $post_id, array $ticket_ids = null, $args = array() ) {
$find_by_args = wp_parse_args( $args, array(
'post_id' => $post_id,
'ticket_id' => $ticket_ids,
) );
$orders = Tribe__Tickets__Commerce__PayPal__Order::find_by( $find_by_args );
$found = array();
$statuses = $this->get_order_statuses();
if ( ! empty( $orders ) ) {
/** @var Tribe__Tickets__Commerce__PayPal__Order $order */
foreach ( $orders as $order ) {
$order_id = $order->paypal_id();
$status = $order->get_status();
$attendees = $order->get_attendees();
$refund_order_id = $order->get_refund_order_id();
$found[ $order_id ] = array(
'url' => $this->get_transaction_url( $order_id ),
'number' => $order_id,
'status' => $status,
'status_label' => Tribe__Utils__Array::get( $statuses, $status, Tribe__Tickets__Commerce__PayPal__Stati::$undefined ),
'purchaser_name' => $order->get_meta( 'address_name' ),
'purchaser_email' => $order->get_meta( 'payer_email' ),
'purchase_time' => $order->get_meta( 'payment_date' ),
'attendees' => $attendees,
'items' => $order->get_meta( 'items' ),
'line_total' => $order->get_line_total(),
);
if ( ! empty( $refund_order_id ) ) {
$found[ $order_id ]['refund_number'] = $refund_order_id;
$found[ $order_id ]['refund_url'] = $this->get_transaction_url( $refund_order_id );
}
}
}
return $found;
}
/**
* Returns the list of PayPal tickets order stati.
*
* @since 4.7
*
* @return array An associative array in the [ <slug> => <label> ] format.
*/
public function get_order_statuses() {
$statuses = tribe( 'tickets.status' )->get_all_provider_statuses( 'tpp' );
$order_statuses = array();
foreach ( $statuses as $status ) {
$order_statuses[ $status->provider_name ] = _x( $status->name, 'a PayPal ticket order status', 'event-tickets' );
}
/**
* Filters the list of PayPal tickets order stati.
*
* @since 4.7
*
* @param array $order_statuses
*
* @return array An associative array in the [ <slug> => <label> ] format.
*/
return apply_filters( 'tribe_tickets_commerce_paypal_order_stati', $order_statuses );
}
/**
* If product cache parameter is found, delete saved products from temporary cart.
*
* @filter wp_loaded 0
*
* @since 4.9
*/
public function maybe_delete_expired_products() {
$delete = tribe_get_request_var( 'clear_product_cache', null );
if ( empty( $delete ) ) {
return;
}
delete_transient( $this->get_current_cart_transient() );
// Bail if ET+ is not in place
if ( ! class_exists( 'Tribe__Tickets_Plus__Meta__Storage' ) ) {
return;
}
$storage = new Tribe__Tickets_Plus__Meta__Storage();
$storage->delete_cookie();
}
/**
* Redirect to attendees meta screen before loading Paypal.
*
* @filter wp_loaded 1
*
* @since 4.9
*
* @param string $redirect
*/
public function maybe_redirect_to_attendees_registration_screen( $redirect = null ) {
if ( ! $this->is_checkout_page() ) {
return;
}
if ( tribe( 'tickets.attendee_registration' )->is_on_page() ) {
return;
}
if ( $_POST ) {
return;
}
$redirect = tribe_get_request_var( 'tribe_tickets_redirect_to', null );
$redirect = base64_encode( $redirect );
parent::maybe_redirect_to_attendees_registration_screen( $redirect );
}
/**
* Returns if it's TPP checkout based on the redirect query var
*
* @since 4.9
*
* @return bool
*/
public function is_checkout_page() {
if ( is_admin() ) {
return false;
}
$redirect = tribe_get_request_var( 'tribe_tickets_redirect_to', null );
return ! empty( $redirect );
}
/**
* Get the tickets currently in the cart.
*
* @since 4.9
*
* @param array $tickets
*
* @return array
*/
public function get_tickets_in_cart( $tickets ) {
$contents = get_transient( $this->get_current_cart_transient() );
if ( empty( $contents ) ) {
return $tickets;
}
foreach ( $contents as $id => $quantity ) {
$event_check = get_post_meta( $id, $this->event_key, true );
if ( empty( $event_check ) ) {
continue;
}
$tickets[ $id ] = $quantity;
}
return $tickets;
}
/**
* Get the current cart Transient key.
*
* @since 4.9
*
* @return string
*/
private function get_current_cart_transient() {
$cart = tribe( 'tickets.commerce.paypal.cart' );
$invoice = Tribe__Utils__Array::get(
$_COOKIE, Tribe__Tickets__Commerce__PayPal__Gateway::$invoice_cookie_name,
false
);
$cart_class = get_class( $cart );
return call_user_func( array( $cart_class, 'get_transient_name' ), $invoice );
}
/**
* Returns all the tickets for an event
*
* @since 4.7
*
* @param int $post_id
*
* @return array
*/
public function get_tickets( $post_id ) {
$ticket_ids = $this->get_tickets_ids( $post_id );
if ( ! $ticket_ids ) {
return array();
}
$tickets = array();
foreach ( $ticket_ids as $post ) {
$tickets[] = $this->get_ticket( $post_id, $post );
}
return $tickets;
}
/**
* Renders the advanced fields in the new/edit ticket form.
* Using the method, providers can add as many fields as
* they want, specific to their implementation.
*
* @since 4.7
*
* @param int $post_id
* @param int $ticket_id
*
* @return mixed
*/
public function do_metabox_capacity_options( $post_id, $ticket_id ) {
$is_correct_provider = tribe( 'tickets.handler' )->is_correct_provider( $post_id, $this );
$url = '';
$stock = '';
$global_stock_mode = tribe( 'tickets.handler' )->get_default_capacity_mode();
$global_stock_cap = 0;
$ticket_capacity = null;
$post_capacity = null;
$stock_object = new Tribe__Tickets__Global_Stock( $post_id );
if ( $stock_object->is_enabled() ) {
$post_capacity = tribe_tickets_get_capacity( $post_id );
}
if ( ! empty( $ticket_id ) ) {
$ticket = $this->get_ticket( $post_id, $ticket_id );
$is_correct_provider = tribe( 'tickets.handler' )->is_correct_provider( $ticket_id, $this );
if ( ! empty( $ticket ) ) {
$stock = $ticket->managing_stock() ? $ticket->stock() : '';
$ticket_capacity = tribe_tickets_get_capacity( $ticket->ID );
$global_stock_mode = ( method_exists( $ticket, 'global_stock_mode' ) ) ? $ticket->global_stock_mode() : '';
$global_stock_cap = ( method_exists( $ticket, 'global_stock_cap' ) ) ? $ticket->global_stock_cap() : 0;
}
}
// Bail when we are not dealing with this provider
if ( ! $is_correct_provider ) {
return;
}
$file = Tribe__Tickets__Main::instance()->plugin_path . 'src/admin-views/tpp-metabox-capacity.php';
/**
* Filters the absolute path to the file containing the metabox capacity HTML.
*
* @since 4.7
*
* @param string $file The absolute path to the file containing the metabox capacity HTML
* @param int|string $ticket_capacity
* @param int|string $post_capacity
*/
$file = apply_filters( 'tribe_tickets_tpp_metabox_capacity_file', $file, $ticket_capacity, $post_capacity );
if ( file_exists( $file ) ) {
include $file;
}
}
/**
* Indicates if global stock support is enabled for this provider.
*
* @since 4.7
*
* @return bool
*/
public function supports_global_stock() {
/**
* Allows the declaration of global stock support for WooCommerce tickets
* to be overridden.
*
* @param bool $enable_global_stock_support
*/
return (bool) apply_filters( 'tribe_tickets_tpp_enable_global_stock', true );
}
/**
* Gets the product price value
*
* @since 4.7
*
* @param int|WP_Post $product
*
* @return string
*/
public function get_price_value( $product ) {
if ( ! $product instanceof WP_Post ) {
$product = get_post( $product );
}
if ( ! $product instanceof WP_Post ) {
return false;
}
return get_post_meta( $product->ID, '_price', true );
}
/**
* Returns the number of pending attendees by ticket.
*
* @since 4.7
*
* @param int $ticket_id The ticket post ID
* @param bool $refresh Whether to try and use the cached value or not.
*
* @return int
*/
public function get_qty_pending( $ticket_id, $refresh = false ) {
if ( $refresh || empty( $this->pending_attendees_by_ticket[ $ticket_id ] ) ) {
$pending_query = new WP_Query( array(
'fields' => 'ids',
'per_page' => 1,
'post_type' => self::ATTENDEE_OBJECT,
'meta_query' => array(
array(
'key' => self::ATTENDEE_PRODUCT_KEY,
'value' => $ticket_id,
),
'relation' => 'AND',
array(
'key' => $this->attendee_tpp_key,
'value' => Tribe__Tickets__Commerce__PayPal__Stati::$pending,
),
),
) );
$this->pending_attendees_by_ticket[ $ticket_id ] = $pending_query->found_posts;
}
return $this->pending_attendees_by_ticket[ $ticket_id ];
}
/**
* Whether a specific attendee is valid toward inventory decrease or not.
*
* By default only attendees generated as part of a Completed order will count toward
* an inventory decrease but, if the option to reserve stock for Pending Orders is activated,
* then those attendees generated as part of a Pending Order will, for a limited time after the
* order creation, cause the inventory to be decreased.
*
* @since 4.7
*
* @param array $attendee
*
* @return bool
*/
public function attendee_decreases_inventory( array $attendee ) {
$order_status = Tribe__Utils__Array::get( $attendee, 'order_status', 'undefined' );
$order_id = Tribe__Utils__Array::get( $attendee, 'order_id', false );
/**
* Whether the pending Order stock reserve logic should be ignored completely or not.
*
* If set to `true` then the behaviour chosen in the Settings will apply, if `false`
* only Completed tickets will count to decrease the inventory. This is useful when
*
* @since 4.7
*
* @param bool $ignore_pending
* @param array $attendee An array of data defining the current Attendee
*/
$ignore_pending = apply_filters( 'tribe_tickets_tpp_pending_stock_ignore', $this->ignore_pending_stock_logic );
if (
'on-pending' === tribe_get_option( 'ticket-paypal-stock-handling', 'on-complete' )
&& ! $ignore_pending
&& Tribe__Tickets__Commerce__PayPal__Stati::$pending === $order_status
&& false !== $order_id
&& false !== $order = Tribe__Tickets__Commerce__PayPal__Order::from_attendee_id( $order_id )
) {
/** @var \Tribe__Tickets__Commerce__PayPal__Order $order */
$order_creation_timestamp = Tribe__Date_Utils::wp_strtotime( $order->get_creation_date() );
/**
* Filters the amount of time a part of the stock will be reserved by a pending Order.
*
* The time applies from the Order creation time.
* In the unlikely scenario that an Order goes from Completed to Pending then, if the
* reservation time allows it, a part of the stock will be reserved for it.
*
* @since 4.7
*
* @param int $pending_stock_reservation_time The amount of seconds, from the Order creation time,
* part of the stock will be reserved for the Order;
* defaults to 30 minutes.
* @param array $attendee An array of data defining the current Attendee
* @param Tribe__Tickets__Commerce__PayPal__Order $order The object representing the Order that generated
* the Attendee
*/
$pending_stock_reservation_time = (int) apply_filters( 'tribe_tickets_tpp_pending_stock_reserve_time', 30 * 60, $attendee, $order );
return current_time( 'timestamp' ) <= ( $order_creation_timestamp + $pending_stock_reservation_time );
}
return Tribe__Tickets__Commerce__PayPal__Stati::$completed === $order_status;
}
/**
* Update Stock and Global Stock when deleting an Attendee
*
* @since 4.10.5
*
* @param int $ticket_id the attendee id being deleted
* @param int $post_id the post or event id for the attendee
* @param int $product_id the ticket-product id in Tribe Commerce
*/
public function update_stock_after_deletion( $ticket_id, $post_id, $product_id ) {
$global_stock = new Tribe__Tickets__Global_Stock( $post_id );
$shared_capacity = false;
if ( $global_stock->is_enabled() ) {
$shared_capacity = true;
}
$this->decrease_ticket_sales_by( $product_id, 1, $shared_capacity, $global_stock );
}
/**
* Increases the sales for a ticket by an amount.
*
* @since 4.7
* @since 4.10.2 added $shared_capacity and $global_stock parameter
*
* @param int $ticket_id The ticket post ID
* @param int $qty the quanitity to modify stock
* @param bool $shared_capacity true or false if the ticket is using share capacity
* @param object|null $global_stock the object of Tribe__Tickets__Global_Stock or null
*
* @return int
*/
public function increase_ticket_sales_by( $ticket_id, $qty = 1, $shared_capacity = false, $global_stock = null ) {
$sales = (int) get_post_meta( $ticket_id, 'total_sales', true );
update_post_meta( $ticket_id, 'total_sales', $sales + $qty );
if ( $shared_capacity && $global_stock instanceof Tribe__Tickets__Global_Stock ) {
$this->update_global_stock( $global_stock, $qty );
}
return $sales;
}
/**
* Decreases the sales for a ticket by an amount.
*
* @since 4.7
* @since 4.10.2 added $shared_capacity and $global_stock parameter
*
* @param int $ticket_id The ticket post ID
* @param int $qty the quanitity to modify stock
* @param bool $shared_capacity true or false if the ticket is using share capacity
* @param object|null $global_stock the object of Tribe__Tickets__Global_Stock or null
*
* @return int
*/
public function decrease_ticket_sales_by( $ticket_id, $qty = 1, $shared_capacity = false, $global_stock = null ) {
$sales = (int) get_post_meta( $ticket_id, 'total_sales', true );
update_post_meta( $ticket_id, 'total_sales', max( $sales - $qty, 0 ) );
if ( $shared_capacity && $global_stock instanceof Tribe__Tickets__Global_Stock ) {
$this->update_global_stock( $global_stock, $qty, true );
}
}
/**
* Update Global Stock
*
* @since 4.10.2
*
* @param Tribe__Tickets__Global_Stock $global_stock The global stock object.
* @param int $qty The quantity to modify stock.
* @param bool $increase Whether to increase stock, default is false.
*/
public function update_global_stock( $global_stock, $qty = 1, $increase = false ) {
$level = $global_stock->get_stock_level();
if ( $increase ) {
$new_level = (int) $level + (int) $qty;
} else {
$new_level = (int) $level - (int) $qty;
}
$global_stock->set_stock_level( $new_level );
}
/**
* {@inheritdoc}
*/
public function get_attendee( $attendee, $post_id = 0 ) {
if ( is_numeric( $attendee ) ) {
$attendee = get_post( $attendee );
}
if ( ! $attendee instanceof WP_Post || self::ATTENDEE_OBJECT !== $attendee->post_type ) {
return false;
}
$checkin = get_post_meta( $attendee->ID, $this->checkin_key, true );
$security = get_post_meta( $attendee->ID, $this->security_code, true );
$order_id = get_post_meta( $attendee->ID, $this->order_key, true );
$product_id = get_post_meta( $attendee->ID, $this->attendee_product_key, true );
$optout = (bool) get_post_meta( $attendee->ID, $this->attendee_optout_key, true );
$status = get_post_meta( $attendee->ID, $this->attendee_tpp_key, true );
$user_id = get_post_meta( $attendee->ID, $this->attendee_user_id, true );
$ticket_sent = (bool) get_post_meta( $attendee->ID, $this->attendee_ticket_sent, true );
if ( empty( $product_id ) ) {
return false;
}
$product = get_post( $product_id );
$product_title = ( ! empty( $product ) ) ? $product->post_title : get_post_meta( $attendee->ID, $this->deleted_product, true ) . ' ' . __( '(deleted)', 'event-tickets' );
$ticket_unique_id = get_post_meta( $attendee->ID, '_unique_id', true );
$ticket_unique_id = $ticket_unique_id === '' ? $attendee->ID : $ticket_unique_id;
$meta = '';
if ( class_exists( 'Tribe__Tickets_Plus__Meta', false ) ) {
$meta = get_post_meta( $attendee->ID, Tribe__Tickets_Plus__Meta::META_KEY, true );
// Process Meta to include value, slug, and label
if ( ! empty( $meta ) ) {
$meta = $this->process_attendee_meta( $product_id, $meta );
}
}
$attendee_data = array_merge( $this->get_order_data( $attendee->ID ), array(
'optout' => $optout,
'ticket' => $product_title,
'attendee_id' => $attendee->ID,
'security' => $security,
'product_id' => $product_id,
'check_in' => $checkin,
'order_status' => $status,
'user_id' => $user_id,
'ticket_sent' => $ticket_sent,
// this is used to find existing attendees
'post_title' => $attendee->post_title,
// Fields for Email Tickets
'event_id' => get_post_meta( $attendee->ID, $this->attendee_event_key, true ),
'ticket_name' => ! empty( $product ) ? $product->post_title : false,
'holder_name' => get_post_meta( $attendee->ID, $this->full_name, true ),
'holder_email' => get_post_meta( $attendee->ID, $this->email, true ),
'order_id' => $attendee->ID,
'order_hash' => $order_id,
'ticket_id' => $ticket_unique_id,
'qr_ticket_id' => $attendee->ID,
'security_code' => $security,
// Attendee Meta
'attendee_meta' => $meta,
) );
/**
* Allow users to filter the Attendee Data
*
* @since 4.7
*
* @param array $attendee_data An associative array with the Information of the Attendee
* @param string $provider What Provider is been used
* @param WP_Post $attendee Attendee Object
*
*/
$attendee_data = apply_filters( 'tribe_tickets_attendee_data', $attendee_data, 'tpp', $attendee );
return $attendee_data;
}
/**
* Returns the total number of cancelled tickets.
*
* @since 4.7
*
* @param int $ticket_id The ticket post ID.
*
* @return int
*/
protected function get_cancelled( $ticket_id ) {
$denied_orders = Tribe__Tickets__Commerce__PayPal__Order::find_by( array(
'ticket_id' => $ticket_id,
'post_status' => Tribe__Tickets__Commerce__PayPal__Stati::$denied,
'posts_per_page' => -1,
) );
$denied = 0;
foreach ( $denied_orders as $denied_order ) {
$denied += $denied_order->get_item_quantity( $ticket_id );
}
return max( 0, $denied );
}
/**
* Whether the Pending Order stock reservation logic should be ignored or
* not, no matter the Settings.
*
* This is useful when trying to get the "true" inventory of a ticket.
*
* @param bool $ignore_pending_stock_logic
*
* @see Tribe__Tickets__Commerce__PayPal__Main::attendee_decreases_inventory
*/
public function ignore_pending_stock_logic( $ignore_pending_stock_logic ) {
$this->ignore_pending_stock_logic = (bool) $ignore_pending_stock_logic;
}
/**
* Redirects to the source post after a recoverable (logic) error.
*
* @since 4.7
*
* @param int $error_code The current error code
* @param bool $redirect Whether to really redirect or not.
* @param int $post_id A post ID
*
* @return string
*
* @see Tribe__Tickets__Commerce__PayPal__Errors for error codes translations.
*/
protected function redirect_after_error( $error_code, $redirect, $post_id ) {
$url = add_query_arg( 'tpp_error', $error_code, get_permalink( $post_id ) );
if ( $redirect ) {
wp_redirect( esc_url_raw( $url ) );
}
tribe_exit();
}
/**
* Generates the validation code that will be printed in the ticket.
*
* Its purpose is to be used to validate the ticket at the door of an event.
*
* @since 4.7
*
* @param int $attendee_id
*
* @return string
*/
public function generate_security_code( $attendee_id ) {
return substr( md5( rand() . '_' . $attendee_id ), 0, 10 );
}
/**
* If other modules are active, we should deprioritize this one (we want other commerce
* modules to take priority over this one).
*
* @since 4.7.1
*
* @param string $default_module
* @param string[] $available_modules
*
* @return string
*/
public function deprioritize_module( $default_module, array $available_modules ) {
$tribe_commerce_module = get_class( $this );
// If this isn't the default (or if there isn't a choice), no need to deprioritize
if (
$default_module !== $tribe_commerce_module
|| count( $available_modules ) < 2
|| reset( $available_modules ) !== $tribe_commerce_module
) {
return $default_module;
}
return next( $available_modules );
}
/**
* Add our class to the list of classes for the attendee registartion form
*
* @since 4.10.4
*
* @param array $classes existing array of classes
*
* @return array $classes with our class added
*/
public function tribe_attendee_registration_form_class( $classes ) {
$classes[ $this->attendee_object ] = 'tpp';
return $classes;
}
}
Changelog
| Version | Description |
|---|---|
| 4.7 | Introduced. |
Methods
- __construct — Class constructor
- add_admin_tickets_hooks
- add_cart_url — Adds cart url to list used for localized variables.
- add_checkout_url — Adds checkout url to list used for localized variables.
- add_message — Adds a submission message
- add_ticket_to_cart — Handles the process of adding a ticket product to the cart.
- attendance_totals — Get the attendance totals instance.
- attendee_decreases_inventory — Whether a specific attendee is valid toward inventory decrease or not.
- bind_implementations — Registers the implementations in the container
- commerce_get_cart_url — Get Tribe Commerce Cart URL for Commerce.
- commerce_get_checkout_url — Get Tribe Commerce Checkout URL for Commerce.
- commerce_get_tickets_in_cart — Get all tickets currently in the cart for Commerce.
- commerce_update_tickets_in_cart — Update tickets in Tribe Commerce cart for Commerce.
- decrease_ticket_sales_by — Decrease the sales for a ticket by a specific quantity.
- delete_ticket — Deletes a ticket
- deprioritize_module — If other modules are active, we should deprioritize this one (we want other commerce modules to take priority over this one).
- do_metabox_advanced_options — Renders the advanced fields in the new/edit ticket form.
- do_metabox_capacity_options — Renders the advanced fields in the new/edit ticket form.
- do_metabox_sku_options — Add the sku field in the admin's new/edit ticket metabox
- enqueue_resources — Enqueue the plugin admin stylesheet(s) and JS.
- filter_cache_listener_save_post_types — Filters the list of post types that should trigger a cache invalidation on `save_post` to add all the ones modeling PayPal Tickets, Attendees and Orders.
- filter_event_tickets_attendees_tpp_checkin_stati — Filters the array of statuses that will mark an ticket attendee as eligible for check-in.
- front_end_tickets_form — Shows the tickets form in the front end
- generate_security_code — Generates the validation code that will be printed in the ticket.
- generate_tickets — Generate and store all the attendees information for a new order.
- get_attendee — {@inheritdoc}
- get_attendees_by_id — Get attendees by id and associated post type or default to using $post_id
- get_attendees_by_order_id — {@inheritdoc}
- get_cart_url — Get Tribe Commerce Cart URL.
- get_checkout_url — Get Tribe Commerce Checkout URL.
- get_event_reports_link — Links to sales report for all tickets for this event.
- get_instance — Get (and instantiate, if necessary) the instance of the class
- get_key — Returns the value of a key defined by the class.
- get_messages — Gets ticket messages
- get_order_data — Retrieve only order related information Important: On PayPal Ticket the order is the Attendee Object
- get_order_statuses — Returns the list of PayPal tickets order stati.
- get_orders_by_post_id — Returns a list of attendees grouped by order.
- get_post_id_from_order — Returns the ID of the post associated with a PayPal order if any.
- get_price_html — Get's the product price html
- get_price_value — Gets the product price value
- get_qty_pending — Returns the number of pending attendees by ticket.
- get_ticket — Gets an individual ticket
- get_ticket_reports_link — Links to the sales report for this product.
- get_tickets — {@inheritDoc}
- get_tickets_in_cart — Get the tickets currently in the cart.
- get_transaction_url — Gets a transaction URL
- hooks — Registers all actions/filters
- ignore_pending_stock_logic — Whether the Pending Order stock reservation logic should be ignored or not, no matter the Settings.
- increase_ticket_sales_by — Increase the sales for a ticket by a specific quantity.
- init — Hooked to the init action
- is_active — Whether PayPal tickets will be available as a provider or not.
- is_checkout_page — Returns if it's TPP checkout based on the redirect query var
- is_paypal_ticket — Whether the ticket is a PayPal one or not.
- login_required — Indicates if we currently require users to be logged in before they can obtain tickets.
- maybe_delete_expired_products — If product cache parameter is found, delete saved products from temporary cart.
- maybe_redirect_to_attendees_registration_screen — Redirect to attendees meta screen before loading Paypal.
- maybe_redirect_to_attendees_report — If the post that was moved to the trash was an PayPal Ticket attendee post type, redirect to the Attendees Report rather than the PayPal Ticket attendees post list (because that's kind of confusing)
- maybe_send_tickets_after_status_change — Triggers the sending of ticket emails after PayPal Ticket information is updated.
- register_resources — registers resources
- register_types — Register our custom post type
- save_ticket — Saves a Tribe Commerce ticket.
- send_tickets_email — Sends ticket email
- send_tickets_email_for_attendees — Send RSVPs/tickets email for attendees.
- set_tickets_view — Set the tickets view
- setup_attendance_totals — Adds Tribe Commerce attendance totals to the summary box of the attendance screen.
- supports_global_stock — Indicates if global stock support is enabled for this provider.
- tribe_attendee_registration_cart_provider — Filter the provider object to return this class if tickets are for this provider.
- tribe_attendee_registration_form_class — Add our class to the list of classes for the attendee registartion form
- update_attendee_data — Update the PayPalTicket values for this user.
- update_global_stock — Update Global Stock
- update_stock_after_deletion — Update Stock and Global Stock when deleting an Attendee
- updated_messages — Filters the post_updated_messages array for attendees