Completed
Branch BUG/11268/session-ticket-relea... (ea1fef)
by
unknown
30:16 queued 12:59
created
modules/ticket_sales_monitor/EED_Ticket_Sales_Monitor.module.php 2 patches
Indentation   +979 added lines, -979 removed lines patch added patch discarded remove patch
@@ -3,7 +3,7 @@  discard block
 block discarded – undo
3 3
 use EventEspresso\core\exceptions\UnexpectedEntityException;
4 4
 
5 5
 if (! defined('EVENT_ESPRESSO_VERSION')) {
6
-    exit('No direct script access allowed');
6
+	exit('No direct script access allowed');
7 7
 }
8 8
 
9 9
 
@@ -22,985 +22,985 @@  discard block
 block discarded – undo
22 22
 class EED_Ticket_Sales_Monitor extends EED_Module
23 23
 {
24 24
 
25
-    const debug = false;    //	true false
26
-
27
-    /**
28
-     * an array of raw ticket data from EED_Ticket_Selector
29
-     *
30
-     * @var array $ticket_selections
31
-     */
32
-    protected $ticket_selections = array();
33
-
34
-    /**
35
-     * the raw ticket data from EED_Ticket_Selector is organized in rows
36
-     * according to how they are displayed in the actual Ticket_Selector
37
-     * this tracks the current row being processed
38
-     *
39
-     * @var int $current_row
40
-     */
41
-    protected $current_row = 0;
42
-
43
-    /**
44
-     * an array for tracking names of tickets that have sold out
45
-     *
46
-     * @var array $sold_out_tickets
47
-     */
48
-    protected $sold_out_tickets = array();
49
-
50
-    /**
51
-     * an array for tracking names of tickets that have had their quantities reduced
52
-     *
53
-     * @var array $decremented_tickets
54
-     */
55
-    protected $decremented_tickets = array();
56
-
57
-
58
-
59
-    /**
60
-     * set_hooks - for hooking into EE Core, other modules, etc
61
-     *
62
-     * @return    void
63
-     */
64
-    public static function set_hooks()
65
-    {
66
-        // release tickets for expired carts
67
-        add_action(
68
-            'EED_Ticket_Selector__process_ticket_selections__before',
69
-            array('EED_Ticket_Sales_Monitor', 'release_tickets_for_expired_carts'),
70
-            1
71
-        );
72
-        // check ticket reserves AFTER MER does it's check (hence priority 20)
73
-        add_filter(
74
-            'FHEE__EE_Ticket_Selector___add_ticket_to_cart__ticket_qty',
75
-            array('EED_Ticket_Sales_Monitor', 'validate_ticket_sale'),
76
-            20,
77
-            3
78
-        );
79
-        // add notices for sold out tickets
80
-        add_action(
81
-            'AHEE__EE_Ticket_Selector__process_ticket_selections__after_tickets_added_to_cart',
82
-            array('EED_Ticket_Sales_Monitor', 'post_notices'),
83
-            10
84
-        );
85
-        // handle ticket quantities adjusted in cart
86
-        //add_action(
87
-        //	'FHEE__EED_Multi_Event_Registration__adjust_line_item_quantity__line_item_quantity_updated',
88
-        //	array( 'EED_Ticket_Sales_Monitor', 'ticket_quantity_updated' ),
89
-        //	10, 2
90
-        //);
91
-        // handle tickets deleted from cart
92
-        add_action(
93
-            'FHEE__EED_Multi_Event_Registration__delete_ticket__ticket_removed_from_cart',
94
-            array('EED_Ticket_Sales_Monitor', 'ticket_removed_from_cart'),
95
-            10,
96
-            2
97
-        );
98
-        // handle emptied carts
99
-        add_action(
100
-            'AHEE__EE_Session__reset_cart__before_reset',
101
-            array('EED_Ticket_Sales_Monitor', 'session_cart_reset'),
102
-            10,
103
-            1
104
-        );
105
-        add_action(
106
-            'AHEE__EED_Multi_Event_Registration__empty_event_cart__before_delete_cart',
107
-            array('EED_Ticket_Sales_Monitor', 'session_cart_reset'),
108
-            10,
109
-            1
110
-        );
111
-        // handle cancelled registrations
112
-        add_action(
113
-            'AHEE__EE_Session__reset_checkout__before_reset',
114
-            array('EED_Ticket_Sales_Monitor', 'session_checkout_reset'),
115
-            10,
116
-            1
117
-        );
118
-        // cron tasks
119
-        add_action(
120
-            'AHEE__EE_Cron_Tasks__process_expired_transactions__abandoned_transaction',
121
-            array('EED_Ticket_Sales_Monitor', 'process_abandoned_transactions'),
122
-            10,
123
-            1
124
-        );
125
-        add_action(
126
-            'AHEE__EE_Cron_Tasks__process_expired_transactions__incomplete_transaction',
127
-            array('EED_Ticket_Sales_Monitor', 'process_abandoned_transactions'),
128
-            10,
129
-            1
130
-        );
131
-        add_action(
132
-            'AHEE__EE_Cron_Tasks__process_expired_transactions__failed_transaction',
133
-            array('EED_Ticket_Sales_Monitor', 'process_failed_transactions'),
134
-            10,
135
-            1
136
-        );
137
-    }
138
-
139
-
140
-
141
-    /**
142
-     * set_hooks_admin - for hooking into EE Admin Core, other modules, etc
143
-     *
144
-     * @return void
145
-     */
146
-    public static function set_hooks_admin()
147
-    {
148
-        EED_Ticket_Sales_Monitor::set_hooks();
149
-    }
150
-
151
-
152
-
153
-    /**
154
-     * @return EED_Ticket_Sales_Monitor|EED_Module
155
-     */
156
-    public static function instance()
157
-    {
158
-        return parent::get_instance(__CLASS__);
159
-    }
160
-
161
-
162
-
163
-    /**
164
-     * @param WP_Query $WP_Query
165
-     * @return    void
166
-     */
167
-    public function run($WP_Query)
168
-    {
169
-    }
170
-
171
-
172
-
173
-    /********************************** PRE_TICKET_SALES  **********************************/
174
-
175
-
176
-
177
-    /**
178
-     * Retrieves grand totals from the line items that have no TXN ID
179
-     * and timestamps less than the current time minus the session lifespan.
180
-     * These are carts that have been abandoned before the "registrant" even attempted to checkout.
181
-     * We're going to release the tickets for these line items before attempting to add more to the cart.
182
-     *
183
-     * @return void
184
-     * @throws EE_Error
185
-     * @throws InvalidArgumentException
186
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
187
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
188
-     */
189
-    public static function release_tickets_for_expired_carts()
190
-    {
191
-        do_action('AHEE__EED_Ticket_Sales_Monitor__release_tickets_for_expired_carts__begin');
192
-        $expired_ticket_IDs      = array();
193
-        $valid_ticket_line_items = array();
194
-        $total_line_items        = EEM_Line_Item::instance()->get_total_line_items_with_no_transaction();
195
-        if (empty($total_line_items)) {
196
-            do_action(
197
-                'AHEE__EED_Ticket_Sales_Monitor__release_tickets_for_expired_carts__end',
198
-                $total_line_items,
199
-                $valid_ticket_line_items,
200
-                $expired_ticket_IDs
201
-            );
202
-            return;
203
-        }
204
-        $expired = current_time('timestamp') - EE_Registry::instance()->SSN->lifespan();
205
-        foreach ($total_line_items as $total_line_item) {
206
-            /** @var EE_Line_Item $total_line_item */
207
-            $ticket_line_items = EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total($total_line_item);
208
-            foreach ($ticket_line_items as $ticket_line_item) {
209
-                if (! $ticket_line_item instanceof EE_Line_Item) {
210
-                    continue;
211
-                }
212
-                if ($total_line_item->timestamp(true) <= $expired) {
213
-                    $expired_ticket_IDs[$ticket_line_item->OBJ_ID()] = $ticket_line_item->OBJ_ID();
214
-                } else {
215
-                    $valid_ticket_line_items[$ticket_line_item->OBJ_ID()] = $ticket_line_item;
216
-                }
217
-            }
218
-        }
219
-        if (! empty($expired_ticket_IDs)) {
220
-            EED_Ticket_Sales_Monitor::release_reservations_for_tickets(
221
-                \EEM_Ticket::instance()->get_tickets_with_IDs($expired_ticket_IDs),
222
-                $valid_ticket_line_items
223
-            );
224
-            // let's get rid of expired line items so that they can't interfere with tracking
225
-            add_action(
226
-                'shutdown',
227
-                array('EED_Ticket_Sales_Monitor', 'clear_expired_line_items_with_no_transaction'),
228
-                999
229
-            );
230
-        }
231
-        do_action(
232
-            'AHEE__EED_Ticket_Sales_Monitor__release_tickets_for_expired_carts__end',
233
-            $total_line_items,
234
-            $valid_ticket_line_items,
235
-            $expired_ticket_IDs
236
-        );
237
-    }
238
-
239
-
240
-
241
-    /********************************** VALIDATE_TICKET_SALE  **********************************/
242
-
243
-
244
-
245
-    /**
246
-     * callback for 'FHEE__EED_Ticket_Selector__process_ticket_selections__valid_post_data'
247
-     *
248
-     * @param int       $qty
249
-     * @param EE_Ticket $ticket
250
-     * @return bool
251
-     * @throws UnexpectedEntityException
252
-     * @throws EE_Error
253
-     */
254
-    public static function validate_ticket_sale($qty = 1, EE_Ticket $ticket)
255
-    {
256
-        $qty = absint($qty);
257
-        if ($qty > 0) {
258
-            $qty = EED_Ticket_Sales_Monitor::instance()->_validate_ticket_sale($ticket, $qty);
259
-        }
260
-        if (self::debug) {
261
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '()';
262
-            echo '<br /><br /><b> RETURNED QTY: ' . $qty . '</b>';
263
-        }
264
-        return $qty;
265
-    }
266
-
267
-
268
-
269
-    /**
270
-     * checks whether an individual ticket is available for purchase based on datetime, and ticket details
271
-     *
272
-     * @param   EE_Ticket $ticket
273
-     * @param int         $qty
274
-     * @return int
275
-     * @throws UnexpectedEntityException
276
-     * @throws EE_Error
277
-     */
278
-    protected function _validate_ticket_sale(EE_Ticket $ticket, $qty = 1)
279
-    {
280
-        if (self::debug) {
281
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
282
-        }
283
-        if (! $ticket instanceof EE_Ticket) {
284
-            return 0;
285
-        }
286
-        if (self::debug) {
287
-            echo '<br /><b> . ticket->ID: ' . $ticket->ID() . '</b>';
288
-            echo '<br /> . original ticket->reserved: ' . $ticket->reserved();
289
-        }
290
-        $ticket->refresh_from_db();
291
-        // first let's determine the ticket availability based on sales
292
-        $available = $ticket->qty('saleable');
293
-        if (self::debug) {
294
-            echo '<br /> . . . ticket->qty: ' . $ticket->qty();
295
-            echo '<br /> . . . ticket->sold: ' . $ticket->sold();
296
-            echo '<br /> . . . ticket->reserved: ' . $ticket->reserved();
297
-            echo '<br /> . . . ticket->qty(saleable): ' . $ticket->qty('saleable');
298
-            echo '<br /> . . . available: ' . $available;
299
-        }
300
-        if ($available < 1) {
301
-            $this->_ticket_sold_out($ticket);
302
-            return 0;
303
-        }
304
-        if (self::debug) {
305
-            echo '<br /> . . . qty: ' . $qty;
306
-        }
307
-        if ($available < $qty) {
308
-            $qty = $available;
309
-            if (self::debug) {
310
-                echo '<br /> . . . QTY ADJUSTED: ' . $qty;
311
-            }
312
-            $this->_ticket_quantity_decremented($ticket);
313
-        }
314
-        $this->_reserve_ticket($ticket, $qty);
315
-        return $qty;
316
-    }
317
-
318
-
319
-
320
-    /**
321
-     * increments ticket reserved based on quantity passed
322
-     *
323
-     * @param    EE_Ticket $ticket
324
-     * @param int          $quantity
325
-     * @return bool
326
-     * @throws EE_Error
327
-     */
328
-    protected function _reserve_ticket(EE_Ticket $ticket, $quantity = 1)
329
-    {
330
-        if (self::debug) {
331
-            echo '<br /><br /> . . . INCREASE RESERVED: ' . $quantity;
332
-        }
333
-        $ticket->increase_reserved($quantity);
334
-        return $ticket->save();
335
-    }
336
-
337
-
338
-
339
-    /**
340
-     * @param  EE_Ticket $ticket
341
-     * @param  int       $quantity
342
-     * @return bool
343
-     * @throws EE_Error
344
-     */
345
-    protected function _release_reserved_ticket(EE_Ticket $ticket, $quantity = 1)
346
-    {
347
-        if (self::debug) {
348
-            echo '<br /> . . . ticket->ID: ' . $ticket->ID();
349
-            echo '<br /> . . . ticket->reserved before: ' . $ticket->reserved();
350
-        }
351
-        $ticket->decrease_reserved($quantity);
352
-        if (self::debug) {
353
-            echo '<br /> . . . ticket->reserved after: ' . $ticket->reserved();
354
-        }
355
-        return $ticket->save() ? 1 : 0;
356
-    }
357
-
358
-
359
-
360
-    /**
361
-     * removes quantities within the ticket selector based on zero ticket availability
362
-     *
363
-     * @param    EE_Ticket $ticket
364
-     * @return    void
365
-     * @throws UnexpectedEntityException
366
-     * @throws EE_Error
367
-     */
368
-    protected function _ticket_sold_out(EE_Ticket $ticket)
369
-    {
370
-        if (self::debug) {
371
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
372
-            echo '<br /> . . ticket->name: ' . $this->_get_ticket_and_event_name($ticket);
373
-        }
374
-        $this->sold_out_tickets[] = $this->_get_ticket_and_event_name($ticket);
375
-    }
376
-
377
-
378
-
379
-    /**
380
-     * adjusts quantities within the ticket selector based on decreased ticket availability
381
-     *
382
-     * @param    EE_Ticket $ticket
383
-     * @return void
384
-     * @throws UnexpectedEntityException
385
-     * @throws EE_Error
386
-     */
387
-    protected function _ticket_quantity_decremented(EE_Ticket $ticket)
388
-    {
389
-        if (self::debug) {
390
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
391
-            echo '<br /> . . ticket->name: ' . $this->_get_ticket_and_event_name($ticket);
392
-        }
393
-        $this->decremented_tickets[] = $this->_get_ticket_and_event_name($ticket);
394
-    }
395
-
396
-
397
-
398
-    /**
399
-     * builds string out of ticket and event name
400
-     *
401
-     * @param    EE_Ticket $ticket
402
-     * @return string
403
-     * @throws UnexpectedEntityException
404
-     * @throws EE_Error
405
-     */
406
-    protected function _get_ticket_and_event_name(EE_Ticket $ticket)
407
-    {
408
-        $event = $ticket->get_related_event();
409
-        if ($event instanceof EE_Event) {
410
-            $ticket_name = sprintf(
411
-                _x('%1$s for %2$s', 'ticket name for event name', 'event_espresso'),
412
-                $ticket->name(),
413
-                $event->name()
414
-            );
415
-        } else {
416
-            $ticket_name = $ticket->name();
417
-        }
418
-        return $ticket_name;
419
-    }
420
-
421
-
422
-
423
-    /********************************** EVENT CART  **********************************/
424
-
425
-
426
-
427
-    /**
428
-     * releases or reserves ticket(s) based on quantity passed
429
-     *
430
-     * @param  EE_Line_Item $line_item
431
-     * @param  int          $quantity
432
-     * @return void
433
-     * @throws EE_Error
434
-     * @throws InvalidArgumentException
435
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
436
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
437
-     */
438
-    public static function ticket_quantity_updated(EE_Line_Item $line_item, $quantity = 1)
439
-    {
440
-        $ticket = EEM_Ticket::instance()->get_one_by_ID(absint($line_item->OBJ_ID()));
441
-        if ($ticket instanceof EE_Ticket) {
442
-            if ($quantity > 0) {
443
-                EED_Ticket_Sales_Monitor::instance()->_reserve_ticket($ticket, $quantity);
444
-            } else {
445
-                EED_Ticket_Sales_Monitor::instance()->_release_reserved_ticket($ticket, $quantity);
446
-            }
447
-        }
448
-    }
449
-
450
-
451
-
452
-    /**
453
-     * releases reserved ticket(s) based on quantity passed
454
-     *
455
-     * @param  EE_Ticket $ticket
456
-     * @param  int       $quantity
457
-     * @return void
458
-     * @throws EE_Error
459
-     */
460
-    public static function ticket_removed_from_cart(EE_Ticket $ticket, $quantity = 1)
461
-    {
462
-        EED_Ticket_Sales_Monitor::instance()->_release_reserved_ticket($ticket, $quantity);
463
-    }
464
-
465
-
466
-
467
-    /********************************** POST_NOTICES  **********************************/
468
-
469
-
470
-
471
-    /**
472
-     * @return void
473
-     * @throws EE_Error
474
-     * @throws InvalidArgumentException
475
-     * @throws ReflectionException
476
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
477
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
478
-     */
479
-    public static function post_notices()
480
-    {
481
-        EED_Ticket_Sales_Monitor::instance()->_post_notices();
482
-    }
483
-
484
-
485
-
486
-    /**
487
-     * @return void
488
-     * @throws EE_Error
489
-     * @throws InvalidArgumentException
490
-     * @throws ReflectionException
491
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
492
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
493
-     */
494
-    protected function _post_notices()
495
-    {
496
-        if (self::debug) {
497
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
498
-        }
499
-        $refresh_msg    = '';
500
-        $none_added_msg = '';
501
-        if (defined('DOING_AJAX') && DOING_AJAX) {
502
-            $refresh_msg    = __(
503
-                'Please refresh the page to view updated ticket quantities.',
504
-                'event_espresso'
505
-            );
506
-            $none_added_msg = __('No tickets were added for the event.', 'event_espresso');
507
-        }
508
-        if (! empty($this->sold_out_tickets)) {
509
-            EE_Error::add_attention(
510
-                sprintf(
511
-                    apply_filters(
512
-                        'FHEE__EED_Ticket_Sales_Monitor___post_notices__sold_out_tickets_notice',
513
-                        __(
514
-                            'We\'re sorry...%1$sThe following items have sold out since you first viewed this page, and can no longer be registered for:%1$s%1$s%2$s%1$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%1$s%1$s%3$s%1$s%4$s%1$s',
515
-                            'event_espresso'
516
-                        )
517
-                    ),
518
-                    '<br />',
519
-                    implode('<br />', $this->sold_out_tickets),
520
-                    $none_added_msg,
521
-                    $refresh_msg
522
-                )
523
-            );
524
-            // alter code flow in the Ticket Selector for better UX
525
-            add_filter('FHEE__EED_Ticket_Selector__process_ticket_selections__tckts_slctd', '__return_true');
526
-            add_filter('FHEE__EED_Ticket_Selector__process_ticket_selections__success', '__return_false');
527
-            $this->sold_out_tickets = array();
528
-            // and reset the cart
529
-            EED_Ticket_Sales_Monitor::session_cart_reset(EE_Registry::instance()->SSN);
530
-        }
531
-        if (! empty($this->decremented_tickets)) {
532
-            EE_Error::add_attention(
533
-                sprintf(
534
-                    apply_filters(
535
-                        'FHEE__EED_Ticket_Sales_Monitor___ticket_quantity_decremented__notice',
536
-                        __(
537
-                            'We\'re sorry...%1$sDue to sales that have occurred since you first viewed the last page, the following items have had their quantities adjusted to match the current available amount:%1$s%1$s%2$s%1$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%1$s%1$s%3$s%1$s%4$s%1$s',
538
-                            'event_espresso'
539
-                        )
540
-                    ),
541
-                    '<br />',
542
-                    implode('<br />', $this->decremented_tickets),
543
-                    $none_added_msg,
544
-                    $refresh_msg
545
-                )
546
-            );
547
-            $this->decremented_tickets = array();
548
-        }
549
-    }
550
-
551
-
552
-
553
-    /********************************** RELEASE_ALL_RESERVED_TICKETS_FOR_TRANSACTION  **********************************/
554
-
555
-
556
-
557
-    /**
558
-     * releases reserved tickets for all registrations of an EE_Transaction
559
-     * by default, will NOT release tickets for finalized transactions
560
-     *
561
-     * @param    EE_Transaction $transaction
562
-     * @return int
563
-     * @throws EE_Error
564
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
565
-     */
566
-    protected function _release_all_reserved_tickets_for_transaction(EE_Transaction $transaction)
567
-    {
568
-        if (self::debug) {
569
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
570
-            echo '<br /> . transaction->ID: ' . $transaction->ID();
571
-        }
572
-        // check if 'finalize_registration' step has been completed...
573
-        $finalized = $transaction->reg_step_completed('finalize_registration');
574
-        if (self::debug) {
575
-            // DEBUG LOG
576
-            EEH_Debug_Tools::log(
577
-                __CLASS__,
578
-                __FUNCTION__,
579
-                __LINE__,
580
-                array('finalized' => $finalized),
581
-                false,
582
-                'EE_Transaction: ' . $transaction->ID()
583
-            );
584
-        }
585
-        // how many tickets were released
586
-        $count = 0;
587
-        if (self::debug) {
588
-            echo '<br /> . . . finalized: ' . $finalized;
589
-        }
590
-        $release_tickets_with_TXN_status = array(
591
-            EEM_Transaction::failed_status_code,
592
-            EEM_Transaction::abandoned_status_code,
593
-            EEM_Transaction::incomplete_status_code,
594
-        );
595
-        // if the session is getting cleared BEFORE the TXN has been finalized
596
-        if (! $finalized || in_array($transaction->status_ID(), $release_tickets_with_TXN_status, true)) {
597
-            // let's cancel any reserved tickets
598
-            $registrations = $transaction->registrations();
599
-            if (! empty($registrations)) {
600
-                foreach ($registrations as $registration) {
601
-                    if ($registration instanceof EE_Registration) {
602
-                        $count += $this->_release_reserved_ticket_for_registration($registration, $transaction);
603
-                    }
604
-                }
605
-            }
606
-        }
607
-        return $count;
608
-    }
609
-
610
-
611
-
612
-    /**
613
-     * releases reserved tickets for an EE_Registration
614
-     * by default, will NOT release tickets for APPROVED registrations
615
-     *
616
-     * @param    EE_Registration $registration
617
-     * @param    EE_Transaction  $transaction
618
-     * @return    int
619
-     * @throws    EE_Error
620
-     */
621
-    protected function _release_reserved_ticket_for_registration(
622
-        EE_Registration $registration,
623
-        EE_Transaction $transaction
624
-    ) {
625
-        $STS_ID = $transaction->status_ID();
626
-        if (self::debug) {
627
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
628
-            echo '<br /> . . registration->ID: ' . $registration->ID();
629
-            echo '<br /> . . registration->status_ID: ' . $registration->status_ID();
630
-            echo '<br /> . . transaction->status_ID(): ' . $STS_ID;
631
-        }
632
-        if (
633
-            // release Tickets for Failed Transactions and Abandoned Transactions
634
-            $STS_ID === EEM_Transaction::failed_status_code
635
-            || $STS_ID === EEM_Transaction::abandoned_status_code
636
-            || (
637
-                // also release Tickets for Incomplete Transactions, but ONLY if the Registrations are NOT Approved
638
-                $STS_ID === EEM_Transaction::incomplete_status_code
639
-                && $registration->status_ID() !== EEM_Registration::status_id_approved
640
-            )
641
-        ) {
642
-            $ticket = $registration->ticket();
643
-            if ($ticket instanceof EE_Ticket) {
644
-                return $this->_release_reserved_ticket($ticket);
645
-            }
646
-        }
647
-        return 0;
648
-    }
649
-
650
-
651
-
652
-    /********************************** SESSION_CART_RESET  **********************************/
653
-
654
-
655
-
656
-    /**
657
-     * callback hooked into 'AHEE__EE_Session__reset_cart__before_reset'
658
-     *
659
-     * @param    EE_Session $session
660
-     * @return void
661
-     * @throws EE_Error
662
-     * @throws InvalidArgumentException
663
-     * @throws ReflectionException
664
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
665
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
666
-     */
667
-    public static function session_cart_reset(EE_Session $session)
668
-    {
669
-        // don't release tickets if checkout was already reset
670
-        if (did_action('AHEE__EE_Session__reset_checkout__before_reset')) {
671
-            return;
672
-        }
673
-        if (self::debug) {
674
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
675
-        }
676
-        $cart = $session->cart();
677
-        if ($cart instanceof EE_Cart) {
678
-            if (self::debug) {
679
-                echo '<br /><br /> cart instance of EE_Cart: ';
680
-            }
681
-            EED_Ticket_Sales_Monitor::instance()->_session_cart_reset($cart);
682
-        } else {
683
-            if (self::debug) {
684
-                echo '<br /><br /> invalid EE_Cart: ';
685
-                var_export($cart, true);
686
-            }
687
-        }
688
-    }
689
-
690
-
691
-
692
-    /**
693
-     * releases reserved tickets in the EE_Cart
694
-     *
695
-     * @param    EE_Cart $cart
696
-     * @return void
697
-     * @throws EE_Error
698
-     * @throws InvalidArgumentException
699
-     * @throws ReflectionException
700
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
701
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
702
-     */
703
-    protected function _session_cart_reset(EE_Cart $cart)
704
-    {
705
-        if (self::debug) {
706
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
707
-        }
708
-        $ticket_line_items = $cart->get_tickets();
709
-        if (empty($ticket_line_items)) {
710
-            return;
711
-        }
712
-        if (self::debug) {
713
-            echo '<br /> . ticket_line_item count: ' . count($ticket_line_items);
714
-        }
715
-        foreach ($ticket_line_items as $ticket_line_item) {
716
-            if (self::debug) {
717
-                echo '<br /> . . ticket_line_item->ID(): ' . $ticket_line_item->ID();
718
-            }
719
-            if ($ticket_line_item instanceof EE_Line_Item && $ticket_line_item->OBJ_type() === 'Ticket') {
720
-                if (self::debug) {
721
-                    echo '<br /> . . . ticket_line_item->OBJ_ID(): ' . $ticket_line_item->OBJ_ID();
722
-                }
723
-                $ticket = EEM_Ticket::instance()->get_one_by_ID($ticket_line_item->OBJ_ID());
724
-                if ($ticket instanceof EE_Ticket) {
725
-                    if (self::debug) {
726
-                        echo '<br /> . . . ticket->ID(): ' . $ticket->ID();
727
-                        echo '<br /> . . . ticket_line_item->quantity(): ' . $ticket_line_item->quantity();
728
-                    }
729
-                    $this->_release_reserved_ticket($ticket, $ticket_line_item->quantity());
730
-                }
731
-            }
732
-        }
733
-        if (self::debug) {
734
-            echo '<br /><br /> RESET COMPLETED ';
735
-        }
736
-    }
737
-
738
-
739
-
740
-    /********************************** SESSION_CHECKOUT_RESET  **********************************/
741
-
742
-
743
-
744
-    /**
745
-     * callback hooked into 'AHEE__EE_Session__reset_checkout__before_reset'
746
-     *
747
-     * @param    EE_Session $session
748
-     * @return void
749
-     * @throws EE_Error
750
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
751
-     */
752
-    public static function session_checkout_reset(EE_Session $session)
753
-    {
754
-        // don't release tickets if cart was already reset
755
-        if(did_action('AHEE__EE_Session__reset_cart__before_reset')) {
756
-            return;
757
-        }
758
-        $checkout = $session->checkout();
759
-        if ($checkout instanceof EE_Checkout) {
760
-            EED_Ticket_Sales_Monitor::instance()->_session_checkout_reset($checkout);
761
-        }
762
-    }
763
-
764
-
765
-
766
-    /**
767
-     * releases reserved tickets for the EE_Checkout->transaction
768
-     *
769
-     * @param    EE_Checkout $checkout
770
-     * @return void
771
-     * @throws EE_Error
772
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
773
-     */
774
-    protected function _session_checkout_reset(EE_Checkout $checkout)
775
-    {
776
-        if (self::debug) {
777
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
778
-        }
779
-        // we want to release the each registration's reserved tickets if the session was cleared, but not if this is a revisit
780
-        if ($checkout->revisit || ! $checkout->transaction instanceof EE_Transaction) {
781
-            return;
782
-        }
783
-        $this->_release_all_reserved_tickets_for_transaction($checkout->transaction);
784
-    }
785
-
786
-
787
-
788
-    /********************************** SESSION_EXPIRED_RESET  **********************************/
789
-
790
-
791
-
792
-    /**
793
-     * @param    EE_Session $session
794
-     * @return    void
795
-     */
796
-    public static function session_expired_reset(EE_Session $session)
797
-    {
798
-    }
799
-
800
-
801
-
802
-    /********************************** PROCESS_ABANDONED_TRANSACTIONS  **********************************/
803
-
804
-
805
-
806
-    /**
807
-     * releases reserved tickets for all registrations of an ABANDONED EE_Transaction
808
-     * by default, will NOT release tickets for free transactions, or any that have received a payment
809
-     *
810
-     * @param    EE_Transaction $transaction
811
-     * @return void
812
-     * @throws EE_Error
813
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
814
-     */
815
-    public static function process_abandoned_transactions(EE_Transaction $transaction)
816
-    {
817
-        // is this TXN free or has any money been paid towards this TXN? If so, then leave it alone
818
-        if ($transaction->is_free() || $transaction->paid() > 0) {
819
-            if (self::debug) {
820
-                // DEBUG LOG
821
-                EEH_Debug_Tools::log(
822
-                    __CLASS__,
823
-                    __FUNCTION__,
824
-                    __LINE__,
825
-                    array($transaction),
826
-                    false,
827
-                    'EE_Transaction: ' . $transaction->ID()
828
-                );
829
-            }
830
-            return;
831
-        }
832
-        // have their been any successful payments made ?
833
-        $payments = $transaction->payments();
834
-        foreach ($payments as $payment) {
835
-            if ($payment instanceof EE_Payment && $payment->status() === EEM_Payment::status_id_approved) {
836
-                if (self::debug) {
837
-                    // DEBUG LOG
838
-                    EEH_Debug_Tools::log(
839
-                        __CLASS__,
840
-                        __FUNCTION__,
841
-                        __LINE__,
842
-                        array($payment),
843
-                        false,
844
-                        'EE_Transaction: ' . $transaction->ID()
845
-                    );
846
-                }
847
-                return;
848
-            }
849
-        }
850
-        // since you haven't even attempted to pay for your ticket...
851
-        EED_Ticket_Sales_Monitor::instance()->_release_all_reserved_tickets_for_transaction($transaction);
852
-    }
853
-
854
-
855
-
856
-    /********************************** PROCESS_FAILED_TRANSACTIONS  **********************************/
857
-
858
-
859
-
860
-    /**
861
-     * releases reserved tickets for absolutely ALL registrations of a FAILED EE_Transaction
862
-     *
863
-     * @param    EE_Transaction $transaction
864
-     * @return void
865
-     * @throws EE_Error
866
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
867
-     */
868
-    public static function process_failed_transactions(EE_Transaction $transaction)
869
-    {
870
-        // since you haven't even attempted to pay for your ticket...
871
-        EED_Ticket_Sales_Monitor::instance()->_release_all_reserved_tickets_for_transaction($transaction);
872
-    }
873
-
874
-
875
-
876
-    /********************************** RESET RESERVATION COUNTS  *********************************/
877
-    /**
878
-     * Resets all ticket and datetime reserved counts to zero
879
-     * Tickets that are currently associated with a Transaction that is in progress
880
-     *
881
-     * @throws \EE_Error
882
-     * @throws \DomainException
883
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
884
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
885
-     * @throws \InvalidArgumentException
886
-     */
887
-    public static function reset_reservation_counts()
888
-    {
889
-        /** @var EE_Line_Item[] $valid_reserved_tickets */
890
-        $valid_reserved_tickets   = array();
891
-        $transactions_in_progress = EEM_Transaction::instance()->get_transactions_in_progress();
892
-        foreach ($transactions_in_progress as $transaction_in_progress) {
893
-            // if this TXN has been fully completed, then skip it
894
-            if ($transaction_in_progress->reg_step_completed('finalize_registration')) {
895
-                continue;
896
-            }
897
-            /** @var EE_Transaction $transaction_in_progress */
898
-            $total_line_item = $transaction_in_progress->total_line_item();
899
-            // $transaction_in_progress->line
900
-            if (! $total_line_item instanceof EE_Line_Item) {
901
-                throw new DomainException(
902
-                    esc_html__(
903
-                        'Transaction does not have a valid Total Line Item associated with it.',
904
-                        'event_espresso'
905
-                    )
906
-                );
907
-            }
908
-            $valid_reserved_tickets += EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total(
909
-                $total_line_item
910
-            );
911
-        }
912
-        $total_line_items = EEM_Line_Item::instance()->get_total_line_items_for_active_carts();
913
-        foreach ($total_line_items as $total_line_item) {
914
-            $valid_reserved_tickets += EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total(
915
-                $total_line_item
916
-            );
917
-        }
918
-        return EED_Ticket_Sales_Monitor::release_reservations_for_tickets(
919
-            EEM_Ticket::instance()->get_tickets_with_reservations(),
920
-            $valid_reserved_tickets
921
-        );
922
-    }
923
-
924
-
925
-
926
-    /**
927
-     * @param EE_Line_Item $total_line_item
928
-     * @return EE_Line_Item[]
929
-     */
930
-    private static function get_ticket_line_items_for_grand_total(EE_Line_Item $total_line_item)
931
-    {
932
-        /** @var EE_Line_Item[] $valid_reserved_tickets */
933
-        $valid_reserved_tickets = array();
934
-        $ticket_line_items      = EEH_Line_Item::get_ticket_line_items($total_line_item);
935
-        foreach ($ticket_line_items as $ticket_line_item) {
936
-            if ($ticket_line_item instanceof EE_Line_Item) {
937
-                $valid_reserved_tickets[] = $ticket_line_item;
938
-            }
939
-        }
940
-        return $valid_reserved_tickets;
941
-    }
942
-
943
-
944
-
945
-    /**
946
-     * @param EE_Ticket[]    $tickets_with_reservations
947
-     * @param EE_Line_Item[] $valid_reserved_ticket_line_items
948
-     * @return int
949
-     * @throws \EE_Error
950
-     */
951
-    private static function release_reservations_for_tickets(
952
-        array $tickets_with_reservations,
953
-        $valid_reserved_ticket_line_items = array()
954
-    ) {
955
-        $total_tickets_released = 0;
956
-        foreach ($tickets_with_reservations as $ticket_with_reservations) {
957
-            if (! $ticket_with_reservations instanceof EE_Ticket) {
958
-                continue;
959
-            }
960
-            $reserved_qty = $ticket_with_reservations->reserved();
961
-            foreach ($valid_reserved_ticket_line_items as $valid_reserved_ticket_line_item) {
962
-                if (
963
-                    $valid_reserved_ticket_line_item instanceof EE_Line_Item
964
-                    && $valid_reserved_ticket_line_item->OBJ_ID() === $ticket_with_reservations->ID()
965
-                ) {
966
-                    $reserved_qty -= $valid_reserved_ticket_line_item->quantity();
967
-                }
968
-            }
969
-            if ($reserved_qty > 0) {
970
-                $ticket_with_reservations->decrease_reserved($reserved_qty);
971
-                $ticket_with_reservations->save();
972
-                $total_tickets_released += $reserved_qty;
973
-            }
974
-        }
975
-        return $total_tickets_released;
976
-    }
977
-
978
-
979
-
980
-    /********************************** SHUTDOWN  **********************************/
981
-
982
-
983
-
984
-    /**
985
-     * @return false|int
986
-     * @throws EE_Error
987
-     * @throws InvalidArgumentException
988
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
989
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
990
-     */
991
-    public static function clear_expired_line_items_with_no_transaction()
992
-    {
993
-        /** @type WPDB $wpdb */
994
-        global $wpdb;
995
-        return $wpdb->query(
996
-            $wpdb->prepare(
997
-                'DELETE FROM ' . EEM_Line_Item::instance()->table() . '
25
+	const debug = false;    //	true false
26
+
27
+	/**
28
+	 * an array of raw ticket data from EED_Ticket_Selector
29
+	 *
30
+	 * @var array $ticket_selections
31
+	 */
32
+	protected $ticket_selections = array();
33
+
34
+	/**
35
+	 * the raw ticket data from EED_Ticket_Selector is organized in rows
36
+	 * according to how they are displayed in the actual Ticket_Selector
37
+	 * this tracks the current row being processed
38
+	 *
39
+	 * @var int $current_row
40
+	 */
41
+	protected $current_row = 0;
42
+
43
+	/**
44
+	 * an array for tracking names of tickets that have sold out
45
+	 *
46
+	 * @var array $sold_out_tickets
47
+	 */
48
+	protected $sold_out_tickets = array();
49
+
50
+	/**
51
+	 * an array for tracking names of tickets that have had their quantities reduced
52
+	 *
53
+	 * @var array $decremented_tickets
54
+	 */
55
+	protected $decremented_tickets = array();
56
+
57
+
58
+
59
+	/**
60
+	 * set_hooks - for hooking into EE Core, other modules, etc
61
+	 *
62
+	 * @return    void
63
+	 */
64
+	public static function set_hooks()
65
+	{
66
+		// release tickets for expired carts
67
+		add_action(
68
+			'EED_Ticket_Selector__process_ticket_selections__before',
69
+			array('EED_Ticket_Sales_Monitor', 'release_tickets_for_expired_carts'),
70
+			1
71
+		);
72
+		// check ticket reserves AFTER MER does it's check (hence priority 20)
73
+		add_filter(
74
+			'FHEE__EE_Ticket_Selector___add_ticket_to_cart__ticket_qty',
75
+			array('EED_Ticket_Sales_Monitor', 'validate_ticket_sale'),
76
+			20,
77
+			3
78
+		);
79
+		// add notices for sold out tickets
80
+		add_action(
81
+			'AHEE__EE_Ticket_Selector__process_ticket_selections__after_tickets_added_to_cart',
82
+			array('EED_Ticket_Sales_Monitor', 'post_notices'),
83
+			10
84
+		);
85
+		// handle ticket quantities adjusted in cart
86
+		//add_action(
87
+		//	'FHEE__EED_Multi_Event_Registration__adjust_line_item_quantity__line_item_quantity_updated',
88
+		//	array( 'EED_Ticket_Sales_Monitor', 'ticket_quantity_updated' ),
89
+		//	10, 2
90
+		//);
91
+		// handle tickets deleted from cart
92
+		add_action(
93
+			'FHEE__EED_Multi_Event_Registration__delete_ticket__ticket_removed_from_cart',
94
+			array('EED_Ticket_Sales_Monitor', 'ticket_removed_from_cart'),
95
+			10,
96
+			2
97
+		);
98
+		// handle emptied carts
99
+		add_action(
100
+			'AHEE__EE_Session__reset_cart__before_reset',
101
+			array('EED_Ticket_Sales_Monitor', 'session_cart_reset'),
102
+			10,
103
+			1
104
+		);
105
+		add_action(
106
+			'AHEE__EED_Multi_Event_Registration__empty_event_cart__before_delete_cart',
107
+			array('EED_Ticket_Sales_Monitor', 'session_cart_reset'),
108
+			10,
109
+			1
110
+		);
111
+		// handle cancelled registrations
112
+		add_action(
113
+			'AHEE__EE_Session__reset_checkout__before_reset',
114
+			array('EED_Ticket_Sales_Monitor', 'session_checkout_reset'),
115
+			10,
116
+			1
117
+		);
118
+		// cron tasks
119
+		add_action(
120
+			'AHEE__EE_Cron_Tasks__process_expired_transactions__abandoned_transaction',
121
+			array('EED_Ticket_Sales_Monitor', 'process_abandoned_transactions'),
122
+			10,
123
+			1
124
+		);
125
+		add_action(
126
+			'AHEE__EE_Cron_Tasks__process_expired_transactions__incomplete_transaction',
127
+			array('EED_Ticket_Sales_Monitor', 'process_abandoned_transactions'),
128
+			10,
129
+			1
130
+		);
131
+		add_action(
132
+			'AHEE__EE_Cron_Tasks__process_expired_transactions__failed_transaction',
133
+			array('EED_Ticket_Sales_Monitor', 'process_failed_transactions'),
134
+			10,
135
+			1
136
+		);
137
+	}
138
+
139
+
140
+
141
+	/**
142
+	 * set_hooks_admin - for hooking into EE Admin Core, other modules, etc
143
+	 *
144
+	 * @return void
145
+	 */
146
+	public static function set_hooks_admin()
147
+	{
148
+		EED_Ticket_Sales_Monitor::set_hooks();
149
+	}
150
+
151
+
152
+
153
+	/**
154
+	 * @return EED_Ticket_Sales_Monitor|EED_Module
155
+	 */
156
+	public static function instance()
157
+	{
158
+		return parent::get_instance(__CLASS__);
159
+	}
160
+
161
+
162
+
163
+	/**
164
+	 * @param WP_Query $WP_Query
165
+	 * @return    void
166
+	 */
167
+	public function run($WP_Query)
168
+	{
169
+	}
170
+
171
+
172
+
173
+	/********************************** PRE_TICKET_SALES  **********************************/
174
+
175
+
176
+
177
+	/**
178
+	 * Retrieves grand totals from the line items that have no TXN ID
179
+	 * and timestamps less than the current time minus the session lifespan.
180
+	 * These are carts that have been abandoned before the "registrant" even attempted to checkout.
181
+	 * We're going to release the tickets for these line items before attempting to add more to the cart.
182
+	 *
183
+	 * @return void
184
+	 * @throws EE_Error
185
+	 * @throws InvalidArgumentException
186
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
187
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
188
+	 */
189
+	public static function release_tickets_for_expired_carts()
190
+	{
191
+		do_action('AHEE__EED_Ticket_Sales_Monitor__release_tickets_for_expired_carts__begin');
192
+		$expired_ticket_IDs      = array();
193
+		$valid_ticket_line_items = array();
194
+		$total_line_items        = EEM_Line_Item::instance()->get_total_line_items_with_no_transaction();
195
+		if (empty($total_line_items)) {
196
+			do_action(
197
+				'AHEE__EED_Ticket_Sales_Monitor__release_tickets_for_expired_carts__end',
198
+				$total_line_items,
199
+				$valid_ticket_line_items,
200
+				$expired_ticket_IDs
201
+			);
202
+			return;
203
+		}
204
+		$expired = current_time('timestamp') - EE_Registry::instance()->SSN->lifespan();
205
+		foreach ($total_line_items as $total_line_item) {
206
+			/** @var EE_Line_Item $total_line_item */
207
+			$ticket_line_items = EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total($total_line_item);
208
+			foreach ($ticket_line_items as $ticket_line_item) {
209
+				if (! $ticket_line_item instanceof EE_Line_Item) {
210
+					continue;
211
+				}
212
+				if ($total_line_item->timestamp(true) <= $expired) {
213
+					$expired_ticket_IDs[$ticket_line_item->OBJ_ID()] = $ticket_line_item->OBJ_ID();
214
+				} else {
215
+					$valid_ticket_line_items[$ticket_line_item->OBJ_ID()] = $ticket_line_item;
216
+				}
217
+			}
218
+		}
219
+		if (! empty($expired_ticket_IDs)) {
220
+			EED_Ticket_Sales_Monitor::release_reservations_for_tickets(
221
+				\EEM_Ticket::instance()->get_tickets_with_IDs($expired_ticket_IDs),
222
+				$valid_ticket_line_items
223
+			);
224
+			// let's get rid of expired line items so that they can't interfere with tracking
225
+			add_action(
226
+				'shutdown',
227
+				array('EED_Ticket_Sales_Monitor', 'clear_expired_line_items_with_no_transaction'),
228
+				999
229
+			);
230
+		}
231
+		do_action(
232
+			'AHEE__EED_Ticket_Sales_Monitor__release_tickets_for_expired_carts__end',
233
+			$total_line_items,
234
+			$valid_ticket_line_items,
235
+			$expired_ticket_IDs
236
+		);
237
+	}
238
+
239
+
240
+
241
+	/********************************** VALIDATE_TICKET_SALE  **********************************/
242
+
243
+
244
+
245
+	/**
246
+	 * callback for 'FHEE__EED_Ticket_Selector__process_ticket_selections__valid_post_data'
247
+	 *
248
+	 * @param int       $qty
249
+	 * @param EE_Ticket $ticket
250
+	 * @return bool
251
+	 * @throws UnexpectedEntityException
252
+	 * @throws EE_Error
253
+	 */
254
+	public static function validate_ticket_sale($qty = 1, EE_Ticket $ticket)
255
+	{
256
+		$qty = absint($qty);
257
+		if ($qty > 0) {
258
+			$qty = EED_Ticket_Sales_Monitor::instance()->_validate_ticket_sale($ticket, $qty);
259
+		}
260
+		if (self::debug) {
261
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '()';
262
+			echo '<br /><br /><b> RETURNED QTY: ' . $qty . '</b>';
263
+		}
264
+		return $qty;
265
+	}
266
+
267
+
268
+
269
+	/**
270
+	 * checks whether an individual ticket is available for purchase based on datetime, and ticket details
271
+	 *
272
+	 * @param   EE_Ticket $ticket
273
+	 * @param int         $qty
274
+	 * @return int
275
+	 * @throws UnexpectedEntityException
276
+	 * @throws EE_Error
277
+	 */
278
+	protected function _validate_ticket_sale(EE_Ticket $ticket, $qty = 1)
279
+	{
280
+		if (self::debug) {
281
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
282
+		}
283
+		if (! $ticket instanceof EE_Ticket) {
284
+			return 0;
285
+		}
286
+		if (self::debug) {
287
+			echo '<br /><b> . ticket->ID: ' . $ticket->ID() . '</b>';
288
+			echo '<br /> . original ticket->reserved: ' . $ticket->reserved();
289
+		}
290
+		$ticket->refresh_from_db();
291
+		// first let's determine the ticket availability based on sales
292
+		$available = $ticket->qty('saleable');
293
+		if (self::debug) {
294
+			echo '<br /> . . . ticket->qty: ' . $ticket->qty();
295
+			echo '<br /> . . . ticket->sold: ' . $ticket->sold();
296
+			echo '<br /> . . . ticket->reserved: ' . $ticket->reserved();
297
+			echo '<br /> . . . ticket->qty(saleable): ' . $ticket->qty('saleable');
298
+			echo '<br /> . . . available: ' . $available;
299
+		}
300
+		if ($available < 1) {
301
+			$this->_ticket_sold_out($ticket);
302
+			return 0;
303
+		}
304
+		if (self::debug) {
305
+			echo '<br /> . . . qty: ' . $qty;
306
+		}
307
+		if ($available < $qty) {
308
+			$qty = $available;
309
+			if (self::debug) {
310
+				echo '<br /> . . . QTY ADJUSTED: ' . $qty;
311
+			}
312
+			$this->_ticket_quantity_decremented($ticket);
313
+		}
314
+		$this->_reserve_ticket($ticket, $qty);
315
+		return $qty;
316
+	}
317
+
318
+
319
+
320
+	/**
321
+	 * increments ticket reserved based on quantity passed
322
+	 *
323
+	 * @param    EE_Ticket $ticket
324
+	 * @param int          $quantity
325
+	 * @return bool
326
+	 * @throws EE_Error
327
+	 */
328
+	protected function _reserve_ticket(EE_Ticket $ticket, $quantity = 1)
329
+	{
330
+		if (self::debug) {
331
+			echo '<br /><br /> . . . INCREASE RESERVED: ' . $quantity;
332
+		}
333
+		$ticket->increase_reserved($quantity);
334
+		return $ticket->save();
335
+	}
336
+
337
+
338
+
339
+	/**
340
+	 * @param  EE_Ticket $ticket
341
+	 * @param  int       $quantity
342
+	 * @return bool
343
+	 * @throws EE_Error
344
+	 */
345
+	protected function _release_reserved_ticket(EE_Ticket $ticket, $quantity = 1)
346
+	{
347
+		if (self::debug) {
348
+			echo '<br /> . . . ticket->ID: ' . $ticket->ID();
349
+			echo '<br /> . . . ticket->reserved before: ' . $ticket->reserved();
350
+		}
351
+		$ticket->decrease_reserved($quantity);
352
+		if (self::debug) {
353
+			echo '<br /> . . . ticket->reserved after: ' . $ticket->reserved();
354
+		}
355
+		return $ticket->save() ? 1 : 0;
356
+	}
357
+
358
+
359
+
360
+	/**
361
+	 * removes quantities within the ticket selector based on zero ticket availability
362
+	 *
363
+	 * @param    EE_Ticket $ticket
364
+	 * @return    void
365
+	 * @throws UnexpectedEntityException
366
+	 * @throws EE_Error
367
+	 */
368
+	protected function _ticket_sold_out(EE_Ticket $ticket)
369
+	{
370
+		if (self::debug) {
371
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
372
+			echo '<br /> . . ticket->name: ' . $this->_get_ticket_and_event_name($ticket);
373
+		}
374
+		$this->sold_out_tickets[] = $this->_get_ticket_and_event_name($ticket);
375
+	}
376
+
377
+
378
+
379
+	/**
380
+	 * adjusts quantities within the ticket selector based on decreased ticket availability
381
+	 *
382
+	 * @param    EE_Ticket $ticket
383
+	 * @return void
384
+	 * @throws UnexpectedEntityException
385
+	 * @throws EE_Error
386
+	 */
387
+	protected function _ticket_quantity_decremented(EE_Ticket $ticket)
388
+	{
389
+		if (self::debug) {
390
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
391
+			echo '<br /> . . ticket->name: ' . $this->_get_ticket_and_event_name($ticket);
392
+		}
393
+		$this->decremented_tickets[] = $this->_get_ticket_and_event_name($ticket);
394
+	}
395
+
396
+
397
+
398
+	/**
399
+	 * builds string out of ticket and event name
400
+	 *
401
+	 * @param    EE_Ticket $ticket
402
+	 * @return string
403
+	 * @throws UnexpectedEntityException
404
+	 * @throws EE_Error
405
+	 */
406
+	protected function _get_ticket_and_event_name(EE_Ticket $ticket)
407
+	{
408
+		$event = $ticket->get_related_event();
409
+		if ($event instanceof EE_Event) {
410
+			$ticket_name = sprintf(
411
+				_x('%1$s for %2$s', 'ticket name for event name', 'event_espresso'),
412
+				$ticket->name(),
413
+				$event->name()
414
+			);
415
+		} else {
416
+			$ticket_name = $ticket->name();
417
+		}
418
+		return $ticket_name;
419
+	}
420
+
421
+
422
+
423
+	/********************************** EVENT CART  **********************************/
424
+
425
+
426
+
427
+	/**
428
+	 * releases or reserves ticket(s) based on quantity passed
429
+	 *
430
+	 * @param  EE_Line_Item $line_item
431
+	 * @param  int          $quantity
432
+	 * @return void
433
+	 * @throws EE_Error
434
+	 * @throws InvalidArgumentException
435
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
436
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
437
+	 */
438
+	public static function ticket_quantity_updated(EE_Line_Item $line_item, $quantity = 1)
439
+	{
440
+		$ticket = EEM_Ticket::instance()->get_one_by_ID(absint($line_item->OBJ_ID()));
441
+		if ($ticket instanceof EE_Ticket) {
442
+			if ($quantity > 0) {
443
+				EED_Ticket_Sales_Monitor::instance()->_reserve_ticket($ticket, $quantity);
444
+			} else {
445
+				EED_Ticket_Sales_Monitor::instance()->_release_reserved_ticket($ticket, $quantity);
446
+			}
447
+		}
448
+	}
449
+
450
+
451
+
452
+	/**
453
+	 * releases reserved ticket(s) based on quantity passed
454
+	 *
455
+	 * @param  EE_Ticket $ticket
456
+	 * @param  int       $quantity
457
+	 * @return void
458
+	 * @throws EE_Error
459
+	 */
460
+	public static function ticket_removed_from_cart(EE_Ticket $ticket, $quantity = 1)
461
+	{
462
+		EED_Ticket_Sales_Monitor::instance()->_release_reserved_ticket($ticket, $quantity);
463
+	}
464
+
465
+
466
+
467
+	/********************************** POST_NOTICES  **********************************/
468
+
469
+
470
+
471
+	/**
472
+	 * @return void
473
+	 * @throws EE_Error
474
+	 * @throws InvalidArgumentException
475
+	 * @throws ReflectionException
476
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
477
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
478
+	 */
479
+	public static function post_notices()
480
+	{
481
+		EED_Ticket_Sales_Monitor::instance()->_post_notices();
482
+	}
483
+
484
+
485
+
486
+	/**
487
+	 * @return void
488
+	 * @throws EE_Error
489
+	 * @throws InvalidArgumentException
490
+	 * @throws ReflectionException
491
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
492
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
493
+	 */
494
+	protected function _post_notices()
495
+	{
496
+		if (self::debug) {
497
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
498
+		}
499
+		$refresh_msg    = '';
500
+		$none_added_msg = '';
501
+		if (defined('DOING_AJAX') && DOING_AJAX) {
502
+			$refresh_msg    = __(
503
+				'Please refresh the page to view updated ticket quantities.',
504
+				'event_espresso'
505
+			);
506
+			$none_added_msg = __('No tickets were added for the event.', 'event_espresso');
507
+		}
508
+		if (! empty($this->sold_out_tickets)) {
509
+			EE_Error::add_attention(
510
+				sprintf(
511
+					apply_filters(
512
+						'FHEE__EED_Ticket_Sales_Monitor___post_notices__sold_out_tickets_notice',
513
+						__(
514
+							'We\'re sorry...%1$sThe following items have sold out since you first viewed this page, and can no longer be registered for:%1$s%1$s%2$s%1$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%1$s%1$s%3$s%1$s%4$s%1$s',
515
+							'event_espresso'
516
+						)
517
+					),
518
+					'<br />',
519
+					implode('<br />', $this->sold_out_tickets),
520
+					$none_added_msg,
521
+					$refresh_msg
522
+				)
523
+			);
524
+			// alter code flow in the Ticket Selector for better UX
525
+			add_filter('FHEE__EED_Ticket_Selector__process_ticket_selections__tckts_slctd', '__return_true');
526
+			add_filter('FHEE__EED_Ticket_Selector__process_ticket_selections__success', '__return_false');
527
+			$this->sold_out_tickets = array();
528
+			// and reset the cart
529
+			EED_Ticket_Sales_Monitor::session_cart_reset(EE_Registry::instance()->SSN);
530
+		}
531
+		if (! empty($this->decremented_tickets)) {
532
+			EE_Error::add_attention(
533
+				sprintf(
534
+					apply_filters(
535
+						'FHEE__EED_Ticket_Sales_Monitor___ticket_quantity_decremented__notice',
536
+						__(
537
+							'We\'re sorry...%1$sDue to sales that have occurred since you first viewed the last page, the following items have had their quantities adjusted to match the current available amount:%1$s%1$s%2$s%1$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%1$s%1$s%3$s%1$s%4$s%1$s',
538
+							'event_espresso'
539
+						)
540
+					),
541
+					'<br />',
542
+					implode('<br />', $this->decremented_tickets),
543
+					$none_added_msg,
544
+					$refresh_msg
545
+				)
546
+			);
547
+			$this->decremented_tickets = array();
548
+		}
549
+	}
550
+
551
+
552
+
553
+	/********************************** RELEASE_ALL_RESERVED_TICKETS_FOR_TRANSACTION  **********************************/
554
+
555
+
556
+
557
+	/**
558
+	 * releases reserved tickets for all registrations of an EE_Transaction
559
+	 * by default, will NOT release tickets for finalized transactions
560
+	 *
561
+	 * @param    EE_Transaction $transaction
562
+	 * @return int
563
+	 * @throws EE_Error
564
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
565
+	 */
566
+	protected function _release_all_reserved_tickets_for_transaction(EE_Transaction $transaction)
567
+	{
568
+		if (self::debug) {
569
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
570
+			echo '<br /> . transaction->ID: ' . $transaction->ID();
571
+		}
572
+		// check if 'finalize_registration' step has been completed...
573
+		$finalized = $transaction->reg_step_completed('finalize_registration');
574
+		if (self::debug) {
575
+			// DEBUG LOG
576
+			EEH_Debug_Tools::log(
577
+				__CLASS__,
578
+				__FUNCTION__,
579
+				__LINE__,
580
+				array('finalized' => $finalized),
581
+				false,
582
+				'EE_Transaction: ' . $transaction->ID()
583
+			);
584
+		}
585
+		// how many tickets were released
586
+		$count = 0;
587
+		if (self::debug) {
588
+			echo '<br /> . . . finalized: ' . $finalized;
589
+		}
590
+		$release_tickets_with_TXN_status = array(
591
+			EEM_Transaction::failed_status_code,
592
+			EEM_Transaction::abandoned_status_code,
593
+			EEM_Transaction::incomplete_status_code,
594
+		);
595
+		// if the session is getting cleared BEFORE the TXN has been finalized
596
+		if (! $finalized || in_array($transaction->status_ID(), $release_tickets_with_TXN_status, true)) {
597
+			// let's cancel any reserved tickets
598
+			$registrations = $transaction->registrations();
599
+			if (! empty($registrations)) {
600
+				foreach ($registrations as $registration) {
601
+					if ($registration instanceof EE_Registration) {
602
+						$count += $this->_release_reserved_ticket_for_registration($registration, $transaction);
603
+					}
604
+				}
605
+			}
606
+		}
607
+		return $count;
608
+	}
609
+
610
+
611
+
612
+	/**
613
+	 * releases reserved tickets for an EE_Registration
614
+	 * by default, will NOT release tickets for APPROVED registrations
615
+	 *
616
+	 * @param    EE_Registration $registration
617
+	 * @param    EE_Transaction  $transaction
618
+	 * @return    int
619
+	 * @throws    EE_Error
620
+	 */
621
+	protected function _release_reserved_ticket_for_registration(
622
+		EE_Registration $registration,
623
+		EE_Transaction $transaction
624
+	) {
625
+		$STS_ID = $transaction->status_ID();
626
+		if (self::debug) {
627
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
628
+			echo '<br /> . . registration->ID: ' . $registration->ID();
629
+			echo '<br /> . . registration->status_ID: ' . $registration->status_ID();
630
+			echo '<br /> . . transaction->status_ID(): ' . $STS_ID;
631
+		}
632
+		if (
633
+			// release Tickets for Failed Transactions and Abandoned Transactions
634
+			$STS_ID === EEM_Transaction::failed_status_code
635
+			|| $STS_ID === EEM_Transaction::abandoned_status_code
636
+			|| (
637
+				// also release Tickets for Incomplete Transactions, but ONLY if the Registrations are NOT Approved
638
+				$STS_ID === EEM_Transaction::incomplete_status_code
639
+				&& $registration->status_ID() !== EEM_Registration::status_id_approved
640
+			)
641
+		) {
642
+			$ticket = $registration->ticket();
643
+			if ($ticket instanceof EE_Ticket) {
644
+				return $this->_release_reserved_ticket($ticket);
645
+			}
646
+		}
647
+		return 0;
648
+	}
649
+
650
+
651
+
652
+	/********************************** SESSION_CART_RESET  **********************************/
653
+
654
+
655
+
656
+	/**
657
+	 * callback hooked into 'AHEE__EE_Session__reset_cart__before_reset'
658
+	 *
659
+	 * @param    EE_Session $session
660
+	 * @return void
661
+	 * @throws EE_Error
662
+	 * @throws InvalidArgumentException
663
+	 * @throws ReflectionException
664
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
665
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
666
+	 */
667
+	public static function session_cart_reset(EE_Session $session)
668
+	{
669
+		// don't release tickets if checkout was already reset
670
+		if (did_action('AHEE__EE_Session__reset_checkout__before_reset')) {
671
+			return;
672
+		}
673
+		if (self::debug) {
674
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
675
+		}
676
+		$cart = $session->cart();
677
+		if ($cart instanceof EE_Cart) {
678
+			if (self::debug) {
679
+				echo '<br /><br /> cart instance of EE_Cart: ';
680
+			}
681
+			EED_Ticket_Sales_Monitor::instance()->_session_cart_reset($cart);
682
+		} else {
683
+			if (self::debug) {
684
+				echo '<br /><br /> invalid EE_Cart: ';
685
+				var_export($cart, true);
686
+			}
687
+		}
688
+	}
689
+
690
+
691
+
692
+	/**
693
+	 * releases reserved tickets in the EE_Cart
694
+	 *
695
+	 * @param    EE_Cart $cart
696
+	 * @return void
697
+	 * @throws EE_Error
698
+	 * @throws InvalidArgumentException
699
+	 * @throws ReflectionException
700
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
701
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
702
+	 */
703
+	protected function _session_cart_reset(EE_Cart $cart)
704
+	{
705
+		if (self::debug) {
706
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
707
+		}
708
+		$ticket_line_items = $cart->get_tickets();
709
+		if (empty($ticket_line_items)) {
710
+			return;
711
+		}
712
+		if (self::debug) {
713
+			echo '<br /> . ticket_line_item count: ' . count($ticket_line_items);
714
+		}
715
+		foreach ($ticket_line_items as $ticket_line_item) {
716
+			if (self::debug) {
717
+				echo '<br /> . . ticket_line_item->ID(): ' . $ticket_line_item->ID();
718
+			}
719
+			if ($ticket_line_item instanceof EE_Line_Item && $ticket_line_item->OBJ_type() === 'Ticket') {
720
+				if (self::debug) {
721
+					echo '<br /> . . . ticket_line_item->OBJ_ID(): ' . $ticket_line_item->OBJ_ID();
722
+				}
723
+				$ticket = EEM_Ticket::instance()->get_one_by_ID($ticket_line_item->OBJ_ID());
724
+				if ($ticket instanceof EE_Ticket) {
725
+					if (self::debug) {
726
+						echo '<br /> . . . ticket->ID(): ' . $ticket->ID();
727
+						echo '<br /> . . . ticket_line_item->quantity(): ' . $ticket_line_item->quantity();
728
+					}
729
+					$this->_release_reserved_ticket($ticket, $ticket_line_item->quantity());
730
+				}
731
+			}
732
+		}
733
+		if (self::debug) {
734
+			echo '<br /><br /> RESET COMPLETED ';
735
+		}
736
+	}
737
+
738
+
739
+
740
+	/********************************** SESSION_CHECKOUT_RESET  **********************************/
741
+
742
+
743
+
744
+	/**
745
+	 * callback hooked into 'AHEE__EE_Session__reset_checkout__before_reset'
746
+	 *
747
+	 * @param    EE_Session $session
748
+	 * @return void
749
+	 * @throws EE_Error
750
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
751
+	 */
752
+	public static function session_checkout_reset(EE_Session $session)
753
+	{
754
+		// don't release tickets if cart was already reset
755
+		if(did_action('AHEE__EE_Session__reset_cart__before_reset')) {
756
+			return;
757
+		}
758
+		$checkout = $session->checkout();
759
+		if ($checkout instanceof EE_Checkout) {
760
+			EED_Ticket_Sales_Monitor::instance()->_session_checkout_reset($checkout);
761
+		}
762
+	}
763
+
764
+
765
+
766
+	/**
767
+	 * releases reserved tickets for the EE_Checkout->transaction
768
+	 *
769
+	 * @param    EE_Checkout $checkout
770
+	 * @return void
771
+	 * @throws EE_Error
772
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
773
+	 */
774
+	protected function _session_checkout_reset(EE_Checkout $checkout)
775
+	{
776
+		if (self::debug) {
777
+			echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
778
+		}
779
+		// we want to release the each registration's reserved tickets if the session was cleared, but not if this is a revisit
780
+		if ($checkout->revisit || ! $checkout->transaction instanceof EE_Transaction) {
781
+			return;
782
+		}
783
+		$this->_release_all_reserved_tickets_for_transaction($checkout->transaction);
784
+	}
785
+
786
+
787
+
788
+	/********************************** SESSION_EXPIRED_RESET  **********************************/
789
+
790
+
791
+
792
+	/**
793
+	 * @param    EE_Session $session
794
+	 * @return    void
795
+	 */
796
+	public static function session_expired_reset(EE_Session $session)
797
+	{
798
+	}
799
+
800
+
801
+
802
+	/********************************** PROCESS_ABANDONED_TRANSACTIONS  **********************************/
803
+
804
+
805
+
806
+	/**
807
+	 * releases reserved tickets for all registrations of an ABANDONED EE_Transaction
808
+	 * by default, will NOT release tickets for free transactions, or any that have received a payment
809
+	 *
810
+	 * @param    EE_Transaction $transaction
811
+	 * @return void
812
+	 * @throws EE_Error
813
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
814
+	 */
815
+	public static function process_abandoned_transactions(EE_Transaction $transaction)
816
+	{
817
+		// is this TXN free or has any money been paid towards this TXN? If so, then leave it alone
818
+		if ($transaction->is_free() || $transaction->paid() > 0) {
819
+			if (self::debug) {
820
+				// DEBUG LOG
821
+				EEH_Debug_Tools::log(
822
+					__CLASS__,
823
+					__FUNCTION__,
824
+					__LINE__,
825
+					array($transaction),
826
+					false,
827
+					'EE_Transaction: ' . $transaction->ID()
828
+				);
829
+			}
830
+			return;
831
+		}
832
+		// have their been any successful payments made ?
833
+		$payments = $transaction->payments();
834
+		foreach ($payments as $payment) {
835
+			if ($payment instanceof EE_Payment && $payment->status() === EEM_Payment::status_id_approved) {
836
+				if (self::debug) {
837
+					// DEBUG LOG
838
+					EEH_Debug_Tools::log(
839
+						__CLASS__,
840
+						__FUNCTION__,
841
+						__LINE__,
842
+						array($payment),
843
+						false,
844
+						'EE_Transaction: ' . $transaction->ID()
845
+					);
846
+				}
847
+				return;
848
+			}
849
+		}
850
+		// since you haven't even attempted to pay for your ticket...
851
+		EED_Ticket_Sales_Monitor::instance()->_release_all_reserved_tickets_for_transaction($transaction);
852
+	}
853
+
854
+
855
+
856
+	/********************************** PROCESS_FAILED_TRANSACTIONS  **********************************/
857
+
858
+
859
+
860
+	/**
861
+	 * releases reserved tickets for absolutely ALL registrations of a FAILED EE_Transaction
862
+	 *
863
+	 * @param    EE_Transaction $transaction
864
+	 * @return void
865
+	 * @throws EE_Error
866
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
867
+	 */
868
+	public static function process_failed_transactions(EE_Transaction $transaction)
869
+	{
870
+		// since you haven't even attempted to pay for your ticket...
871
+		EED_Ticket_Sales_Monitor::instance()->_release_all_reserved_tickets_for_transaction($transaction);
872
+	}
873
+
874
+
875
+
876
+	/********************************** RESET RESERVATION COUNTS  *********************************/
877
+	/**
878
+	 * Resets all ticket and datetime reserved counts to zero
879
+	 * Tickets that are currently associated with a Transaction that is in progress
880
+	 *
881
+	 * @throws \EE_Error
882
+	 * @throws \DomainException
883
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
884
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
885
+	 * @throws \InvalidArgumentException
886
+	 */
887
+	public static function reset_reservation_counts()
888
+	{
889
+		/** @var EE_Line_Item[] $valid_reserved_tickets */
890
+		$valid_reserved_tickets   = array();
891
+		$transactions_in_progress = EEM_Transaction::instance()->get_transactions_in_progress();
892
+		foreach ($transactions_in_progress as $transaction_in_progress) {
893
+			// if this TXN has been fully completed, then skip it
894
+			if ($transaction_in_progress->reg_step_completed('finalize_registration')) {
895
+				continue;
896
+			}
897
+			/** @var EE_Transaction $transaction_in_progress */
898
+			$total_line_item = $transaction_in_progress->total_line_item();
899
+			// $transaction_in_progress->line
900
+			if (! $total_line_item instanceof EE_Line_Item) {
901
+				throw new DomainException(
902
+					esc_html__(
903
+						'Transaction does not have a valid Total Line Item associated with it.',
904
+						'event_espresso'
905
+					)
906
+				);
907
+			}
908
+			$valid_reserved_tickets += EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total(
909
+				$total_line_item
910
+			);
911
+		}
912
+		$total_line_items = EEM_Line_Item::instance()->get_total_line_items_for_active_carts();
913
+		foreach ($total_line_items as $total_line_item) {
914
+			$valid_reserved_tickets += EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total(
915
+				$total_line_item
916
+			);
917
+		}
918
+		return EED_Ticket_Sales_Monitor::release_reservations_for_tickets(
919
+			EEM_Ticket::instance()->get_tickets_with_reservations(),
920
+			$valid_reserved_tickets
921
+		);
922
+	}
923
+
924
+
925
+
926
+	/**
927
+	 * @param EE_Line_Item $total_line_item
928
+	 * @return EE_Line_Item[]
929
+	 */
930
+	private static function get_ticket_line_items_for_grand_total(EE_Line_Item $total_line_item)
931
+	{
932
+		/** @var EE_Line_Item[] $valid_reserved_tickets */
933
+		$valid_reserved_tickets = array();
934
+		$ticket_line_items      = EEH_Line_Item::get_ticket_line_items($total_line_item);
935
+		foreach ($ticket_line_items as $ticket_line_item) {
936
+			if ($ticket_line_item instanceof EE_Line_Item) {
937
+				$valid_reserved_tickets[] = $ticket_line_item;
938
+			}
939
+		}
940
+		return $valid_reserved_tickets;
941
+	}
942
+
943
+
944
+
945
+	/**
946
+	 * @param EE_Ticket[]    $tickets_with_reservations
947
+	 * @param EE_Line_Item[] $valid_reserved_ticket_line_items
948
+	 * @return int
949
+	 * @throws \EE_Error
950
+	 */
951
+	private static function release_reservations_for_tickets(
952
+		array $tickets_with_reservations,
953
+		$valid_reserved_ticket_line_items = array()
954
+	) {
955
+		$total_tickets_released = 0;
956
+		foreach ($tickets_with_reservations as $ticket_with_reservations) {
957
+			if (! $ticket_with_reservations instanceof EE_Ticket) {
958
+				continue;
959
+			}
960
+			$reserved_qty = $ticket_with_reservations->reserved();
961
+			foreach ($valid_reserved_ticket_line_items as $valid_reserved_ticket_line_item) {
962
+				if (
963
+					$valid_reserved_ticket_line_item instanceof EE_Line_Item
964
+					&& $valid_reserved_ticket_line_item->OBJ_ID() === $ticket_with_reservations->ID()
965
+				) {
966
+					$reserved_qty -= $valid_reserved_ticket_line_item->quantity();
967
+				}
968
+			}
969
+			if ($reserved_qty > 0) {
970
+				$ticket_with_reservations->decrease_reserved($reserved_qty);
971
+				$ticket_with_reservations->save();
972
+				$total_tickets_released += $reserved_qty;
973
+			}
974
+		}
975
+		return $total_tickets_released;
976
+	}
977
+
978
+
979
+
980
+	/********************************** SHUTDOWN  **********************************/
981
+
982
+
983
+
984
+	/**
985
+	 * @return false|int
986
+	 * @throws EE_Error
987
+	 * @throws InvalidArgumentException
988
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
989
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
990
+	 */
991
+	public static function clear_expired_line_items_with_no_transaction()
992
+	{
993
+		/** @type WPDB $wpdb */
994
+		global $wpdb;
995
+		return $wpdb->query(
996
+			$wpdb->prepare(
997
+				'DELETE FROM ' . EEM_Line_Item::instance()->table() . '
998 998
                 WHERE TXN_ID = 0 AND LIN_timestamp <= %s',
999
-                // use GMT time because that's what LIN_timestamps are in
1000
-                date('Y-m-d H:i:s', time() - EE_Registry::instance()->SSN->lifespan())
1001
-            )
1002
-        );
1003
-    }
999
+				// use GMT time because that's what LIN_timestamps are in
1000
+				date('Y-m-d H:i:s', time() - EE_Registry::instance()->SSN->lifespan())
1001
+			)
1002
+		);
1003
+	}
1004 1004
 
1005 1005
 }
1006 1006
 // End of file EED_Ticket_Sales_Monitor.module.php
Please login to merge, or discard this patch.
Spacing   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -2,7 +2,7 @@  discard block
 block discarded – undo
2 2
 
3 3
 use EventEspresso\core\exceptions\UnexpectedEntityException;
4 4
 
5
-if (! defined('EVENT_ESPRESSO_VERSION')) {
5
+if ( ! defined('EVENT_ESPRESSO_VERSION')) {
6 6
     exit('No direct script access allowed');
7 7
 }
8 8
 
@@ -22,7 +22,7 @@  discard block
 block discarded – undo
22 22
 class EED_Ticket_Sales_Monitor extends EED_Module
23 23
 {
24 24
 
25
-    const debug = false;    //	true false
25
+    const debug = false; //	true false
26 26
 
27 27
     /**
28 28
      * an array of raw ticket data from EED_Ticket_Selector
@@ -206,7 +206,7 @@  discard block
 block discarded – undo
206 206
             /** @var EE_Line_Item $total_line_item */
207 207
             $ticket_line_items = EED_Ticket_Sales_Monitor::get_ticket_line_items_for_grand_total($total_line_item);
208 208
             foreach ($ticket_line_items as $ticket_line_item) {
209
-                if (! $ticket_line_item instanceof EE_Line_Item) {
209
+                if ( ! $ticket_line_item instanceof EE_Line_Item) {
210 210
                     continue;
211 211
                 }
212 212
                 if ($total_line_item->timestamp(true) <= $expired) {
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
                 }
217 217
             }
218 218
         }
219
-        if (! empty($expired_ticket_IDs)) {
219
+        if ( ! empty($expired_ticket_IDs)) {
220 220
             EED_Ticket_Sales_Monitor::release_reservations_for_tickets(
221 221
                 \EEM_Ticket::instance()->get_tickets_with_IDs($expired_ticket_IDs),
222 222
                 $valid_ticket_line_items
@@ -258,8 +258,8 @@  discard block
 block discarded – undo
258 258
             $qty = EED_Ticket_Sales_Monitor::instance()->_validate_ticket_sale($ticket, $qty);
259 259
         }
260 260
         if (self::debug) {
261
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '()';
262
-            echo '<br /><br /><b> RETURNED QTY: ' . $qty . '</b>';
261
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'()';
262
+            echo '<br /><br /><b> RETURNED QTY: '.$qty.'</b>';
263 263
         }
264 264
         return $qty;
265 265
     }
@@ -278,36 +278,36 @@  discard block
 block discarded – undo
278 278
     protected function _validate_ticket_sale(EE_Ticket $ticket, $qty = 1)
279 279
     {
280 280
         if (self::debug) {
281
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
281
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
282 282
         }
283
-        if (! $ticket instanceof EE_Ticket) {
283
+        if ( ! $ticket instanceof EE_Ticket) {
284 284
             return 0;
285 285
         }
286 286
         if (self::debug) {
287
-            echo '<br /><b> . ticket->ID: ' . $ticket->ID() . '</b>';
288
-            echo '<br /> . original ticket->reserved: ' . $ticket->reserved();
287
+            echo '<br /><b> . ticket->ID: '.$ticket->ID().'</b>';
288
+            echo '<br /> . original ticket->reserved: '.$ticket->reserved();
289 289
         }
290 290
         $ticket->refresh_from_db();
291 291
         // first let's determine the ticket availability based on sales
292 292
         $available = $ticket->qty('saleable');
293 293
         if (self::debug) {
294
-            echo '<br /> . . . ticket->qty: ' . $ticket->qty();
295
-            echo '<br /> . . . ticket->sold: ' . $ticket->sold();
296
-            echo '<br /> . . . ticket->reserved: ' . $ticket->reserved();
297
-            echo '<br /> . . . ticket->qty(saleable): ' . $ticket->qty('saleable');
298
-            echo '<br /> . . . available: ' . $available;
294
+            echo '<br /> . . . ticket->qty: '.$ticket->qty();
295
+            echo '<br /> . . . ticket->sold: '.$ticket->sold();
296
+            echo '<br /> . . . ticket->reserved: '.$ticket->reserved();
297
+            echo '<br /> . . . ticket->qty(saleable): '.$ticket->qty('saleable');
298
+            echo '<br /> . . . available: '.$available;
299 299
         }
300 300
         if ($available < 1) {
301 301
             $this->_ticket_sold_out($ticket);
302 302
             return 0;
303 303
         }
304 304
         if (self::debug) {
305
-            echo '<br /> . . . qty: ' . $qty;
305
+            echo '<br /> . . . qty: '.$qty;
306 306
         }
307 307
         if ($available < $qty) {
308 308
             $qty = $available;
309 309
             if (self::debug) {
310
-                echo '<br /> . . . QTY ADJUSTED: ' . $qty;
310
+                echo '<br /> . . . QTY ADJUSTED: '.$qty;
311 311
             }
312 312
             $this->_ticket_quantity_decremented($ticket);
313 313
         }
@@ -328,7 +328,7 @@  discard block
 block discarded – undo
328 328
     protected function _reserve_ticket(EE_Ticket $ticket, $quantity = 1)
329 329
     {
330 330
         if (self::debug) {
331
-            echo '<br /><br /> . . . INCREASE RESERVED: ' . $quantity;
331
+            echo '<br /><br /> . . . INCREASE RESERVED: '.$quantity;
332 332
         }
333 333
         $ticket->increase_reserved($quantity);
334 334
         return $ticket->save();
@@ -345,12 +345,12 @@  discard block
 block discarded – undo
345 345
     protected function _release_reserved_ticket(EE_Ticket $ticket, $quantity = 1)
346 346
     {
347 347
         if (self::debug) {
348
-            echo '<br /> . . . ticket->ID: ' . $ticket->ID();
349
-            echo '<br /> . . . ticket->reserved before: ' . $ticket->reserved();
348
+            echo '<br /> . . . ticket->ID: '.$ticket->ID();
349
+            echo '<br /> . . . ticket->reserved before: '.$ticket->reserved();
350 350
         }
351 351
         $ticket->decrease_reserved($quantity);
352 352
         if (self::debug) {
353
-            echo '<br /> . . . ticket->reserved after: ' . $ticket->reserved();
353
+            echo '<br /> . . . ticket->reserved after: '.$ticket->reserved();
354 354
         }
355 355
         return $ticket->save() ? 1 : 0;
356 356
     }
@@ -368,8 +368,8 @@  discard block
 block discarded – undo
368 368
     protected function _ticket_sold_out(EE_Ticket $ticket)
369 369
     {
370 370
         if (self::debug) {
371
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
372
-            echo '<br /> . . ticket->name: ' . $this->_get_ticket_and_event_name($ticket);
371
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
372
+            echo '<br /> . . ticket->name: '.$this->_get_ticket_and_event_name($ticket);
373 373
         }
374 374
         $this->sold_out_tickets[] = $this->_get_ticket_and_event_name($ticket);
375 375
     }
@@ -387,8 +387,8 @@  discard block
 block discarded – undo
387 387
     protected function _ticket_quantity_decremented(EE_Ticket $ticket)
388 388
     {
389 389
         if (self::debug) {
390
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
391
-            echo '<br /> . . ticket->name: ' . $this->_get_ticket_and_event_name($ticket);
390
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
391
+            echo '<br /> . . ticket->name: '.$this->_get_ticket_and_event_name($ticket);
392 392
         }
393 393
         $this->decremented_tickets[] = $this->_get_ticket_and_event_name($ticket);
394 394
     }
@@ -494,18 +494,18 @@  discard block
 block discarded – undo
494 494
     protected function _post_notices()
495 495
     {
496 496
         if (self::debug) {
497
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
497
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
498 498
         }
499 499
         $refresh_msg    = '';
500 500
         $none_added_msg = '';
501 501
         if (defined('DOING_AJAX') && DOING_AJAX) {
502
-            $refresh_msg    = __(
502
+            $refresh_msg = __(
503 503
                 'Please refresh the page to view updated ticket quantities.',
504 504
                 'event_espresso'
505 505
             );
506 506
             $none_added_msg = __('No tickets were added for the event.', 'event_espresso');
507 507
         }
508
-        if (! empty($this->sold_out_tickets)) {
508
+        if ( ! empty($this->sold_out_tickets)) {
509 509
             EE_Error::add_attention(
510 510
                 sprintf(
511 511
                     apply_filters(
@@ -528,7 +528,7 @@  discard block
 block discarded – undo
528 528
             // and reset the cart
529 529
             EED_Ticket_Sales_Monitor::session_cart_reset(EE_Registry::instance()->SSN);
530 530
         }
531
-        if (! empty($this->decremented_tickets)) {
531
+        if ( ! empty($this->decremented_tickets)) {
532 532
             EE_Error::add_attention(
533 533
                 sprintf(
534 534
                     apply_filters(
@@ -566,8 +566,8 @@  discard block
 block discarded – undo
566 566
     protected function _release_all_reserved_tickets_for_transaction(EE_Transaction $transaction)
567 567
     {
568 568
         if (self::debug) {
569
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
570
-            echo '<br /> . transaction->ID: ' . $transaction->ID();
569
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
570
+            echo '<br /> . transaction->ID: '.$transaction->ID();
571 571
         }
572 572
         // check if 'finalize_registration' step has been completed...
573 573
         $finalized = $transaction->reg_step_completed('finalize_registration');
@@ -579,13 +579,13 @@  discard block
 block discarded – undo
579 579
                 __LINE__,
580 580
                 array('finalized' => $finalized),
581 581
                 false,
582
-                'EE_Transaction: ' . $transaction->ID()
582
+                'EE_Transaction: '.$transaction->ID()
583 583
             );
584 584
         }
585 585
         // how many tickets were released
586 586
         $count = 0;
587 587
         if (self::debug) {
588
-            echo '<br /> . . . finalized: ' . $finalized;
588
+            echo '<br /> . . . finalized: '.$finalized;
589 589
         }
590 590
         $release_tickets_with_TXN_status = array(
591 591
             EEM_Transaction::failed_status_code,
@@ -593,10 +593,10 @@  discard block
 block discarded – undo
593 593
             EEM_Transaction::incomplete_status_code,
594 594
         );
595 595
         // if the session is getting cleared BEFORE the TXN has been finalized
596
-        if (! $finalized || in_array($transaction->status_ID(), $release_tickets_with_TXN_status, true)) {
596
+        if ( ! $finalized || in_array($transaction->status_ID(), $release_tickets_with_TXN_status, true)) {
597 597
             // let's cancel any reserved tickets
598 598
             $registrations = $transaction->registrations();
599
-            if (! empty($registrations)) {
599
+            if ( ! empty($registrations)) {
600 600
                 foreach ($registrations as $registration) {
601 601
                     if ($registration instanceof EE_Registration) {
602 602
                         $count += $this->_release_reserved_ticket_for_registration($registration, $transaction);
@@ -624,10 +624,10 @@  discard block
 block discarded – undo
624 624
     ) {
625 625
         $STS_ID = $transaction->status_ID();
626 626
         if (self::debug) {
627
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
628
-            echo '<br /> . . registration->ID: ' . $registration->ID();
629
-            echo '<br /> . . registration->status_ID: ' . $registration->status_ID();
630
-            echo '<br /> . . transaction->status_ID(): ' . $STS_ID;
627
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
628
+            echo '<br /> . . registration->ID: '.$registration->ID();
629
+            echo '<br /> . . registration->status_ID: '.$registration->status_ID();
630
+            echo '<br /> . . transaction->status_ID(): '.$STS_ID;
631 631
         }
632 632
         if (
633 633
             // release Tickets for Failed Transactions and Abandoned Transactions
@@ -671,7 +671,7 @@  discard block
 block discarded – undo
671 671
             return;
672 672
         }
673 673
         if (self::debug) {
674
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
674
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
675 675
         }
676 676
         $cart = $session->cart();
677 677
         if ($cart instanceof EE_Cart) {
@@ -703,28 +703,28 @@  discard block
 block discarded – undo
703 703
     protected function _session_cart_reset(EE_Cart $cart)
704 704
     {
705 705
         if (self::debug) {
706
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
706
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
707 707
         }
708 708
         $ticket_line_items = $cart->get_tickets();
709 709
         if (empty($ticket_line_items)) {
710 710
             return;
711 711
         }
712 712
         if (self::debug) {
713
-            echo '<br /> . ticket_line_item count: ' . count($ticket_line_items);
713
+            echo '<br /> . ticket_line_item count: '.count($ticket_line_items);
714 714
         }
715 715
         foreach ($ticket_line_items as $ticket_line_item) {
716 716
             if (self::debug) {
717
-                echo '<br /> . . ticket_line_item->ID(): ' . $ticket_line_item->ID();
717
+                echo '<br /> . . ticket_line_item->ID(): '.$ticket_line_item->ID();
718 718
             }
719 719
             if ($ticket_line_item instanceof EE_Line_Item && $ticket_line_item->OBJ_type() === 'Ticket') {
720 720
                 if (self::debug) {
721
-                    echo '<br /> . . . ticket_line_item->OBJ_ID(): ' . $ticket_line_item->OBJ_ID();
721
+                    echo '<br /> . . . ticket_line_item->OBJ_ID(): '.$ticket_line_item->OBJ_ID();
722 722
                 }
723 723
                 $ticket = EEM_Ticket::instance()->get_one_by_ID($ticket_line_item->OBJ_ID());
724 724
                 if ($ticket instanceof EE_Ticket) {
725 725
                     if (self::debug) {
726
-                        echo '<br /> . . . ticket->ID(): ' . $ticket->ID();
727
-                        echo '<br /> . . . ticket_line_item->quantity(): ' . $ticket_line_item->quantity();
726
+                        echo '<br /> . . . ticket->ID(): '.$ticket->ID();
727
+                        echo '<br /> . . . ticket_line_item->quantity(): '.$ticket_line_item->quantity();
728 728
                     }
729 729
                     $this->_release_reserved_ticket($ticket, $ticket_line_item->quantity());
730 730
                 }
@@ -752,7 +752,7 @@  discard block
 block discarded – undo
752 752
     public static function session_checkout_reset(EE_Session $session)
753 753
     {
754 754
         // don't release tickets if cart was already reset
755
-        if(did_action('AHEE__EE_Session__reset_cart__before_reset')) {
755
+        if (did_action('AHEE__EE_Session__reset_cart__before_reset')) {
756 756
             return;
757 757
         }
758 758
         $checkout = $session->checkout();
@@ -774,7 +774,7 @@  discard block
 block discarded – undo
774 774
     protected function _session_checkout_reset(EE_Checkout $checkout)
775 775
     {
776 776
         if (self::debug) {
777
-            echo '<br /><br /> ' . __LINE__ . ') ' . __METHOD__ . '() ';
777
+            echo '<br /><br /> '.__LINE__.') '.__METHOD__.'() ';
778 778
         }
779 779
         // we want to release the each registration's reserved tickets if the session was cleared, but not if this is a revisit
780 780
         if ($checkout->revisit || ! $checkout->transaction instanceof EE_Transaction) {
@@ -824,7 +824,7 @@  discard block
 block discarded – undo
824 824
                     __LINE__,
825 825
                     array($transaction),
826 826
                     false,
827
-                    'EE_Transaction: ' . $transaction->ID()
827
+                    'EE_Transaction: '.$transaction->ID()
828 828
                 );
829 829
             }
830 830
             return;
@@ -841,7 +841,7 @@  discard block
 block discarded – undo
841 841
                         __LINE__,
842 842
                         array($payment),
843 843
                         false,
844
-                        'EE_Transaction: ' . $transaction->ID()
844
+                        'EE_Transaction: '.$transaction->ID()
845 845
                     );
846 846
                 }
847 847
                 return;
@@ -897,7 +897,7 @@  discard block
 block discarded – undo
897 897
             /** @var EE_Transaction $transaction_in_progress */
898 898
             $total_line_item = $transaction_in_progress->total_line_item();
899 899
             // $transaction_in_progress->line
900
-            if (! $total_line_item instanceof EE_Line_Item) {
900
+            if ( ! $total_line_item instanceof EE_Line_Item) {
901 901
                 throw new DomainException(
902 902
                     esc_html__(
903 903
                         'Transaction does not have a valid Total Line Item associated with it.',
@@ -954,7 +954,7 @@  discard block
 block discarded – undo
954 954
     ) {
955 955
         $total_tickets_released = 0;
956 956
         foreach ($tickets_with_reservations as $ticket_with_reservations) {
957
-            if (! $ticket_with_reservations instanceof EE_Ticket) {
957
+            if ( ! $ticket_with_reservations instanceof EE_Ticket) {
958 958
                 continue;
959 959
             }
960 960
             $reserved_qty = $ticket_with_reservations->reserved();
@@ -994,7 +994,7 @@  discard block
 block discarded – undo
994 994
         global $wpdb;
995 995
         return $wpdb->query(
996 996
             $wpdb->prepare(
997
-                'DELETE FROM ' . EEM_Line_Item::instance()->table() . '
997
+                'DELETE FROM '.EEM_Line_Item::instance()->table().'
998 998
                 WHERE TXN_ID = 0 AND LIN_timestamp <= %s',
999 999
                 // use GMT time because that's what LIN_timestamps are in
1000 1000
                 date('Y-m-d H:i:s', time() - EE_Registry::instance()->SSN->lifespan())
Please login to merge, or discard this patch.