Completed
Branch FET/conditional-update-queries... (b217a8)
by
unknown
25:52 queued 17:21
created
core/db_classes/EE_Ticket.class.php 1 patch
Indentation   +1466 added lines, -1466 removed lines patch added patch discarded remove patch
@@ -12,1470 +12,1470 @@
 block discarded – undo
12 12
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
13 13
 {
14 14
 
15
-    /**
16
-     * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
17
-     */
18
-    const sold_out = 'TKS';
19
-
20
-    /**
21
-     *
22
-     */
23
-    const expired = 'TKE';
24
-
25
-    /**
26
-     *
27
-     */
28
-    const archived = 'TKA';
29
-
30
-    /**
31
-     *
32
-     */
33
-    const pending = 'TKP';
34
-
35
-    /**
36
-     *
37
-     */
38
-    const onsale = 'TKO';
39
-
40
-    /**
41
-     * extra meta key for tracking ticket reservations
42
-     *
43
-     * @type string
44
-     */
45
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
46
-
47
-    /**
48
-     * cached result from method of the same name
49
-     *
50
-     * @var float $_ticket_total_with_taxes
51
-     */
52
-    private $_ticket_total_with_taxes;
53
-
54
-
55
-    /**
56
-     * @param array  $props_n_values          incoming values
57
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
58
-     *                                        used.)
59
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
60
-     *                                        date_format and the second value is the time format
61
-     * @return EE_Ticket
62
-     * @throws \EE_Error
63
-     */
64
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
65
-    {
66
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
67
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
68
-    }
69
-
70
-
71
-    /**
72
-     * @param array  $props_n_values  incoming values from the database
73
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
74
-     *                                the website will be used.
75
-     * @return EE_Ticket
76
-     * @throws \EE_Error
77
-     */
78
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
79
-    {
80
-        return new self($props_n_values, true, $timezone);
81
-    }
82
-
83
-
84
-    /**
85
-     * @return bool
86
-     * @throws \EE_Error
87
-     */
88
-    public function parent()
89
-    {
90
-        return $this->get('TKT_parent');
91
-    }
92
-
93
-
94
-    /**
95
-     * return if a ticket has quantities available for purchase
96
-     *
97
-     * @param  int $DTT_ID the primary key for a particular datetime
98
-     * @return boolean
99
-     * @throws \EE_Error
100
-     */
101
-    public function available($DTT_ID = 0)
102
-    {
103
-        // are we checking availability for a particular datetime ?
104
-        if ($DTT_ID) {
105
-            // get that datetime object
106
-            $datetime = $this->get_first_related('Datetime', array(array('DTT_ID' => $DTT_ID)));
107
-            // if  ticket sales for this datetime have exceeded the reg limit...
108
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
109
-                return false;
110
-            }
111
-        }
112
-        // datetime is still open for registration, but is this ticket sold out ?
113
-        return $this->qty() < 1 || $this->qty() > $this->sold() ? true : false;
114
-    }
115
-
116
-
117
-    /**
118
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
119
-     *
120
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
121
-     *                               relevant status const
122
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
123
-     *               further processing
124
-     * @return mixed status int if the display string isn't requested
125
-     * @throws \EE_Error
126
-     */
127
-    public function ticket_status($display = false, $remaining = null)
128
-    {
129
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
130
-        if (! $remaining) {
131
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
132
-        }
133
-        if ($this->get('TKT_deleted')) {
134
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
135
-        }
136
-        if ($this->is_expired()) {
137
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
138
-        }
139
-        if ($this->is_pending()) {
140
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
141
-        }
142
-        if ($this->is_on_sale()) {
143
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
144
-        }
145
-        return '';
146
-    }
147
-
148
-
149
-    /**
150
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
151
-     * considering ALL the factors used for figuring that out.
152
-     *
153
-     * @access public
154
-     * @param  int $DTT_ID if an int above 0 is included here then we get a specific dtt.
155
-     * @return boolean         true = tickets remaining, false not.
156
-     * @throws \EE_Error
157
-     */
158
-    public function is_remaining($DTT_ID = 0)
159
-    {
160
-        $num_remaining = $this->remaining($DTT_ID);
161
-        if ($num_remaining === 0) {
162
-            return false;
163
-        }
164
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
165
-            return false;
166
-        }
167
-        return true;
168
-    }
169
-
170
-
171
-    /**
172
-     * return the total number of tickets available for purchase
173
-     *
174
-     * @param  int $DTT_ID the primary key for a particular datetime.
175
-     *                     set to 0 for all related datetimes
176
-     * @return int
177
-     * @throws \EE_Error
178
-     */
179
-    public function remaining($DTT_ID = 0)
180
-    {
181
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
182
-    }
183
-
184
-
185
-    /**
186
-     * Gets min
187
-     *
188
-     * @return int
189
-     * @throws \EE_Error
190
-     */
191
-    public function min()
192
-    {
193
-        return $this->get('TKT_min');
194
-    }
195
-
196
-
197
-    /**
198
-     * return if a ticket is no longer available cause its available dates have expired.
199
-     *
200
-     * @return boolean
201
-     * @throws \EE_Error
202
-     */
203
-    public function is_expired()
204
-    {
205
-        return ($this->get_raw('TKT_end_date') < time());
206
-    }
207
-
208
-
209
-    /**
210
-     * Return if a ticket is yet to go on sale or not
211
-     *
212
-     * @return boolean
213
-     * @throws \EE_Error
214
-     */
215
-    public function is_pending()
216
-    {
217
-        return ($this->get_raw('TKT_start_date') > time());
218
-    }
219
-
220
-
221
-    /**
222
-     * Return if a ticket is on sale or not
223
-     *
224
-     * @return boolean
225
-     * @throws \EE_Error
226
-     */
227
-    public function is_on_sale()
228
-    {
229
-        return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
230
-    }
231
-
232
-
233
-    /**
234
-     * This returns the chronologically last datetime that this ticket is associated with
235
-     *
236
-     * @param string $dt_frmt
237
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
238
-     *                            the end date ie: Jan 01 "to" Dec 31
239
-     * @return string
240
-     * @throws \EE_Error
241
-     */
242
-    public function date_range($dt_frmt = '', $conjunction = ' - ')
243
-    {
244
-        $first_date = $this->first_datetime() instanceof EE_Datetime ? $this->first_datetime()->start_date($dt_frmt)
245
-            : '';
246
-        $last_date = $this->last_datetime() instanceof EE_Datetime ? $this->last_datetime()->end_date($dt_frmt) : '';
247
-
248
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
249
-    }
250
-
251
-
252
-    /**
253
-     * This returns the chronologically first datetime that this ticket is associated with
254
-     *
255
-     * @return EE_Datetime
256
-     * @throws \EE_Error
257
-     */
258
-    public function first_datetime()
259
-    {
260
-        $datetimes = $this->datetimes(array('limit' => 1));
261
-        return reset($datetimes);
262
-    }
263
-
264
-
265
-    /**
266
-     * Gets all the datetimes this ticket can be used for attending.
267
-     * Unless otherwise specified, orders datetimes by start date.
268
-     *
269
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
270
-     * @return EE_Datetime[]|EE_Base_Class[]
271
-     * @throws \EE_Error
272
-     */
273
-    public function datetimes($query_params = array())
274
-    {
275
-        if (! isset($query_params['order_by'])) {
276
-            $query_params['order_by']['DTT_order'] = 'ASC';
277
-        }
278
-        return $this->get_many_related('Datetime', $query_params);
279
-    }
280
-
281
-
282
-    /**
283
-     * This returns the chronologically last datetime that this ticket is associated with
284
-     *
285
-     * @return EE_Datetime
286
-     * @throws \EE_Error
287
-     */
288
-    public function last_datetime()
289
-    {
290
-        $datetimes = $this->datetimes(array('limit' => 1, 'order_by' => array('DTT_EVT_start' => 'DESC')));
291
-        return end($datetimes);
292
-    }
293
-
294
-
295
-    /**
296
-     * This returns the total tickets sold depending on the given parameters.
297
-     *
298
-     * @param  string $what   Can be one of two options: 'ticket', 'datetime'.
299
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
300
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
301
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
302
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
303
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
304
-     * @param  int    $dtt_id [optional] include the dtt_id with $what = 'datetime'.
305
-     * @return mixed (array|int)          how many tickets have sold
306
-     * @throws \EE_Error
307
-     */
308
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
309
-    {
310
-        $total = 0;
311
-        $tickets_sold = $this->_all_tickets_sold();
312
-        switch ($what) {
313
-            case 'ticket':
314
-                return $tickets_sold['ticket'];
315
-                break;
316
-            case 'datetime':
317
-                if (empty($tickets_sold['datetime'])) {
318
-                    return $total;
319
-                }
320
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
321
-                    EE_Error::add_error(
322
-                        __(
323
-                            'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
324
-                            'event_espresso'
325
-                        ),
326
-                        __FILE__,
327
-                        __FUNCTION__,
328
-                        __LINE__
329
-                    );
330
-                    return $total;
331
-                }
332
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
333
-                break;
334
-            default:
335
-                return $total;
336
-        }
337
-    }
338
-
339
-
340
-    /**
341
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
342
-     *
343
-     * @return EE_Ticket[]
344
-     * @throws \EE_Error
345
-     */
346
-    protected function _all_tickets_sold()
347
-    {
348
-        $datetimes = $this->get_many_related('Datetime');
349
-        $tickets_sold = array();
350
-        if (! empty($datetimes)) {
351
-            foreach ($datetimes as $datetime) {
352
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
353
-            }
354
-        }
355
-        // Tickets sold
356
-        $tickets_sold['ticket'] = $this->sold();
357
-        return $tickets_sold;
358
-    }
359
-
360
-
361
-    /**
362
-     * This returns the base price object for the ticket.
363
-     *
364
-     * @param  bool $return_array whether to return as an array indexed by price id or just the object.
365
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
366
-     * @throws \EE_Error
367
-     */
368
-    public function base_price($return_array = false)
369
-    {
370
-        $_where = array('Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price);
371
-        return $return_array
372
-            ? $this->get_many_related('Price', array($_where))
373
-            : $this->get_first_related('Price', array($_where));
374
-    }
375
-
376
-
377
-    /**
378
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
379
-     *
380
-     * @access public
381
-     * @return EE_Price[]
382
-     * @throws \EE_Error
383
-     */
384
-    public function price_modifiers()
385
-    {
386
-        $query_params = array(
387
-            0 => array(
388
-                'Price_Type.PBT_ID' => array(
389
-                    'NOT IN',
390
-                    array(EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax),
391
-                ),
392
-            ),
393
-        );
394
-        return $this->prices($query_params);
395
-    }
396
-
397
-
398
-    /**
399
-     * Gets all the prices that combine to form the final price of this ticket
400
-     *
401
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
402
-     * @return EE_Price[]|EE_Base_Class[]
403
-     * @throws \EE_Error
404
-     */
405
-    public function prices($query_params = array())
406
-    {
407
-        return $this->get_many_related('Price', $query_params);
408
-    }
409
-
410
-
411
-    /**
412
-     * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
413
-     *
414
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
415
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
416
-     * @throws \EE_Error
417
-     */
418
-    public function datetime_tickets($query_params = array())
419
-    {
420
-        return $this->get_many_related('Datetime_Ticket', $query_params);
421
-    }
422
-
423
-
424
-    /**
425
-     * Gets all the datetimes from the db ordered by DTT_order
426
-     *
427
-     * @param boolean $show_expired
428
-     * @param boolean $show_deleted
429
-     * @return EE_Datetime[]
430
-     * @throws \EE_Error
431
-     */
432
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
433
-    {
434
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
435
-            $this->ID(),
436
-            $show_expired,
437
-            $show_deleted
438
-        );
439
-    }
440
-
441
-
442
-    /**
443
-     * Gets ID
444
-     *
445
-     * @return string
446
-     * @throws \EE_Error
447
-     */
448
-    public function ID()
449
-    {
450
-        return $this->get('TKT_ID');
451
-    }
452
-
453
-
454
-    /**
455
-     * get the author of the ticket.
456
-     *
457
-     * @since 4.5.0
458
-     * @return int
459
-     * @throws \EE_Error
460
-     */
461
-    public function wp_user()
462
-    {
463
-        return $this->get('TKT_wp_user');
464
-    }
465
-
466
-
467
-    /**
468
-     * Gets the template for the ticket
469
-     *
470
-     * @return EE_Ticket_Template|EE_Base_Class
471
-     * @throws \EE_Error
472
-     */
473
-    public function template()
474
-    {
475
-        return $this->get_first_related('Ticket_Template');
476
-    }
477
-
478
-
479
-    /**
480
-     * Simply returns an array of EE_Price objects that are taxes.
481
-     *
482
-     * @return EE_Price[]
483
-     * @throws \EE_Error
484
-     */
485
-    public function get_ticket_taxes_for_admin()
486
-    {
487
-        return EE_Taxes::get_taxes_for_admin();
488
-    }
489
-
490
-
491
-    /**
492
-     * @return float
493
-     * @throws \EE_Error
494
-     */
495
-    public function ticket_price()
496
-    {
497
-        return $this->get('TKT_price');
498
-    }
499
-
500
-
501
-    /**
502
-     * @return mixed
503
-     * @throws \EE_Error
504
-     */
505
-    public function pretty_price()
506
-    {
507
-        return $this->get_pretty('TKT_price');
508
-    }
509
-
510
-
511
-    /**
512
-     * @return bool
513
-     * @throws \EE_Error
514
-     */
515
-    public function is_free()
516
-    {
517
-        return $this->get_ticket_total_with_taxes() === (float) 0;
518
-    }
519
-
520
-
521
-    /**
522
-     * get_ticket_total_with_taxes
523
-     *
524
-     * @param bool $no_cache
525
-     * @return float
526
-     * @throws \EE_Error
527
-     */
528
-    public function get_ticket_total_with_taxes($no_cache = false)
529
-    {
530
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
531
-            $this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
532
-        }
533
-        return (float) $this->_ticket_total_with_taxes;
534
-    }
535
-
536
-
537
-    public function ensure_TKT_Price_correct()
538
-    {
539
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
540
-        $this->save();
541
-    }
542
-
543
-
544
-    /**
545
-     * @return float
546
-     * @throws \EE_Error
547
-     */
548
-    public function get_ticket_subtotal()
549
-    {
550
-        return EE_Taxes::get_subtotal_for_admin($this);
551
-    }
552
-
553
-
554
-    /**
555
-     * Returns the total taxes applied to this ticket
556
-     *
557
-     * @return float
558
-     * @throws \EE_Error
559
-     */
560
-    public function get_ticket_taxes_total_for_admin()
561
-    {
562
-        return EE_Taxes::get_total_taxes_for_admin($this);
563
-    }
564
-
565
-
566
-    /**
567
-     * Sets name
568
-     *
569
-     * @param string $name
570
-     * @throws \EE_Error
571
-     */
572
-    public function set_name($name)
573
-    {
574
-        $this->set('TKT_name', $name);
575
-    }
576
-
577
-
578
-    /**
579
-     * Gets description
580
-     *
581
-     * @return string
582
-     * @throws \EE_Error
583
-     */
584
-    public function description()
585
-    {
586
-        return $this->get('TKT_description');
587
-    }
588
-
589
-
590
-    /**
591
-     * Sets description
592
-     *
593
-     * @param string $description
594
-     * @throws \EE_Error
595
-     */
596
-    public function set_description($description)
597
-    {
598
-        $this->set('TKT_description', $description);
599
-    }
600
-
601
-
602
-    /**
603
-     * Gets start_date
604
-     *
605
-     * @param string $dt_frmt
606
-     * @param string $tm_frmt
607
-     * @return string
608
-     * @throws \EE_Error
609
-     */
610
-    public function start_date($dt_frmt = '', $tm_frmt = '')
611
-    {
612
-        return $this->_get_datetime('TKT_start_date', $dt_frmt, $tm_frmt);
613
-    }
614
-
615
-
616
-    /**
617
-     * Sets start_date
618
-     *
619
-     * @param string $start_date
620
-     * @return void
621
-     * @throws \EE_Error
622
-     */
623
-    public function set_start_date($start_date)
624
-    {
625
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
626
-    }
627
-
628
-
629
-    /**
630
-     * Gets end_date
631
-     *
632
-     * @param string $dt_frmt
633
-     * @param string $tm_frmt
634
-     * @return string
635
-     * @throws \EE_Error
636
-     */
637
-    public function end_date($dt_frmt = '', $tm_frmt = '')
638
-    {
639
-        return $this->_get_datetime('TKT_end_date', $dt_frmt, $tm_frmt);
640
-    }
641
-
642
-
643
-    /**
644
-     * Sets end_date
645
-     *
646
-     * @param string $end_date
647
-     * @return void
648
-     * @throws \EE_Error
649
-     */
650
-    public function set_end_date($end_date)
651
-    {
652
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
653
-    }
654
-
655
-
656
-    /**
657
-     * Sets sell until time
658
-     *
659
-     * @since 4.5.0
660
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
661
-     * @throws \EE_Error
662
-     */
663
-    public function set_end_time($time)
664
-    {
665
-        $this->_set_time_for($time, 'TKT_end_date');
666
-    }
667
-
668
-
669
-    /**
670
-     * Sets min
671
-     *
672
-     * @param int $min
673
-     * @return void
674
-     * @throws \EE_Error
675
-     */
676
-    public function set_min($min)
677
-    {
678
-        $this->set('TKT_min', $min);
679
-    }
680
-
681
-
682
-    /**
683
-     * Gets max
684
-     *
685
-     * @return int
686
-     * @throws \EE_Error
687
-     */
688
-    public function max()
689
-    {
690
-        return $this->get('TKT_max');
691
-    }
692
-
693
-
694
-    /**
695
-     * Sets max
696
-     *
697
-     * @param int $max
698
-     * @return void
699
-     * @throws \EE_Error
700
-     */
701
-    public function set_max($max)
702
-    {
703
-        $this->set('TKT_max', $max);
704
-    }
705
-
706
-
707
-    /**
708
-     * Sets price
709
-     *
710
-     * @param float $price
711
-     * @return void
712
-     * @throws \EE_Error
713
-     */
714
-    public function set_price($price)
715
-    {
716
-        $this->set('TKT_price', $price);
717
-    }
718
-
719
-
720
-    /**
721
-     * Gets sold
722
-     *
723
-     * @return int
724
-     * @throws \EE_Error
725
-     */
726
-    public function sold()
727
-    {
728
-        return $this->get_raw('TKT_sold');
729
-    }
730
-
731
-
732
-    /**
733
-     * Sets sold
734
-     *
735
-     * @param int $sold
736
-     * @return void
737
-     * @throws \EE_Error
738
-     */
739
-    public function set_sold($sold)
740
-    {
741
-        // sold can not go below zero
742
-        $sold = max(0, $sold);
743
-        $this->set('TKT_sold', $sold);
744
-    }
745
-
746
-
747
-    /**
748
-     * increments sold by amount passed by $qty
749
-     *
750
-     * @param int $qty
751
-     * @return void
752
-     * @throws \EE_Error
753
-     */
754
-    public function increase_sold($qty = 1)
755
-    {
756
-        $sold = $this->sold() + $qty;
757
-        // remove ticket reservation, but don't adjust datetime reservations,  because that will happen
758
-        // via \EE_Datetime::increase_sold() when \EE_Ticket::_increase_sold_for_datetimes() is called
759
-        $this->decrease_reserved($qty, false, "TKT: {$this->ID()} (ln:" . __LINE__ . ')');
760
-        $this->_increase_sold_for_datetimes($qty);
761
-        $this->set_sold($sold);
762
-        do_action(
763
-            'AHEE__EE_Ticket__increase_sold',
764
-            $this,
765
-            $qty,
766
-            $sold
767
-        );
768
-    }
769
-
770
-
771
-    /**
772
-     * Increases sold on related datetimes
773
-     *
774
-     * @param int $qty
775
-     * @return void
776
-     * @throws \EE_Error
777
-     */
778
-    protected function _increase_sold_for_datetimes($qty = 1)
779
-    {
780
-        $datetimes = $this->datetimes();
781
-        if (is_array($datetimes)) {
782
-            foreach ($datetimes as $datetime) {
783
-                if ($datetime instanceof EE_Datetime) {
784
-                    $datetime->increase_sold($qty);
785
-                    $datetime->save();
786
-                }
787
-            }
788
-        }
789
-    }
790
-
791
-
792
-    /**
793
-     * decrements (subtracts) sold by amount passed by $qty
794
-     *
795
-     * @param int $qty
796
-     * @return void
797
-     * @throws \EE_Error
798
-     */
799
-    public function decrease_sold($qty = 1)
800
-    {
801
-        $sold = $this->sold() - $qty;
802
-        $this->_decrease_sold_for_datetimes($qty);
803
-        $this->set_sold($sold);
804
-        do_action(
805
-            'AHEE__EE_Ticket__decrease_sold',
806
-            $this,
807
-            $qty,
808
-            $sold
809
-        );
810
-    }
811
-
812
-
813
-    /**
814
-     * Decreases sold on related datetimes
815
-     *
816
-     * @param int $qty
817
-     * @return void
818
-     * @throws \EE_Error
819
-     */
820
-    protected function _decrease_sold_for_datetimes($qty = 1)
821
-    {
822
-        $datetimes = $this->datetimes();
823
-        if (is_array($datetimes)) {
824
-            foreach ($datetimes as $datetime) {
825
-                if ($datetime instanceof EE_Datetime) {
826
-                    $datetime->decrease_sold($qty);
827
-                    $datetime->save();
828
-                }
829
-            }
830
-        }
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets qty of reserved tickets
836
-     *
837
-     * @return int
838
-     * @throws \EE_Error
839
-     */
840
-    public function reserved()
841
-    {
842
-        return $this->get_raw('TKT_reserved');
843
-    }
844
-
845
-
846
-    /**
847
-     * Sets reserved
848
-     *
849
-     * @param int $reserved
850
-     * @return void
851
-     * @throws \EE_Error
852
-     */
853
-    public function set_reserved($reserved)
854
-    {
855
-        // reserved can not go below zero
856
-        $reserved = max(0, (int) $reserved);
857
-        $this->set('TKT_reserved', $reserved);
858
-    }
859
-
860
-
861
-    /**
862
-     * increments reserved by amount passed by $qty
863
-     *
864
-     * @param int    $qty
865
-     * @param string $source
866
-     * @return void
867
-     * @throws EE_Error
868
-     * @throws InvalidArgumentException
869
-     * @throws ReflectionException
870
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
871
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
872
-     */
873
-    public function increase_reserved($qty = 1, $source = 'unknown')
874
-    {
875
-        $qty = absint($qty);
876
-        $reserved = $this->reserved() + $qty;
877
-        do_action(
878
-            'AHEE__EE_Ticket__increase_reserved__begin',
879
-            $this,
880
-            $qty,
881
-            $reserved
882
-        );
883
-        if ($reserved
884
-            && $this->add_extra_meta(
885
-                EE_Ticket::META_KEY_TICKET_RESERVATIONS,
886
-                "{$qty} from {$source}"
887
-            )
888
-        ) {
889
-            $this->_increase_reserved_for_datetimes($qty);
890
-            $this->set_reserved($reserved);
891
-            do_action(
892
-                'AHEE__EE_Ticket__increase_reserved',
893
-                $this,
894
-                $qty,
895
-                $reserved
896
-            );
897
-        }
898
-    }
899
-
900
-
901
-    /**
902
-     * Increases sold on related datetimes
903
-     *
904
-     * @param int $qty
905
-     * @return void
906
-     * @throws \EE_Error
907
-     */
908
-    protected function _increase_reserved_for_datetimes($qty = 1)
909
-    {
910
-        $datetimes = $this->datetimes();
911
-        if (is_array($datetimes)) {
912
-            foreach ($datetimes as $datetime) {
913
-                if ($datetime instanceof EE_Datetime) {
914
-                    $datetime->increase_reserved($qty);
915
-                    $datetime->save();
916
-                }
917
-            }
918
-        }
919
-    }
920
-
921
-
922
-    /**
923
-     * decrements (subtracts) reserved by amount passed by $qty
924
-     *
925
-     * @param int    $qty
926
-     * @param bool   $adjust_datetimes
927
-     * @param string $source
928
-     * @return void
929
-     * @throws EE_Error
930
-     * @throws InvalidArgumentException
931
-     * @throws ReflectionException
932
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
933
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
934
-     */
935
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
936
-    {
937
-        $reserved = $this->reserved() - absint($qty);
938
-        if ($this->add_extra_meta(
939
-            EE_Ticket::META_KEY_TICKET_RESERVATIONS,
940
-            "-{$qty} from {$source}"
941
-        )) {
942
-            if ($adjust_datetimes) {
943
-                $this->_decrease_reserved_for_datetimes($qty);
944
-            }
945
-            $this->set_reserved($reserved);
946
-            do_action(
947
-                'AHEE__EE_Ticket__decrease_reserved',
948
-                $this,
949
-                $qty,
950
-                $reserved
951
-            );
952
-        }
953
-    }
954
-
955
-
956
-    /**
957
-     * Increases sold on related datetimes
958
-     *
959
-     * @param int $qty
960
-     * @return void
961
-     * @throws \EE_Error
962
-     */
963
-    protected function _decrease_reserved_for_datetimes($qty = 1)
964
-    {
965
-        $datetimes = $this->datetimes();
966
-        if (is_array($datetimes)) {
967
-            foreach ($datetimes as $datetime) {
968
-                if ($datetime instanceof EE_Datetime) {
969
-                    $datetime->decrease_reserved($qty);
970
-                    $datetime->save();
971
-                }
972
-            }
973
-        }
974
-    }
975
-
976
-
977
-    /**
978
-     * Gets ticket quantity
979
-     *
980
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
981
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
982
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
983
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
984
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
985
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
986
-     * @return int
987
-     * @throws \EE_Error
988
-     */
989
-    public function qty($context = '')
990
-    {
991
-        switch ($context) {
992
-            case 'reg_limit':
993
-                return $this->real_quantity_on_ticket();
994
-            case 'saleable':
995
-                return $this->real_quantity_on_ticket('saleable');
996
-            default:
997
-                return $this->get_raw('TKT_qty');
998
-        }
999
-    }
1000
-
1001
-
1002
-    /**
1003
-     * Gets ticket quantity
1004
-     *
1005
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1006
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1007
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1008
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1009
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1010
-     * @param  int   $DTT_ID      the primary key for a particular datetime.
1011
-     *                            set to 0 for all related datetimes
1012
-     * @return int
1013
-     * @throws \EE_Error
1014
-     */
1015
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1016
-    {
1017
-        $raw = $this->get_raw('TKT_qty');
1018
-        // return immediately if it's zero
1019
-        if ($raw === 0) {
1020
-            return $raw;
1021
-        }
1022
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1023
-        // ensure qty doesn't exceed raw value for THIS ticket
1024
-        $qty = min(EE_INF, $raw);
1025
-        // echo "\n . qty: " . $qty . '<br />';
1026
-        // calculate this ticket's total sales and reservations
1027
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1028
-        // echo "\n . sold: " . $this->sold() . '<br />';
1029
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1030
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1031
-        // first we need to calculate the maximum number of tickets available for the datetime
1032
-        // do we want data for one datetime or all of them ?
1033
-        $query_params = $DTT_ID ? array(array('DTT_ID' => $DTT_ID)) : array();
1034
-        $datetimes = $this->datetimes($query_params);
1035
-        if (is_array($datetimes) && ! empty($datetimes)) {
1036
-            foreach ($datetimes as $datetime) {
1037
-                if ($datetime instanceof EE_Datetime) {
1038
-                    $datetime->refresh_from_db();
1039
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1040
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1041
-                    // initialize with no restrictions for each datetime
1042
-                    // but adjust datetime qty based on datetime reg limit
1043
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1044
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1045
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1046
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1047
-                    // and reservations for this datetime, that do NOT include sales and reservations
1048
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1049
-                    if ($context === 'saleable') {
1050
-                        $datetime_qty = max(
1051
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1052
-                            0
1053
-                        );
1054
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1055
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1056
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1057
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1058
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1059
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1060
-                    }
1061
-                    $qty = min($datetime_qty, $qty);
1062
-                    // echo "\n . . qty: " . $qty . '<br />';
1063
-                }
1064
-            }
1065
-        }
1066
-        // NOW that we know the  maximum number of tickets available for the datetime
1067
-        // we can finally factor in the details for this specific ticket
1068
-        if ($qty > 0 && $context === 'saleable') {
1069
-            // and subtract the sales for THIS ticket
1070
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1071
-            // echo "\n . qty: " . $qty . '<br />';
1072
-        }
1073
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1074
-        return $qty;
1075
-    }
1076
-
1077
-
1078
-    /**
1079
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1080
-     *
1081
-     * @param int $qty
1082
-     * @return void
1083
-     * @throws \EE_Error
1084
-     */
1085
-    public function set_qty($qty)
1086
-    {
1087
-        $datetimes = $this->datetimes();
1088
-        foreach ($datetimes as $datetime) {
1089
-            if ($datetime instanceof EE_Datetime) {
1090
-                $qty = min($qty, $datetime->reg_limit());
1091
-            }
1092
-        }
1093
-        $this->set('TKT_qty', $qty);
1094
-    }
1095
-
1096
-
1097
-    /**
1098
-     * Gets uses
1099
-     *
1100
-     * @return int
1101
-     * @throws \EE_Error
1102
-     */
1103
-    public function uses()
1104
-    {
1105
-        return $this->get('TKT_uses');
1106
-    }
1107
-
1108
-
1109
-    /**
1110
-     * Sets uses
1111
-     *
1112
-     * @param int $uses
1113
-     * @return void
1114
-     * @throws \EE_Error
1115
-     */
1116
-    public function set_uses($uses)
1117
-    {
1118
-        $this->set('TKT_uses', $uses);
1119
-    }
1120
-
1121
-
1122
-    /**
1123
-     * returns whether ticket is required or not.
1124
-     *
1125
-     * @return boolean
1126
-     * @throws \EE_Error
1127
-     */
1128
-    public function required()
1129
-    {
1130
-        return $this->get('TKT_required');
1131
-    }
1132
-
1133
-
1134
-    /**
1135
-     * sets the TKT_required property
1136
-     *
1137
-     * @param boolean $required
1138
-     * @return void
1139
-     * @throws \EE_Error
1140
-     */
1141
-    public function set_required($required)
1142
-    {
1143
-        $this->set('TKT_required', $required);
1144
-    }
1145
-
1146
-
1147
-    /**
1148
-     * Gets taxable
1149
-     *
1150
-     * @return boolean
1151
-     * @throws \EE_Error
1152
-     */
1153
-    public function taxable()
1154
-    {
1155
-        return $this->get('TKT_taxable');
1156
-    }
1157
-
1158
-
1159
-    /**
1160
-     * Sets taxable
1161
-     *
1162
-     * @param boolean $taxable
1163
-     * @return void
1164
-     * @throws \EE_Error
1165
-     */
1166
-    public function set_taxable($taxable)
1167
-    {
1168
-        $this->set('TKT_taxable', $taxable);
1169
-    }
1170
-
1171
-
1172
-    /**
1173
-     * Gets is_default
1174
-     *
1175
-     * @return boolean
1176
-     * @throws \EE_Error
1177
-     */
1178
-    public function is_default()
1179
-    {
1180
-        return $this->get('TKT_is_default');
1181
-    }
1182
-
1183
-
1184
-    /**
1185
-     * Sets is_default
1186
-     *
1187
-     * @param boolean $is_default
1188
-     * @return void
1189
-     * @throws \EE_Error
1190
-     */
1191
-    public function set_is_default($is_default)
1192
-    {
1193
-        $this->set('TKT_is_default', $is_default);
1194
-    }
1195
-
1196
-
1197
-    /**
1198
-     * Gets order
1199
-     *
1200
-     * @return int
1201
-     * @throws \EE_Error
1202
-     */
1203
-    public function order()
1204
-    {
1205
-        return $this->get('TKT_order');
1206
-    }
1207
-
1208
-
1209
-    /**
1210
-     * Sets order
1211
-     *
1212
-     * @param int $order
1213
-     * @return void
1214
-     * @throws \EE_Error
1215
-     */
1216
-    public function set_order($order)
1217
-    {
1218
-        $this->set('TKT_order', $order);
1219
-    }
1220
-
1221
-
1222
-    /**
1223
-     * Gets row
1224
-     *
1225
-     * @return int
1226
-     * @throws \EE_Error
1227
-     */
1228
-    public function row()
1229
-    {
1230
-        return $this->get('TKT_row');
1231
-    }
1232
-
1233
-
1234
-    /**
1235
-     * Sets row
1236
-     *
1237
-     * @param int $row
1238
-     * @return void
1239
-     * @throws \EE_Error
1240
-     */
1241
-    public function set_row($row)
1242
-    {
1243
-        $this->set('TKT_row', $row);
1244
-    }
1245
-
1246
-
1247
-    /**
1248
-     * Gets deleted
1249
-     *
1250
-     * @return boolean
1251
-     * @throws \EE_Error
1252
-     */
1253
-    public function deleted()
1254
-    {
1255
-        return $this->get('TKT_deleted');
1256
-    }
1257
-
1258
-
1259
-    /**
1260
-     * Sets deleted
1261
-     *
1262
-     * @param boolean $deleted
1263
-     * @return void
1264
-     * @throws \EE_Error
1265
-     */
1266
-    public function set_deleted($deleted)
1267
-    {
1268
-        $this->set('TKT_deleted', $deleted);
1269
-    }
1270
-
1271
-
1272
-    /**
1273
-     * Gets parent
1274
-     *
1275
-     * @return int
1276
-     * @throws \EE_Error
1277
-     */
1278
-    public function parent_ID()
1279
-    {
1280
-        return $this->get('TKT_parent');
1281
-    }
1282
-
1283
-
1284
-    /**
1285
-     * Sets parent
1286
-     *
1287
-     * @param int $parent
1288
-     * @return void
1289
-     * @throws \EE_Error
1290
-     */
1291
-    public function set_parent_ID($parent)
1292
-    {
1293
-        $this->set('TKT_parent', $parent);
1294
-    }
1295
-
1296
-
1297
-    /**
1298
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1299
-     *
1300
-     * @return string
1301
-     * @throws \EE_Error
1302
-     */
1303
-    public function name_and_info()
1304
-    {
1305
-        $times = array();
1306
-        foreach ($this->datetimes() as $datetime) {
1307
-            $times[] = $datetime->start_date_and_time();
1308
-        }
1309
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1310
-    }
1311
-
1312
-
1313
-    /**
1314
-     * Gets name
1315
-     *
1316
-     * @return string
1317
-     * @throws \EE_Error
1318
-     */
1319
-    public function name()
1320
-    {
1321
-        return $this->get('TKT_name');
1322
-    }
1323
-
1324
-
1325
-    /**
1326
-     * Gets price
1327
-     *
1328
-     * @return float
1329
-     * @throws \EE_Error
1330
-     */
1331
-    public function price()
1332
-    {
1333
-        return $this->get('TKT_price');
1334
-    }
1335
-
1336
-
1337
-    /**
1338
-     * Gets all the registrations for this ticket
1339
-     *
1340
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1341
-     * @return EE_Registration[]|EE_Base_Class[]
1342
-     * @throws \EE_Error
1343
-     */
1344
-    public function registrations($query_params = array())
1345
-    {
1346
-        return $this->get_many_related('Registration', $query_params);
1347
-    }
1348
-
1349
-
1350
-    /**
1351
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1352
-     * into account
1353
-     *
1354
-     * @return int
1355
-     * @throws \EE_Error
1356
-     */
1357
-    public function update_tickets_sold()
1358
-    {
1359
-        $count_regs_for_this_ticket = $this->count_registrations(
1360
-            array(
1361
-                array(
1362
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1363
-                    'REG_deleted' => 0,
1364
-                ),
1365
-            )
1366
-        );
1367
-        $sold = $this->sold();
1368
-        if ($count_regs_for_this_ticket > $sold) {
1369
-            $this->increase_sold($count_regs_for_this_ticket - $sold);
1370
-            $this->save();
1371
-        } elseif ($count_regs_for_this_ticket < $sold) {
1372
-            $this->decrease_sold($count_regs_for_this_ticket - $sold);
1373
-            $this->save();
1374
-        }
1375
-        return $count_regs_for_this_ticket;
1376
-    }
1377
-
1378
-
1379
-    /**
1380
-     * Counts the registrations for this ticket
1381
-     *
1382
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1383
-     * @return int
1384
-     */
1385
-    public function count_registrations($query_params = array())
1386
-    {
1387
-        return $this->count_related('Registration', $query_params);
1388
-    }
1389
-
1390
-
1391
-    /**
1392
-     * Implementation for EEI_Has_Icon interface method.
1393
-     *
1394
-     * @see EEI_Visual_Representation for comments
1395
-     * @return string
1396
-     */
1397
-    public function get_icon()
1398
-    {
1399
-        return '<span class="dashicons dashicons-tickets-alt"></span>';
1400
-    }
1401
-
1402
-
1403
-    /**
1404
-     * Implementation of the EEI_Event_Relation interface method
1405
-     *
1406
-     * @see EEI_Event_Relation for comments
1407
-     * @return EE_Event
1408
-     * @throws \EE_Error
1409
-     * @throws UnexpectedEntityException
1410
-     */
1411
-    public function get_related_event()
1412
-    {
1413
-        // get one datetime to use for getting the event
1414
-        $datetime = $this->first_datetime();
1415
-        if (! $datetime instanceof \EE_Datetime) {
1416
-            throw new UnexpectedEntityException(
1417
-                $datetime,
1418
-                'EE_Datetime',
1419
-                sprintf(
1420
-                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1421
-                    $this->name()
1422
-                )
1423
-            );
1424
-        }
1425
-        $event = $datetime->event();
1426
-        if (! $event instanceof \EE_Event) {
1427
-            throw new UnexpectedEntityException(
1428
-                $event,
1429
-                'EE_Event',
1430
-                sprintf(
1431
-                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1432
-                    $this->name()
1433
-                )
1434
-            );
1435
-        }
1436
-        return $event;
1437
-    }
1438
-
1439
-
1440
-    /**
1441
-     * Implementation of the EEI_Event_Relation interface method
1442
-     *
1443
-     * @see EEI_Event_Relation for comments
1444
-     * @return string
1445
-     * @throws UnexpectedEntityException
1446
-     * @throws \EE_Error
1447
-     */
1448
-    public function get_event_name()
1449
-    {
1450
-        $event = $this->get_related_event();
1451
-        return $event instanceof EE_Event ? $event->name() : '';
1452
-    }
1453
-
1454
-
1455
-    /**
1456
-     * Implementation of the EEI_Event_Relation interface method
1457
-     *
1458
-     * @see EEI_Event_Relation for comments
1459
-     * @return int
1460
-     * @throws UnexpectedEntityException
1461
-     * @throws \EE_Error
1462
-     */
1463
-    public function get_event_ID()
1464
-    {
1465
-        $event = $this->get_related_event();
1466
-        return $event instanceof EE_Event ? $event->ID() : 0;
1467
-    }
1468
-
1469
-
1470
-    /**
1471
-     * This simply returns whether a ticket can be permanently deleted or not.
1472
-     * The criteria for determining this is whether the ticket has any related registrations.
1473
-     * If there are none then it can be permanently deleted.
1474
-     *
1475
-     * @return bool
1476
-     */
1477
-    public function is_permanently_deleteable()
1478
-    {
1479
-        return $this->count_registrations() === 0;
1480
-    }
15
+	/**
16
+	 * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
17
+	 */
18
+	const sold_out = 'TKS';
19
+
20
+	/**
21
+	 *
22
+	 */
23
+	const expired = 'TKE';
24
+
25
+	/**
26
+	 *
27
+	 */
28
+	const archived = 'TKA';
29
+
30
+	/**
31
+	 *
32
+	 */
33
+	const pending = 'TKP';
34
+
35
+	/**
36
+	 *
37
+	 */
38
+	const onsale = 'TKO';
39
+
40
+	/**
41
+	 * extra meta key for tracking ticket reservations
42
+	 *
43
+	 * @type string
44
+	 */
45
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
46
+
47
+	/**
48
+	 * cached result from method of the same name
49
+	 *
50
+	 * @var float $_ticket_total_with_taxes
51
+	 */
52
+	private $_ticket_total_with_taxes;
53
+
54
+
55
+	/**
56
+	 * @param array  $props_n_values          incoming values
57
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
58
+	 *                                        used.)
59
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
60
+	 *                                        date_format and the second value is the time format
61
+	 * @return EE_Ticket
62
+	 * @throws \EE_Error
63
+	 */
64
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
65
+	{
66
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
67
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
68
+	}
69
+
70
+
71
+	/**
72
+	 * @param array  $props_n_values  incoming values from the database
73
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
74
+	 *                                the website will be used.
75
+	 * @return EE_Ticket
76
+	 * @throws \EE_Error
77
+	 */
78
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
79
+	{
80
+		return new self($props_n_values, true, $timezone);
81
+	}
82
+
83
+
84
+	/**
85
+	 * @return bool
86
+	 * @throws \EE_Error
87
+	 */
88
+	public function parent()
89
+	{
90
+		return $this->get('TKT_parent');
91
+	}
92
+
93
+
94
+	/**
95
+	 * return if a ticket has quantities available for purchase
96
+	 *
97
+	 * @param  int $DTT_ID the primary key for a particular datetime
98
+	 * @return boolean
99
+	 * @throws \EE_Error
100
+	 */
101
+	public function available($DTT_ID = 0)
102
+	{
103
+		// are we checking availability for a particular datetime ?
104
+		if ($DTT_ID) {
105
+			// get that datetime object
106
+			$datetime = $this->get_first_related('Datetime', array(array('DTT_ID' => $DTT_ID)));
107
+			// if  ticket sales for this datetime have exceeded the reg limit...
108
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
109
+				return false;
110
+			}
111
+		}
112
+		// datetime is still open for registration, but is this ticket sold out ?
113
+		return $this->qty() < 1 || $this->qty() > $this->sold() ? true : false;
114
+	}
115
+
116
+
117
+	/**
118
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
119
+	 *
120
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
121
+	 *                               relevant status const
122
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
123
+	 *               further processing
124
+	 * @return mixed status int if the display string isn't requested
125
+	 * @throws \EE_Error
126
+	 */
127
+	public function ticket_status($display = false, $remaining = null)
128
+	{
129
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
130
+		if (! $remaining) {
131
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
132
+		}
133
+		if ($this->get('TKT_deleted')) {
134
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
135
+		}
136
+		if ($this->is_expired()) {
137
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
138
+		}
139
+		if ($this->is_pending()) {
140
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
141
+		}
142
+		if ($this->is_on_sale()) {
143
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
144
+		}
145
+		return '';
146
+	}
147
+
148
+
149
+	/**
150
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
151
+	 * considering ALL the factors used for figuring that out.
152
+	 *
153
+	 * @access public
154
+	 * @param  int $DTT_ID if an int above 0 is included here then we get a specific dtt.
155
+	 * @return boolean         true = tickets remaining, false not.
156
+	 * @throws \EE_Error
157
+	 */
158
+	public function is_remaining($DTT_ID = 0)
159
+	{
160
+		$num_remaining = $this->remaining($DTT_ID);
161
+		if ($num_remaining === 0) {
162
+			return false;
163
+		}
164
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
165
+			return false;
166
+		}
167
+		return true;
168
+	}
169
+
170
+
171
+	/**
172
+	 * return the total number of tickets available for purchase
173
+	 *
174
+	 * @param  int $DTT_ID the primary key for a particular datetime.
175
+	 *                     set to 0 for all related datetimes
176
+	 * @return int
177
+	 * @throws \EE_Error
178
+	 */
179
+	public function remaining($DTT_ID = 0)
180
+	{
181
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
182
+	}
183
+
184
+
185
+	/**
186
+	 * Gets min
187
+	 *
188
+	 * @return int
189
+	 * @throws \EE_Error
190
+	 */
191
+	public function min()
192
+	{
193
+		return $this->get('TKT_min');
194
+	}
195
+
196
+
197
+	/**
198
+	 * return if a ticket is no longer available cause its available dates have expired.
199
+	 *
200
+	 * @return boolean
201
+	 * @throws \EE_Error
202
+	 */
203
+	public function is_expired()
204
+	{
205
+		return ($this->get_raw('TKT_end_date') < time());
206
+	}
207
+
208
+
209
+	/**
210
+	 * Return if a ticket is yet to go on sale or not
211
+	 *
212
+	 * @return boolean
213
+	 * @throws \EE_Error
214
+	 */
215
+	public function is_pending()
216
+	{
217
+		return ($this->get_raw('TKT_start_date') > time());
218
+	}
219
+
220
+
221
+	/**
222
+	 * Return if a ticket is on sale or not
223
+	 *
224
+	 * @return boolean
225
+	 * @throws \EE_Error
226
+	 */
227
+	public function is_on_sale()
228
+	{
229
+		return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
230
+	}
231
+
232
+
233
+	/**
234
+	 * This returns the chronologically last datetime that this ticket is associated with
235
+	 *
236
+	 * @param string $dt_frmt
237
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
238
+	 *                            the end date ie: Jan 01 "to" Dec 31
239
+	 * @return string
240
+	 * @throws \EE_Error
241
+	 */
242
+	public function date_range($dt_frmt = '', $conjunction = ' - ')
243
+	{
244
+		$first_date = $this->first_datetime() instanceof EE_Datetime ? $this->first_datetime()->start_date($dt_frmt)
245
+			: '';
246
+		$last_date = $this->last_datetime() instanceof EE_Datetime ? $this->last_datetime()->end_date($dt_frmt) : '';
247
+
248
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
249
+	}
250
+
251
+
252
+	/**
253
+	 * This returns the chronologically first datetime that this ticket is associated with
254
+	 *
255
+	 * @return EE_Datetime
256
+	 * @throws \EE_Error
257
+	 */
258
+	public function first_datetime()
259
+	{
260
+		$datetimes = $this->datetimes(array('limit' => 1));
261
+		return reset($datetimes);
262
+	}
263
+
264
+
265
+	/**
266
+	 * Gets all the datetimes this ticket can be used for attending.
267
+	 * Unless otherwise specified, orders datetimes by start date.
268
+	 *
269
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
270
+	 * @return EE_Datetime[]|EE_Base_Class[]
271
+	 * @throws \EE_Error
272
+	 */
273
+	public function datetimes($query_params = array())
274
+	{
275
+		if (! isset($query_params['order_by'])) {
276
+			$query_params['order_by']['DTT_order'] = 'ASC';
277
+		}
278
+		return $this->get_many_related('Datetime', $query_params);
279
+	}
280
+
281
+
282
+	/**
283
+	 * This returns the chronologically last datetime that this ticket is associated with
284
+	 *
285
+	 * @return EE_Datetime
286
+	 * @throws \EE_Error
287
+	 */
288
+	public function last_datetime()
289
+	{
290
+		$datetimes = $this->datetimes(array('limit' => 1, 'order_by' => array('DTT_EVT_start' => 'DESC')));
291
+		return end($datetimes);
292
+	}
293
+
294
+
295
+	/**
296
+	 * This returns the total tickets sold depending on the given parameters.
297
+	 *
298
+	 * @param  string $what   Can be one of two options: 'ticket', 'datetime'.
299
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
300
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
301
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
302
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
303
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
304
+	 * @param  int    $dtt_id [optional] include the dtt_id with $what = 'datetime'.
305
+	 * @return mixed (array|int)          how many tickets have sold
306
+	 * @throws \EE_Error
307
+	 */
308
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
309
+	{
310
+		$total = 0;
311
+		$tickets_sold = $this->_all_tickets_sold();
312
+		switch ($what) {
313
+			case 'ticket':
314
+				return $tickets_sold['ticket'];
315
+				break;
316
+			case 'datetime':
317
+				if (empty($tickets_sold['datetime'])) {
318
+					return $total;
319
+				}
320
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
321
+					EE_Error::add_error(
322
+						__(
323
+							'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
324
+							'event_espresso'
325
+						),
326
+						__FILE__,
327
+						__FUNCTION__,
328
+						__LINE__
329
+					);
330
+					return $total;
331
+				}
332
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
333
+				break;
334
+			default:
335
+				return $total;
336
+		}
337
+	}
338
+
339
+
340
+	/**
341
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
342
+	 *
343
+	 * @return EE_Ticket[]
344
+	 * @throws \EE_Error
345
+	 */
346
+	protected function _all_tickets_sold()
347
+	{
348
+		$datetimes = $this->get_many_related('Datetime');
349
+		$tickets_sold = array();
350
+		if (! empty($datetimes)) {
351
+			foreach ($datetimes as $datetime) {
352
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
353
+			}
354
+		}
355
+		// Tickets sold
356
+		$tickets_sold['ticket'] = $this->sold();
357
+		return $tickets_sold;
358
+	}
359
+
360
+
361
+	/**
362
+	 * This returns the base price object for the ticket.
363
+	 *
364
+	 * @param  bool $return_array whether to return as an array indexed by price id or just the object.
365
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
366
+	 * @throws \EE_Error
367
+	 */
368
+	public function base_price($return_array = false)
369
+	{
370
+		$_where = array('Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price);
371
+		return $return_array
372
+			? $this->get_many_related('Price', array($_where))
373
+			: $this->get_first_related('Price', array($_where));
374
+	}
375
+
376
+
377
+	/**
378
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
379
+	 *
380
+	 * @access public
381
+	 * @return EE_Price[]
382
+	 * @throws \EE_Error
383
+	 */
384
+	public function price_modifiers()
385
+	{
386
+		$query_params = array(
387
+			0 => array(
388
+				'Price_Type.PBT_ID' => array(
389
+					'NOT IN',
390
+					array(EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax),
391
+				),
392
+			),
393
+		);
394
+		return $this->prices($query_params);
395
+	}
396
+
397
+
398
+	/**
399
+	 * Gets all the prices that combine to form the final price of this ticket
400
+	 *
401
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
402
+	 * @return EE_Price[]|EE_Base_Class[]
403
+	 * @throws \EE_Error
404
+	 */
405
+	public function prices($query_params = array())
406
+	{
407
+		return $this->get_many_related('Price', $query_params);
408
+	}
409
+
410
+
411
+	/**
412
+	 * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
413
+	 *
414
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
415
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
416
+	 * @throws \EE_Error
417
+	 */
418
+	public function datetime_tickets($query_params = array())
419
+	{
420
+		return $this->get_many_related('Datetime_Ticket', $query_params);
421
+	}
422
+
423
+
424
+	/**
425
+	 * Gets all the datetimes from the db ordered by DTT_order
426
+	 *
427
+	 * @param boolean $show_expired
428
+	 * @param boolean $show_deleted
429
+	 * @return EE_Datetime[]
430
+	 * @throws \EE_Error
431
+	 */
432
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
433
+	{
434
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
435
+			$this->ID(),
436
+			$show_expired,
437
+			$show_deleted
438
+		);
439
+	}
440
+
441
+
442
+	/**
443
+	 * Gets ID
444
+	 *
445
+	 * @return string
446
+	 * @throws \EE_Error
447
+	 */
448
+	public function ID()
449
+	{
450
+		return $this->get('TKT_ID');
451
+	}
452
+
453
+
454
+	/**
455
+	 * get the author of the ticket.
456
+	 *
457
+	 * @since 4.5.0
458
+	 * @return int
459
+	 * @throws \EE_Error
460
+	 */
461
+	public function wp_user()
462
+	{
463
+		return $this->get('TKT_wp_user');
464
+	}
465
+
466
+
467
+	/**
468
+	 * Gets the template for the ticket
469
+	 *
470
+	 * @return EE_Ticket_Template|EE_Base_Class
471
+	 * @throws \EE_Error
472
+	 */
473
+	public function template()
474
+	{
475
+		return $this->get_first_related('Ticket_Template');
476
+	}
477
+
478
+
479
+	/**
480
+	 * Simply returns an array of EE_Price objects that are taxes.
481
+	 *
482
+	 * @return EE_Price[]
483
+	 * @throws \EE_Error
484
+	 */
485
+	public function get_ticket_taxes_for_admin()
486
+	{
487
+		return EE_Taxes::get_taxes_for_admin();
488
+	}
489
+
490
+
491
+	/**
492
+	 * @return float
493
+	 * @throws \EE_Error
494
+	 */
495
+	public function ticket_price()
496
+	{
497
+		return $this->get('TKT_price');
498
+	}
499
+
500
+
501
+	/**
502
+	 * @return mixed
503
+	 * @throws \EE_Error
504
+	 */
505
+	public function pretty_price()
506
+	{
507
+		return $this->get_pretty('TKT_price');
508
+	}
509
+
510
+
511
+	/**
512
+	 * @return bool
513
+	 * @throws \EE_Error
514
+	 */
515
+	public function is_free()
516
+	{
517
+		return $this->get_ticket_total_with_taxes() === (float) 0;
518
+	}
519
+
520
+
521
+	/**
522
+	 * get_ticket_total_with_taxes
523
+	 *
524
+	 * @param bool $no_cache
525
+	 * @return float
526
+	 * @throws \EE_Error
527
+	 */
528
+	public function get_ticket_total_with_taxes($no_cache = false)
529
+	{
530
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
531
+			$this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
532
+		}
533
+		return (float) $this->_ticket_total_with_taxes;
534
+	}
535
+
536
+
537
+	public function ensure_TKT_Price_correct()
538
+	{
539
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
540
+		$this->save();
541
+	}
542
+
543
+
544
+	/**
545
+	 * @return float
546
+	 * @throws \EE_Error
547
+	 */
548
+	public function get_ticket_subtotal()
549
+	{
550
+		return EE_Taxes::get_subtotal_for_admin($this);
551
+	}
552
+
553
+
554
+	/**
555
+	 * Returns the total taxes applied to this ticket
556
+	 *
557
+	 * @return float
558
+	 * @throws \EE_Error
559
+	 */
560
+	public function get_ticket_taxes_total_for_admin()
561
+	{
562
+		return EE_Taxes::get_total_taxes_for_admin($this);
563
+	}
564
+
565
+
566
+	/**
567
+	 * Sets name
568
+	 *
569
+	 * @param string $name
570
+	 * @throws \EE_Error
571
+	 */
572
+	public function set_name($name)
573
+	{
574
+		$this->set('TKT_name', $name);
575
+	}
576
+
577
+
578
+	/**
579
+	 * Gets description
580
+	 *
581
+	 * @return string
582
+	 * @throws \EE_Error
583
+	 */
584
+	public function description()
585
+	{
586
+		return $this->get('TKT_description');
587
+	}
588
+
589
+
590
+	/**
591
+	 * Sets description
592
+	 *
593
+	 * @param string $description
594
+	 * @throws \EE_Error
595
+	 */
596
+	public function set_description($description)
597
+	{
598
+		$this->set('TKT_description', $description);
599
+	}
600
+
601
+
602
+	/**
603
+	 * Gets start_date
604
+	 *
605
+	 * @param string $dt_frmt
606
+	 * @param string $tm_frmt
607
+	 * @return string
608
+	 * @throws \EE_Error
609
+	 */
610
+	public function start_date($dt_frmt = '', $tm_frmt = '')
611
+	{
612
+		return $this->_get_datetime('TKT_start_date', $dt_frmt, $tm_frmt);
613
+	}
614
+
615
+
616
+	/**
617
+	 * Sets start_date
618
+	 *
619
+	 * @param string $start_date
620
+	 * @return void
621
+	 * @throws \EE_Error
622
+	 */
623
+	public function set_start_date($start_date)
624
+	{
625
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
626
+	}
627
+
628
+
629
+	/**
630
+	 * Gets end_date
631
+	 *
632
+	 * @param string $dt_frmt
633
+	 * @param string $tm_frmt
634
+	 * @return string
635
+	 * @throws \EE_Error
636
+	 */
637
+	public function end_date($dt_frmt = '', $tm_frmt = '')
638
+	{
639
+		return $this->_get_datetime('TKT_end_date', $dt_frmt, $tm_frmt);
640
+	}
641
+
642
+
643
+	/**
644
+	 * Sets end_date
645
+	 *
646
+	 * @param string $end_date
647
+	 * @return void
648
+	 * @throws \EE_Error
649
+	 */
650
+	public function set_end_date($end_date)
651
+	{
652
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
653
+	}
654
+
655
+
656
+	/**
657
+	 * Sets sell until time
658
+	 *
659
+	 * @since 4.5.0
660
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
661
+	 * @throws \EE_Error
662
+	 */
663
+	public function set_end_time($time)
664
+	{
665
+		$this->_set_time_for($time, 'TKT_end_date');
666
+	}
667
+
668
+
669
+	/**
670
+	 * Sets min
671
+	 *
672
+	 * @param int $min
673
+	 * @return void
674
+	 * @throws \EE_Error
675
+	 */
676
+	public function set_min($min)
677
+	{
678
+		$this->set('TKT_min', $min);
679
+	}
680
+
681
+
682
+	/**
683
+	 * Gets max
684
+	 *
685
+	 * @return int
686
+	 * @throws \EE_Error
687
+	 */
688
+	public function max()
689
+	{
690
+		return $this->get('TKT_max');
691
+	}
692
+
693
+
694
+	/**
695
+	 * Sets max
696
+	 *
697
+	 * @param int $max
698
+	 * @return void
699
+	 * @throws \EE_Error
700
+	 */
701
+	public function set_max($max)
702
+	{
703
+		$this->set('TKT_max', $max);
704
+	}
705
+
706
+
707
+	/**
708
+	 * Sets price
709
+	 *
710
+	 * @param float $price
711
+	 * @return void
712
+	 * @throws \EE_Error
713
+	 */
714
+	public function set_price($price)
715
+	{
716
+		$this->set('TKT_price', $price);
717
+	}
718
+
719
+
720
+	/**
721
+	 * Gets sold
722
+	 *
723
+	 * @return int
724
+	 * @throws \EE_Error
725
+	 */
726
+	public function sold()
727
+	{
728
+		return $this->get_raw('TKT_sold');
729
+	}
730
+
731
+
732
+	/**
733
+	 * Sets sold
734
+	 *
735
+	 * @param int $sold
736
+	 * @return void
737
+	 * @throws \EE_Error
738
+	 */
739
+	public function set_sold($sold)
740
+	{
741
+		// sold can not go below zero
742
+		$sold = max(0, $sold);
743
+		$this->set('TKT_sold', $sold);
744
+	}
745
+
746
+
747
+	/**
748
+	 * increments sold by amount passed by $qty
749
+	 *
750
+	 * @param int $qty
751
+	 * @return void
752
+	 * @throws \EE_Error
753
+	 */
754
+	public function increase_sold($qty = 1)
755
+	{
756
+		$sold = $this->sold() + $qty;
757
+		// remove ticket reservation, but don't adjust datetime reservations,  because that will happen
758
+		// via \EE_Datetime::increase_sold() when \EE_Ticket::_increase_sold_for_datetimes() is called
759
+		$this->decrease_reserved($qty, false, "TKT: {$this->ID()} (ln:" . __LINE__ . ')');
760
+		$this->_increase_sold_for_datetimes($qty);
761
+		$this->set_sold($sold);
762
+		do_action(
763
+			'AHEE__EE_Ticket__increase_sold',
764
+			$this,
765
+			$qty,
766
+			$sold
767
+		);
768
+	}
769
+
770
+
771
+	/**
772
+	 * Increases sold on related datetimes
773
+	 *
774
+	 * @param int $qty
775
+	 * @return void
776
+	 * @throws \EE_Error
777
+	 */
778
+	protected function _increase_sold_for_datetimes($qty = 1)
779
+	{
780
+		$datetimes = $this->datetimes();
781
+		if (is_array($datetimes)) {
782
+			foreach ($datetimes as $datetime) {
783
+				if ($datetime instanceof EE_Datetime) {
784
+					$datetime->increase_sold($qty);
785
+					$datetime->save();
786
+				}
787
+			}
788
+		}
789
+	}
790
+
791
+
792
+	/**
793
+	 * decrements (subtracts) sold by amount passed by $qty
794
+	 *
795
+	 * @param int $qty
796
+	 * @return void
797
+	 * @throws \EE_Error
798
+	 */
799
+	public function decrease_sold($qty = 1)
800
+	{
801
+		$sold = $this->sold() - $qty;
802
+		$this->_decrease_sold_for_datetimes($qty);
803
+		$this->set_sold($sold);
804
+		do_action(
805
+			'AHEE__EE_Ticket__decrease_sold',
806
+			$this,
807
+			$qty,
808
+			$sold
809
+		);
810
+	}
811
+
812
+
813
+	/**
814
+	 * Decreases sold on related datetimes
815
+	 *
816
+	 * @param int $qty
817
+	 * @return void
818
+	 * @throws \EE_Error
819
+	 */
820
+	protected function _decrease_sold_for_datetimes($qty = 1)
821
+	{
822
+		$datetimes = $this->datetimes();
823
+		if (is_array($datetimes)) {
824
+			foreach ($datetimes as $datetime) {
825
+				if ($datetime instanceof EE_Datetime) {
826
+					$datetime->decrease_sold($qty);
827
+					$datetime->save();
828
+				}
829
+			}
830
+		}
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets qty of reserved tickets
836
+	 *
837
+	 * @return int
838
+	 * @throws \EE_Error
839
+	 */
840
+	public function reserved()
841
+	{
842
+		return $this->get_raw('TKT_reserved');
843
+	}
844
+
845
+
846
+	/**
847
+	 * Sets reserved
848
+	 *
849
+	 * @param int $reserved
850
+	 * @return void
851
+	 * @throws \EE_Error
852
+	 */
853
+	public function set_reserved($reserved)
854
+	{
855
+		// reserved can not go below zero
856
+		$reserved = max(0, (int) $reserved);
857
+		$this->set('TKT_reserved', $reserved);
858
+	}
859
+
860
+
861
+	/**
862
+	 * increments reserved by amount passed by $qty
863
+	 *
864
+	 * @param int    $qty
865
+	 * @param string $source
866
+	 * @return void
867
+	 * @throws EE_Error
868
+	 * @throws InvalidArgumentException
869
+	 * @throws ReflectionException
870
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
871
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
872
+	 */
873
+	public function increase_reserved($qty = 1, $source = 'unknown')
874
+	{
875
+		$qty = absint($qty);
876
+		$reserved = $this->reserved() + $qty;
877
+		do_action(
878
+			'AHEE__EE_Ticket__increase_reserved__begin',
879
+			$this,
880
+			$qty,
881
+			$reserved
882
+		);
883
+		if ($reserved
884
+			&& $this->add_extra_meta(
885
+				EE_Ticket::META_KEY_TICKET_RESERVATIONS,
886
+				"{$qty} from {$source}"
887
+			)
888
+		) {
889
+			$this->_increase_reserved_for_datetimes($qty);
890
+			$this->set_reserved($reserved);
891
+			do_action(
892
+				'AHEE__EE_Ticket__increase_reserved',
893
+				$this,
894
+				$qty,
895
+				$reserved
896
+			);
897
+		}
898
+	}
899
+
900
+
901
+	/**
902
+	 * Increases sold on related datetimes
903
+	 *
904
+	 * @param int $qty
905
+	 * @return void
906
+	 * @throws \EE_Error
907
+	 */
908
+	protected function _increase_reserved_for_datetimes($qty = 1)
909
+	{
910
+		$datetimes = $this->datetimes();
911
+		if (is_array($datetimes)) {
912
+			foreach ($datetimes as $datetime) {
913
+				if ($datetime instanceof EE_Datetime) {
914
+					$datetime->increase_reserved($qty);
915
+					$datetime->save();
916
+				}
917
+			}
918
+		}
919
+	}
920
+
921
+
922
+	/**
923
+	 * decrements (subtracts) reserved by amount passed by $qty
924
+	 *
925
+	 * @param int    $qty
926
+	 * @param bool   $adjust_datetimes
927
+	 * @param string $source
928
+	 * @return void
929
+	 * @throws EE_Error
930
+	 * @throws InvalidArgumentException
931
+	 * @throws ReflectionException
932
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
933
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
934
+	 */
935
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
936
+	{
937
+		$reserved = $this->reserved() - absint($qty);
938
+		if ($this->add_extra_meta(
939
+			EE_Ticket::META_KEY_TICKET_RESERVATIONS,
940
+			"-{$qty} from {$source}"
941
+		)) {
942
+			if ($adjust_datetimes) {
943
+				$this->_decrease_reserved_for_datetimes($qty);
944
+			}
945
+			$this->set_reserved($reserved);
946
+			do_action(
947
+				'AHEE__EE_Ticket__decrease_reserved',
948
+				$this,
949
+				$qty,
950
+				$reserved
951
+			);
952
+		}
953
+	}
954
+
955
+
956
+	/**
957
+	 * Increases sold on related datetimes
958
+	 *
959
+	 * @param int $qty
960
+	 * @return void
961
+	 * @throws \EE_Error
962
+	 */
963
+	protected function _decrease_reserved_for_datetimes($qty = 1)
964
+	{
965
+		$datetimes = $this->datetimes();
966
+		if (is_array($datetimes)) {
967
+			foreach ($datetimes as $datetime) {
968
+				if ($datetime instanceof EE_Datetime) {
969
+					$datetime->decrease_reserved($qty);
970
+					$datetime->save();
971
+				}
972
+			}
973
+		}
974
+	}
975
+
976
+
977
+	/**
978
+	 * Gets ticket quantity
979
+	 *
980
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
981
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
982
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
983
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
984
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
985
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
986
+	 * @return int
987
+	 * @throws \EE_Error
988
+	 */
989
+	public function qty($context = '')
990
+	{
991
+		switch ($context) {
992
+			case 'reg_limit':
993
+				return $this->real_quantity_on_ticket();
994
+			case 'saleable':
995
+				return $this->real_quantity_on_ticket('saleable');
996
+			default:
997
+				return $this->get_raw('TKT_qty');
998
+		}
999
+	}
1000
+
1001
+
1002
+	/**
1003
+	 * Gets ticket quantity
1004
+	 *
1005
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1006
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1007
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1008
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1009
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1010
+	 * @param  int   $DTT_ID      the primary key for a particular datetime.
1011
+	 *                            set to 0 for all related datetimes
1012
+	 * @return int
1013
+	 * @throws \EE_Error
1014
+	 */
1015
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1016
+	{
1017
+		$raw = $this->get_raw('TKT_qty');
1018
+		// return immediately if it's zero
1019
+		if ($raw === 0) {
1020
+			return $raw;
1021
+		}
1022
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1023
+		// ensure qty doesn't exceed raw value for THIS ticket
1024
+		$qty = min(EE_INF, $raw);
1025
+		// echo "\n . qty: " . $qty . '<br />';
1026
+		// calculate this ticket's total sales and reservations
1027
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1028
+		// echo "\n . sold: " . $this->sold() . '<br />';
1029
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1030
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1031
+		// first we need to calculate the maximum number of tickets available for the datetime
1032
+		// do we want data for one datetime or all of them ?
1033
+		$query_params = $DTT_ID ? array(array('DTT_ID' => $DTT_ID)) : array();
1034
+		$datetimes = $this->datetimes($query_params);
1035
+		if (is_array($datetimes) && ! empty($datetimes)) {
1036
+			foreach ($datetimes as $datetime) {
1037
+				if ($datetime instanceof EE_Datetime) {
1038
+					$datetime->refresh_from_db();
1039
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1040
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1041
+					// initialize with no restrictions for each datetime
1042
+					// but adjust datetime qty based on datetime reg limit
1043
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1044
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1045
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1046
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1047
+					// and reservations for this datetime, that do NOT include sales and reservations
1048
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1049
+					if ($context === 'saleable') {
1050
+						$datetime_qty = max(
1051
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1052
+							0
1053
+						);
1054
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1055
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1056
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1057
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1058
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1059
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1060
+					}
1061
+					$qty = min($datetime_qty, $qty);
1062
+					// echo "\n . . qty: " . $qty . '<br />';
1063
+				}
1064
+			}
1065
+		}
1066
+		// NOW that we know the  maximum number of tickets available for the datetime
1067
+		// we can finally factor in the details for this specific ticket
1068
+		if ($qty > 0 && $context === 'saleable') {
1069
+			// and subtract the sales for THIS ticket
1070
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1071
+			// echo "\n . qty: " . $qty . '<br />';
1072
+		}
1073
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1074
+		return $qty;
1075
+	}
1076
+
1077
+
1078
+	/**
1079
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1080
+	 *
1081
+	 * @param int $qty
1082
+	 * @return void
1083
+	 * @throws \EE_Error
1084
+	 */
1085
+	public function set_qty($qty)
1086
+	{
1087
+		$datetimes = $this->datetimes();
1088
+		foreach ($datetimes as $datetime) {
1089
+			if ($datetime instanceof EE_Datetime) {
1090
+				$qty = min($qty, $datetime->reg_limit());
1091
+			}
1092
+		}
1093
+		$this->set('TKT_qty', $qty);
1094
+	}
1095
+
1096
+
1097
+	/**
1098
+	 * Gets uses
1099
+	 *
1100
+	 * @return int
1101
+	 * @throws \EE_Error
1102
+	 */
1103
+	public function uses()
1104
+	{
1105
+		return $this->get('TKT_uses');
1106
+	}
1107
+
1108
+
1109
+	/**
1110
+	 * Sets uses
1111
+	 *
1112
+	 * @param int $uses
1113
+	 * @return void
1114
+	 * @throws \EE_Error
1115
+	 */
1116
+	public function set_uses($uses)
1117
+	{
1118
+		$this->set('TKT_uses', $uses);
1119
+	}
1120
+
1121
+
1122
+	/**
1123
+	 * returns whether ticket is required or not.
1124
+	 *
1125
+	 * @return boolean
1126
+	 * @throws \EE_Error
1127
+	 */
1128
+	public function required()
1129
+	{
1130
+		return $this->get('TKT_required');
1131
+	}
1132
+
1133
+
1134
+	/**
1135
+	 * sets the TKT_required property
1136
+	 *
1137
+	 * @param boolean $required
1138
+	 * @return void
1139
+	 * @throws \EE_Error
1140
+	 */
1141
+	public function set_required($required)
1142
+	{
1143
+		$this->set('TKT_required', $required);
1144
+	}
1145
+
1146
+
1147
+	/**
1148
+	 * Gets taxable
1149
+	 *
1150
+	 * @return boolean
1151
+	 * @throws \EE_Error
1152
+	 */
1153
+	public function taxable()
1154
+	{
1155
+		return $this->get('TKT_taxable');
1156
+	}
1157
+
1158
+
1159
+	/**
1160
+	 * Sets taxable
1161
+	 *
1162
+	 * @param boolean $taxable
1163
+	 * @return void
1164
+	 * @throws \EE_Error
1165
+	 */
1166
+	public function set_taxable($taxable)
1167
+	{
1168
+		$this->set('TKT_taxable', $taxable);
1169
+	}
1170
+
1171
+
1172
+	/**
1173
+	 * Gets is_default
1174
+	 *
1175
+	 * @return boolean
1176
+	 * @throws \EE_Error
1177
+	 */
1178
+	public function is_default()
1179
+	{
1180
+		return $this->get('TKT_is_default');
1181
+	}
1182
+
1183
+
1184
+	/**
1185
+	 * Sets is_default
1186
+	 *
1187
+	 * @param boolean $is_default
1188
+	 * @return void
1189
+	 * @throws \EE_Error
1190
+	 */
1191
+	public function set_is_default($is_default)
1192
+	{
1193
+		$this->set('TKT_is_default', $is_default);
1194
+	}
1195
+
1196
+
1197
+	/**
1198
+	 * Gets order
1199
+	 *
1200
+	 * @return int
1201
+	 * @throws \EE_Error
1202
+	 */
1203
+	public function order()
1204
+	{
1205
+		return $this->get('TKT_order');
1206
+	}
1207
+
1208
+
1209
+	/**
1210
+	 * Sets order
1211
+	 *
1212
+	 * @param int $order
1213
+	 * @return void
1214
+	 * @throws \EE_Error
1215
+	 */
1216
+	public function set_order($order)
1217
+	{
1218
+		$this->set('TKT_order', $order);
1219
+	}
1220
+
1221
+
1222
+	/**
1223
+	 * Gets row
1224
+	 *
1225
+	 * @return int
1226
+	 * @throws \EE_Error
1227
+	 */
1228
+	public function row()
1229
+	{
1230
+		return $this->get('TKT_row');
1231
+	}
1232
+
1233
+
1234
+	/**
1235
+	 * Sets row
1236
+	 *
1237
+	 * @param int $row
1238
+	 * @return void
1239
+	 * @throws \EE_Error
1240
+	 */
1241
+	public function set_row($row)
1242
+	{
1243
+		$this->set('TKT_row', $row);
1244
+	}
1245
+
1246
+
1247
+	/**
1248
+	 * Gets deleted
1249
+	 *
1250
+	 * @return boolean
1251
+	 * @throws \EE_Error
1252
+	 */
1253
+	public function deleted()
1254
+	{
1255
+		return $this->get('TKT_deleted');
1256
+	}
1257
+
1258
+
1259
+	/**
1260
+	 * Sets deleted
1261
+	 *
1262
+	 * @param boolean $deleted
1263
+	 * @return void
1264
+	 * @throws \EE_Error
1265
+	 */
1266
+	public function set_deleted($deleted)
1267
+	{
1268
+		$this->set('TKT_deleted', $deleted);
1269
+	}
1270
+
1271
+
1272
+	/**
1273
+	 * Gets parent
1274
+	 *
1275
+	 * @return int
1276
+	 * @throws \EE_Error
1277
+	 */
1278
+	public function parent_ID()
1279
+	{
1280
+		return $this->get('TKT_parent');
1281
+	}
1282
+
1283
+
1284
+	/**
1285
+	 * Sets parent
1286
+	 *
1287
+	 * @param int $parent
1288
+	 * @return void
1289
+	 * @throws \EE_Error
1290
+	 */
1291
+	public function set_parent_ID($parent)
1292
+	{
1293
+		$this->set('TKT_parent', $parent);
1294
+	}
1295
+
1296
+
1297
+	/**
1298
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1299
+	 *
1300
+	 * @return string
1301
+	 * @throws \EE_Error
1302
+	 */
1303
+	public function name_and_info()
1304
+	{
1305
+		$times = array();
1306
+		foreach ($this->datetimes() as $datetime) {
1307
+			$times[] = $datetime->start_date_and_time();
1308
+		}
1309
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1310
+	}
1311
+
1312
+
1313
+	/**
1314
+	 * Gets name
1315
+	 *
1316
+	 * @return string
1317
+	 * @throws \EE_Error
1318
+	 */
1319
+	public function name()
1320
+	{
1321
+		return $this->get('TKT_name');
1322
+	}
1323
+
1324
+
1325
+	/**
1326
+	 * Gets price
1327
+	 *
1328
+	 * @return float
1329
+	 * @throws \EE_Error
1330
+	 */
1331
+	public function price()
1332
+	{
1333
+		return $this->get('TKT_price');
1334
+	}
1335
+
1336
+
1337
+	/**
1338
+	 * Gets all the registrations for this ticket
1339
+	 *
1340
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1341
+	 * @return EE_Registration[]|EE_Base_Class[]
1342
+	 * @throws \EE_Error
1343
+	 */
1344
+	public function registrations($query_params = array())
1345
+	{
1346
+		return $this->get_many_related('Registration', $query_params);
1347
+	}
1348
+
1349
+
1350
+	/**
1351
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1352
+	 * into account
1353
+	 *
1354
+	 * @return int
1355
+	 * @throws \EE_Error
1356
+	 */
1357
+	public function update_tickets_sold()
1358
+	{
1359
+		$count_regs_for_this_ticket = $this->count_registrations(
1360
+			array(
1361
+				array(
1362
+					'STS_ID'      => EEM_Registration::status_id_approved,
1363
+					'REG_deleted' => 0,
1364
+				),
1365
+			)
1366
+		);
1367
+		$sold = $this->sold();
1368
+		if ($count_regs_for_this_ticket > $sold) {
1369
+			$this->increase_sold($count_regs_for_this_ticket - $sold);
1370
+			$this->save();
1371
+		} elseif ($count_regs_for_this_ticket < $sold) {
1372
+			$this->decrease_sold($count_regs_for_this_ticket - $sold);
1373
+			$this->save();
1374
+		}
1375
+		return $count_regs_for_this_ticket;
1376
+	}
1377
+
1378
+
1379
+	/**
1380
+	 * Counts the registrations for this ticket
1381
+	 *
1382
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1383
+	 * @return int
1384
+	 */
1385
+	public function count_registrations($query_params = array())
1386
+	{
1387
+		return $this->count_related('Registration', $query_params);
1388
+	}
1389
+
1390
+
1391
+	/**
1392
+	 * Implementation for EEI_Has_Icon interface method.
1393
+	 *
1394
+	 * @see EEI_Visual_Representation for comments
1395
+	 * @return string
1396
+	 */
1397
+	public function get_icon()
1398
+	{
1399
+		return '<span class="dashicons dashicons-tickets-alt"></span>';
1400
+	}
1401
+
1402
+
1403
+	/**
1404
+	 * Implementation of the EEI_Event_Relation interface method
1405
+	 *
1406
+	 * @see EEI_Event_Relation for comments
1407
+	 * @return EE_Event
1408
+	 * @throws \EE_Error
1409
+	 * @throws UnexpectedEntityException
1410
+	 */
1411
+	public function get_related_event()
1412
+	{
1413
+		// get one datetime to use for getting the event
1414
+		$datetime = $this->first_datetime();
1415
+		if (! $datetime instanceof \EE_Datetime) {
1416
+			throw new UnexpectedEntityException(
1417
+				$datetime,
1418
+				'EE_Datetime',
1419
+				sprintf(
1420
+					__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1421
+					$this->name()
1422
+				)
1423
+			);
1424
+		}
1425
+		$event = $datetime->event();
1426
+		if (! $event instanceof \EE_Event) {
1427
+			throw new UnexpectedEntityException(
1428
+				$event,
1429
+				'EE_Event',
1430
+				sprintf(
1431
+					__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1432
+					$this->name()
1433
+				)
1434
+			);
1435
+		}
1436
+		return $event;
1437
+	}
1438
+
1439
+
1440
+	/**
1441
+	 * Implementation of the EEI_Event_Relation interface method
1442
+	 *
1443
+	 * @see EEI_Event_Relation for comments
1444
+	 * @return string
1445
+	 * @throws UnexpectedEntityException
1446
+	 * @throws \EE_Error
1447
+	 */
1448
+	public function get_event_name()
1449
+	{
1450
+		$event = $this->get_related_event();
1451
+		return $event instanceof EE_Event ? $event->name() : '';
1452
+	}
1453
+
1454
+
1455
+	/**
1456
+	 * Implementation of the EEI_Event_Relation interface method
1457
+	 *
1458
+	 * @see EEI_Event_Relation for comments
1459
+	 * @return int
1460
+	 * @throws UnexpectedEntityException
1461
+	 * @throws \EE_Error
1462
+	 */
1463
+	public function get_event_ID()
1464
+	{
1465
+		$event = $this->get_related_event();
1466
+		return $event instanceof EE_Event ? $event->ID() : 0;
1467
+	}
1468
+
1469
+
1470
+	/**
1471
+	 * This simply returns whether a ticket can be permanently deleted or not.
1472
+	 * The criteria for determining this is whether the ticket has any related registrations.
1473
+	 * If there are none then it can be permanently deleted.
1474
+	 *
1475
+	 * @return bool
1476
+	 */
1477
+	public function is_permanently_deleteable()
1478
+	{
1479
+		return $this->count_registrations() === 0;
1480
+	}
1481 1481
 }
Please login to merge, or discard this patch.