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