Completed
Branch FET/conditional-update-queries (932a14)
by
unknown
26:33 queued 17:46
created

EE_Ticket::uses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\exceptions\InvalidDataTypeException;
4
use EventEspresso\core\exceptions\InvalidInterfaceException;
5
use EventEspresso\core\exceptions\UnexpectedEntityException;
6
7
/**
8
 * EE_Ticket class
9
 *
10
 * @package            Event Espresso
11
 * @subpackage         includes/classes/EE_Ticket.class.php
12
 * @author             Darren Ethier
13
 */
14
class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
15
{
16
17
    /**
18
     * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
19
     */
20
    const sold_out = 'TKS';
21
22
    /**
23
     *
24
     */
25
    const expired = 'TKE';
26
27
    /**
28
     *
29
     */
30
    const archived = 'TKA';
31
32
    /**
33
     *
34
     */
35
    const pending = 'TKP';
36
37
    /**
38
     *
39
     */
40
    const onsale = 'TKO';
41
42
    /**
43
     * extra meta key for tracking ticket reservations
44
     *
45
     * @type string
46
     */
47
    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
48
49
    /**
50
     * cached result from method of the same name
51
     *
52
     * @var float $_ticket_total_with_taxes
53
     */
54
    private $_ticket_total_with_taxes;
55
56
57
    /**
58
     * @param array  $props_n_values          incoming values
59
     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
60
     *                                        used.)
61
     * @param array  $date_formats            incoming date_formats in an array where the first value is the
62
     *                                        date_format and the second value is the time format
63
     * @return EE_Ticket
64
     * @throws \EE_Error
65
     */
66
    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
67
    {
68
        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
69
        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
70
    }
71
72
73
    /**
74
     * @param array  $props_n_values  incoming values from the database
75
     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
76
     *                                the website will be used.
77
     * @return EE_Ticket
78
     * @throws \EE_Error
79
     */
80
    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
81
    {
82
        return new self($props_n_values, true, $timezone);
83
    }
84
85
86
    /**
87
     * @return bool
88
     * @throws \EE_Error
89
     */
90
    public function parent()
91
    {
92
        return $this->get('TKT_parent');
93
    }
94
95
96
    /**
97
     * return if a ticket has quantities available for purchase
98
     *
99
     * @param  int $DTT_ID the primary key for a particular datetime
100
     * @return boolean
101
     * @throws \EE_Error
102
     */
103
    public function available($DTT_ID = 0)
104
    {
105
        // are we checking availability for a particular datetime ?
106
        if ($DTT_ID) {
107
            // get that datetime object
108
            $datetime = $this->get_first_related('Datetime', array(array('DTT_ID' => $DTT_ID)));
109
            // if  ticket sales for this datetime have exceeded the reg limit...
110
            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
111
                return false;
112
            }
113
        }
114
        // datetime is still open for registration, but is this ticket sold out ?
115
        return $this->qty() < 1 || $this->qty() > $this->sold() ? true : false;
116
    }
117
118
119
    /**
120
     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
121
     *
122
     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
123
     *                               relevant status const
124
     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
125
     *               further processing
126
     * @return mixed status int if the display string isn't requested
127
     * @throws \EE_Error
128
     */
129
    public function ticket_status($display = false, $remaining = null)
130
    {
131
        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
132
        if (! $remaining) {
133
            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
134
        }
135
        if ($this->get('TKT_deleted')) {
136
            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
137
        }
138
        if ($this->is_expired()) {
139
            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
140
        }
141
        if ($this->is_pending()) {
142
            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
143
        }
144
        if ($this->is_on_sale()) {
145
            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
146
        }
147
        return '';
148
    }
149
150
151
    /**
152
     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
153
     * considering ALL the factors used for figuring that out.
154
     *
155
     * @access public
156
     * @param  int $DTT_ID if an int above 0 is included here then we get a specific dtt.
157
     * @return boolean         true = tickets remaining, false not.
158
     * @throws \EE_Error
159
     */
160
    public function is_remaining($DTT_ID = 0)
161
    {
162
        $num_remaining = $this->remaining($DTT_ID);
163
        if ($num_remaining === 0) {
164
            return false;
165
        }
166
        if ($num_remaining > 0 && $num_remaining < $this->min()) {
167
            return false;
168
        }
169
        return true;
170
    }
171
172
173
    /**
174
     * return the total number of tickets available for purchase
175
     *
176
     * @param  int $DTT_ID the primary key for a particular datetime.
177
     *                     set to 0 for all related datetimes
178
     * @return int
179
     * @throws \EE_Error
180
     */
181
    public function remaining($DTT_ID = 0)
182
    {
183
        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
184
    }
185
186
187
    /**
188
     * Gets min
189
     *
190
     * @return int
191
     * @throws \EE_Error
192
     */
193
    public function min()
194
    {
195
        return $this->get('TKT_min');
196
    }
197
198
199
    /**
200
     * return if a ticket is no longer available cause its available dates have expired.
201
     *
202
     * @return boolean
203
     * @throws \EE_Error
204
     */
205
    public function is_expired()
206
    {
207
        return ($this->get_raw('TKT_end_date') < time());
208
    }
209
210
211
    /**
212
     * Return if a ticket is yet to go on sale or not
213
     *
214
     * @return boolean
215
     * @throws \EE_Error
216
     */
217
    public function is_pending()
218
    {
219
        return ($this->get_raw('TKT_start_date') > time());
220
    }
221
222
223
    /**
224
     * Return if a ticket is on sale or not
225
     *
226
     * @return boolean
227
     * @throws \EE_Error
228
     */
229
    public function is_on_sale()
230
    {
231
        return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
232
    }
233
234
235
    /**
236
     * This returns the chronologically last datetime that this ticket is associated with
237
     *
238
     * @param string $dt_frmt
239
     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
240
     *                            the end date ie: Jan 01 "to" Dec 31
241
     * @return string
242
     * @throws \EE_Error
243
     */
244
    public function date_range($dt_frmt = '', $conjunction = ' - ')
245
    {
246
        $first_date = $this->first_datetime() instanceof EE_Datetime ? $this->first_datetime()->start_date($dt_frmt)
247
            : '';
248
        $last_date = $this->last_datetime() instanceof EE_Datetime ? $this->last_datetime()->end_date($dt_frmt) : '';
249
250
        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
251
    }
252
253
254
    /**
255
     * This returns the chronologically first datetime that this ticket is associated with
256
     *
257
     * @return EE_Datetime
258
     * @throws \EE_Error
259
     */
260
    public function first_datetime()
261
    {
262
        $datetimes = $this->datetimes(array('limit' => 1));
263
        return reset($datetimes);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression reset($datetimes); of type EE_Base_Class|false adds false to the return on line 263 which is incompatible with the return type documented by EE_Ticket::first_datetime of type EE_Datetime. It seems like you forgot to handle an error condition.
Loading history...
264
    }
265
266
267
    /**
268
     * Gets all the datetimes this ticket can be used for attending.
269
     * Unless otherwise specified, orders datetimes by start date.
270
     *
271
     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
272
     * @return EE_Datetime[]|EE_Base_Class[]
273
     * @throws \EE_Error
274
     */
275
    public function datetimes($query_params = array())
276
    {
277
        if (! isset($query_params['order_by'])) {
278
            $query_params['order_by']['DTT_order'] = 'ASC';
279
        }
280
        return $this->get_many_related('Datetime', $query_params);
281
    }
282
283
284
    /**
285
     * This returns the chronologically last datetime that this ticket is associated with
286
     *
287
     * @return EE_Datetime
288
     * @throws \EE_Error
289
     */
290
    public function last_datetime()
291
    {
292
        $datetimes = $this->datetimes(array('limit' => 1, 'order_by' => array('DTT_EVT_start' => 'DESC')));
293
        return end($datetimes);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression end($datetimes); of type EE_Base_Class|false adds false to the return on line 293 which is incompatible with the return type documented by EE_Ticket::last_datetime of type EE_Datetime. It seems like you forgot to handle an error condition.
Loading history...
294
    }
295
296
297
    /**
298
     * This returns the total tickets sold depending on the given parameters.
299
     *
300
     * @param  string $what   Can be one of two options: 'ticket', 'datetime'.
301
     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
302
     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
303
     *                        'datetime' = total ticket sales in the datetime_ticket table.
304
     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
305
     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
306
     * @param  int    $dtt_id [optional] include the dtt_id with $what = 'datetime'.
307
     * @return mixed (array|int)          how many tickets have sold
308
     * @throws \EE_Error
309
     */
310
    public function tickets_sold($what = 'ticket', $dtt_id = null)
311
    {
312
        $total = 0;
313
        $tickets_sold = $this->_all_tickets_sold();
314
        switch ($what) {
315
            case 'ticket':
316
                return $tickets_sold['ticket'];
317
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
318
            case 'datetime':
319
                if (empty($tickets_sold['datetime'])) {
320
                    return $total;
321
                }
322
                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
323
                    EE_Error::add_error(
324
                        __(
325
                            '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?',
326
                            'event_espresso'
327
                        ),
328
                        __FILE__,
329
                        __FUNCTION__,
330
                        __LINE__
331
                    );
332
                    return $total;
333
                }
334
                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
335
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
336
            default:
337
                return $total;
338
        }
339
    }
340
341
342
    /**
343
     * This returns an array indexed by datetime_id for tickets sold with this ticket.
344
     *
345
     * @return EE_Ticket[]
346
     * @throws \EE_Error
347
     */
348
    protected function _all_tickets_sold()
349
    {
350
        $datetimes = $this->get_many_related('Datetime');
351
        $tickets_sold = array();
352
        if (! empty($datetimes)) {
353
            foreach ($datetimes as $datetime) {
354
                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
355
            }
356
        }
357
        // Tickets sold
358
        $tickets_sold['ticket'] = $this->sold();
359
        return $tickets_sold;
360
    }
361
362
363
    /**
364
     * This returns the base price object for the ticket.
365
     *
366
     * @param  bool $return_array whether to return as an array indexed by price id or just the object.
367
     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
368
     * @throws \EE_Error
369
     */
370
    public function base_price($return_array = false)
371
    {
372
        $_where = array('Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price);
373
        return $return_array
374
            ? $this->get_many_related('Price', array($_where))
375
            : $this->get_first_related('Price', array($_where));
376
    }
377
378
379
    /**
380
     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
381
     *
382
     * @access public
383
     * @return EE_Price[]
384
     * @throws \EE_Error
385
     */
386
    public function price_modifiers()
387
    {
388
        $query_params = array(
389
            0 => array(
390
                'Price_Type.PBT_ID' => array(
391
                    'NOT IN',
392
                    array(EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax),
393
                ),
394
            ),
395
        );
396
        return $this->prices($query_params);
397
    }
398
399
400
    /**
401
     * Gets all the prices that combine to form the final price of this ticket
402
     *
403
     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
404
     * @return EE_Price[]|EE_Base_Class[]
405
     * @throws \EE_Error
406
     */
407
    public function prices($query_params = array())
408
    {
409
        return $this->get_many_related('Price', $query_params);
410
    }
411
412
413
    /**
414
     * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
415
     *
416
     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
417
     * @return EE_Datetime_Ticket|EE_Base_Class[]
418
     * @throws \EE_Error
419
     */
420
    public function datetime_tickets($query_params = array())
421
    {
422
        return $this->get_many_related('Datetime_Ticket', $query_params);
423
    }
424
425
426
    /**
427
     * Gets all the datetimes from the db ordered by DTT_order
428
     *
429
     * @param boolean $show_expired
430
     * @param boolean $show_deleted
431
     * @return EE_Datetime[]
432
     * @throws \EE_Error
433
     */
434
    public function datetimes_ordered($show_expired = true, $show_deleted = false)
435
    {
436
        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
437
            $this->ID(),
438
            $show_expired,
439
            $show_deleted
440
        );
441
    }
442
443
444
    /**
445
     * Gets ID
446
     *
447
     * @return string
448
     * @throws \EE_Error
449
     */
450
    public function ID()
451
    {
452
        return $this->get('TKT_ID');
453
    }
454
455
456
    /**
457
     * get the author of the ticket.
458
     *
459
     * @since 4.5.0
460
     * @return int
461
     * @throws \EE_Error
462
     */
463
    public function wp_user()
464
    {
465
        return $this->get('TKT_wp_user');
466
    }
467
468
469
    /**
470
     * Gets the template for the ticket
471
     *
472
     * @return EE_Ticket_Template|EE_Base_Class
473
     * @throws \EE_Error
474
     */
475
    public function template()
476
    {
477
        return $this->get_first_related('Ticket_Template');
478
    }
479
480
481
    /**
482
     * Simply returns an array of EE_Price objects that are taxes.
483
     *
484
     * @return EE_Price[]
485
     * @throws \EE_Error
486
     */
487
    public function get_ticket_taxes_for_admin()
488
    {
489
        return EE_Taxes::get_taxes_for_admin();
490
    }
491
492
493
    /**
494
     * @return float
495
     * @throws \EE_Error
496
     */
497
    public function ticket_price()
498
    {
499
        return $this->get('TKT_price');
500
    }
501
502
503
    /**
504
     * @return mixed
505
     * @throws \EE_Error
506
     */
507
    public function pretty_price()
508
    {
509
        return $this->get_pretty('TKT_price');
510
    }
511
512
513
    /**
514
     * @return bool
515
     * @throws \EE_Error
516
     */
517
    public function is_free()
518
    {
519
        return $this->get_ticket_total_with_taxes() === (float) 0;
520
    }
521
522
523
    /**
524
     * get_ticket_total_with_taxes
525
     *
526
     * @param bool $no_cache
527
     * @return float
528
     * @throws \EE_Error
529
     */
530
    public function get_ticket_total_with_taxes($no_cache = false)
531
    {
532
        if ($this->_ticket_total_with_taxes === null || $no_cache) {
533
            $this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
534
        }
535
        return (float) $this->_ticket_total_with_taxes;
536
    }
537
538
539
    public function ensure_TKT_Price_correct()
540
    {
541
        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
542
        $this->save();
543
    }
544
545
546
    /**
547
     * @return float
548
     * @throws \EE_Error
549
     */
550
    public function get_ticket_subtotal()
551
    {
552
        return EE_Taxes::get_subtotal_for_admin($this);
553
    }
554
555
556
    /**
557
     * Returns the total taxes applied to this ticket
558
     *
559
     * @return float
560
     * @throws \EE_Error
561
     */
562
    public function get_ticket_taxes_total_for_admin()
563
    {
564
        return EE_Taxes::get_total_taxes_for_admin($this);
565
    }
566
567
568
    /**
569
     * Sets name
570
     *
571
     * @param string $name
572
     * @throws \EE_Error
573
     */
574
    public function set_name($name)
575
    {
576
        $this->set('TKT_name', $name);
577
    }
578
579
580
    /**
581
     * Gets description
582
     *
583
     * @return string
584
     * @throws \EE_Error
585
     */
586
    public function description()
587
    {
588
        return $this->get('TKT_description');
589
    }
590
591
592
    /**
593
     * Sets description
594
     *
595
     * @param string $description
596
     * @throws \EE_Error
597
     */
598
    public function set_description($description)
599
    {
600
        $this->set('TKT_description', $description);
601
    }
602
603
604
    /**
605
     * Gets start_date
606
     *
607
     * @param string $dt_frmt
608
     * @param string $tm_frmt
609
     * @return string
610
     * @throws \EE_Error
611
     */
612
    public function start_date($dt_frmt = '', $tm_frmt = '')
613
    {
614
        return $this->_get_datetime('TKT_start_date', $dt_frmt, $tm_frmt);
615
    }
616
617
618
    /**
619
     * Sets start_date
620
     *
621
     * @param string $start_date
622
     * @return void
623
     * @throws \EE_Error
624
     */
625
    public function set_start_date($start_date)
626
    {
627
        $this->_set_date_time('B', $start_date, 'TKT_start_date');
628
    }
629
630
631
    /**
632
     * Gets end_date
633
     *
634
     * @param string $dt_frmt
635
     * @param string $tm_frmt
636
     * @return string
637
     * @throws \EE_Error
638
     */
639
    public function end_date($dt_frmt = '', $tm_frmt = '')
640
    {
641
        return $this->_get_datetime('TKT_end_date', $dt_frmt, $tm_frmt);
642
    }
643
644
645
    /**
646
     * Sets end_date
647
     *
648
     * @param string $end_date
649
     * @return void
650
     * @throws \EE_Error
651
     */
652
    public function set_end_date($end_date)
653
    {
654
        $this->_set_date_time('B', $end_date, 'TKT_end_date');
655
    }
656
657
658
    /**
659
     * Sets sell until time
660
     *
661
     * @since 4.5.0
662
     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
663
     * @throws \EE_Error
664
     */
665
    public function set_end_time($time)
666
    {
667
        $this->_set_time_for($time, 'TKT_end_date');
668
    }
669
670
671
    /**
672
     * Sets min
673
     *
674
     * @param int $min
675
     * @return void
676
     * @throws \EE_Error
677
     */
678
    public function set_min($min)
679
    {
680
        $this->set('TKT_min', $min);
681
    }
682
683
684
    /**
685
     * Gets max
686
     *
687
     * @return int
688
     * @throws \EE_Error
689
     */
690
    public function max()
691
    {
692
        return $this->get('TKT_max');
693
    }
694
695
696
    /**
697
     * Sets max
698
     *
699
     * @param int $max
700
     * @return void
701
     * @throws \EE_Error
702
     */
703
    public function set_max($max)
704
    {
705
        $this->set('TKT_max', $max);
706
    }
707
708
709
    /**
710
     * Sets price
711
     *
712
     * @param float $price
713
     * @return void
714
     * @throws \EE_Error
715
     */
716
    public function set_price($price)
717
    {
718
        $this->set('TKT_price', $price);
719
    }
720
721
722
    /**
723
     * Gets sold
724
     *
725
     * @return int
726
     * @throws \EE_Error
727
     */
728
    public function sold()
729
    {
730
        return $this->get_raw('TKT_sold');
731
    }
732
733
734
    /**
735
     * Sets sold
736
     *
737
     * @param int $sold
738
     * @return void
739
     * @throws \EE_Error
740
     */
741
    public function set_sold($sold)
742
    {
743
        // sold can not go below zero
744
        $sold = max(0, $sold);
745
        $this->set('TKT_sold', $sold);
746
    }
747
748
749
    /**
750
     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
751
     * associated datetimes.
752
     *
753
     * @param int $qty
754
     * @return void
755
     * @throws EE_Error
756
     * @throws InvalidArgumentException
757
     * @throws InvalidDataTypeException
758
     * @throws InvalidInterfaceException
759
     * @throws ReflectionException
760
     */
761
    public function increase_sold($qty = 1)
762
    {
763
        $qty = absint($qty);
764
        // increment sold and decrement reserved datetime quantities simultaneously
765
        // don't worry about failures, because they must have already had a spot reserved
766
        $this->sellDatetimes($qty);
767
        // Increment and decrement ticket quantities simultaneously
768
        $this->bump(
769
            [
770
                'TKT_reserved' => $qty * -1,
771
                'TKT_sold' => $qty
772
            ]
773
        );
774
        do_action(
775
            'AHEE__EE_Ticket__increase_sold',
776
            $this,
777
            $qty,
778
            $this->sold()
779
        );
780
    }
781
782
    /**
783
     * On each datetiem related to this ticket, increases its sold count and decreases its reserved count by $qty.
784
     * @since $VID:$
785
     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
786
     *             Negative means to decreases old counts (and increase reserved counts).
787
     * @throws EE_Error
788
     * @throws InvalidArgumentException
789
     * @throws InvalidDataTypeException
790
     * @throws InvalidInterfaceException
791
     * @throws ReflectionException
792
     */
793
    protected function sellDatetimes($qty)
794
    {
795
        $qty = $qty;
0 ignored issues
show
Bug introduced by
Why assign $qty to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
796
        foreach ($this->datetimes() as $datetime) {
797
            $datetime->bump(
798
                [
799
                    'DTT_reserved' => $qty * -1,
800
                    'DTT_sold' => $qty
801
                ]
802
            );
803
        }
804
    }
805
806
807
    /**
808
     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
809
     * DB and then updates the model objects.
810
     * Does not affect the reserved counts.
811
     *
812
     * @param int $qty
813
     * @return void
814
     * @throws EE_Error
815
     * @throws InvalidArgumentException
816
     * @throws InvalidDataTypeException
817
     * @throws InvalidInterfaceException
818
     * @throws ReflectionException
819
     */
820
    public function decrease_sold($qty = 1)
821
    {
822
        $qty = absint($qty);
823
        foreach ($this->datetimes() as $datetime) {
824
            $datetime->decrease_sold($qty);
825
        }
826
        $this->bump(
827
            [
828
                'TKT_sold' => $qty * -1
829
            ]
830
        );
831
        do_action(
832
            'AHEE__EE_Ticket__decrease_sold',
833
            $this,
834
            $qty,
835
            $this->sold()
836
        );
837
    }
838
839
840
    /**
841
     * Gets qty of reserved tickets
842
     *
843
     * @return int
844
     * @throws \EE_Error
845
     */
846
    public function reserved()
847
    {
848
        return $this->get_raw('TKT_reserved');
849
    }
850
851
852
    /**
853
     * Sets reserved
854
     *
855
     * @param int $reserved
856
     * @return void
857
     * @throws \EE_Error
858
     */
859
    public function set_reserved($reserved)
860
    {
861
        // reserved can not go below zero
862
        $reserved = max(0, (int) $reserved);
863
        $this->set('TKT_reserved', $reserved);
864
    }
865
866
867
    /**
868
     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
869
     *
870
     * @param int    $qty
871
     * @param string $source
872
     * @return bool whether we successfully reserved the ticket or not.
873
     * @throws EE_Error
874
     * @throws InvalidArgumentException
875
     * @throws ReflectionException
876
     * @throws InvalidDataTypeException
877
     * @throws InvalidInterfaceException
878
     */
879
    public function increase_reserved($qty = 1, $source = 'unknown')
880
    {
881
        do_action(
882
            'AHEE__EE_Ticket__increase_reserved__begin',
883
            $this,
884
            $qty,
885
            $source
886
        );
887
        if (! $this->add_extra_meta(
888
                EE_Ticket::META_KEY_TICKET_RESERVATIONS,
889
                "{$qty} from {$source}"
890
            )
891
        ) {
892
            return false;
893
        }
894
        $datetimes_adjusted_successfully = $this->_increase_reserved_for_datetimes($qty);
895
        if( $datetimes_adjusted_successfully ) {
896
            $successful_bump = $this->bumpConditionally(
897
                'TKT_reserved',
898
                'TKT_sold',
899
                'TKT_qty',
900
                absint($qty)
901
            );
902
            if (! $successful_bump) {
903
                // The datetimes were successfully bumped, but not the
904
                // ticket. So we need to manually rollback the datetimes.
905
                $this->_decrease_reserved_for_datetimes($qty);
906
            }
907
        } else {
908
            $successful_bump = false;
909
        }
910
        do_action(
911
            'AHEE__EE_Ticket__increase_reserved',
912
            $this,
913
            $qty,
914
            $this->reserved(),
915
            $successful_bump
916
        );
917
        return $successful_bump;
918
    }
919
920
921
    /**
922
     * Increases sold on related datetimes
923
     *
924
     * @param int $qty
925
     * @return boolean indicating success
926
     * @throws \EE_Error
927
     */
928
    protected function _increase_reserved_for_datetimes($qty = 1)
929
    {
930
        $datetimes = $this->datetimes();
931
        $datetimes_updated = [];
932
        $limit_exceeded = false;
933
        if (is_array($datetimes)) {
934
            foreach ($datetimes as $datetime) {
935
                if ($datetime instanceof EE_Datetime) {
936
                    if ($datetime->increase_reserved($qty)) {
937
                        $datetimes_updated[] = $datetime;
938
                    } else {
939
                        $limit_exceeded = true;
940
                        break;
941
                    }
942
                }
943
            }
944
            // If somewhere along the way we detected a datetime whose
945
            // limit was exceeded, do a manual rollback.
946
            if( $limit_exceeded ) {
947
                $this->decreaseReservedForDatetimes($datetimes_updated, $qty);
948
                return false;
949
            }
950
        }
951
        return true;
952
    }
953
954
    /**
955
     * Decreases the reserved count on the specified datetimes.
956
     * @since $VID:$
957
     * @param EE_Datetime[] $datetimes
958
     * @param int $qty
959
     * @throws EE_Error
960
     * @throws InvalidArgumentException
961
     * @throws ReflectionException
962
     * @throws InvalidDataTypeException
963
     * @throws InvalidInterfaceException
964
     */
965
    protected function decreaseReservedForDatetimes($datetimes, $qty = 1) {
966
        foreach($datetimes as $datetime) {
967
            if ($datetime instanceof EE_Datetime) {
968
                $datetime->decrease_reserved($qty);
969
            }
970
        }
971
    }
972
973
974
    /**
975
     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
976
     *
977
     * @param int    $qty
978
     * @param bool   $adjust_datetimes
979
     * @param string $source
980
     * @return void
981
     * @throws EE_Error
982
     * @throws InvalidArgumentException
983
     * @throws ReflectionException
984
     * @throws InvalidDataTypeException
985
     * @throws InvalidInterfaceException
986
     */
987
    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
988
    {
989
        $reserved = $this->reserved() - absint($qty);
990
        if (! $this->add_extra_meta(
991
            EE_Ticket::META_KEY_TICKET_RESERVATIONS,
992
            "-{$qty} from {$source}"
993
        )) {
994
            return false;
995
        }
996
        if ($adjust_datetimes) {
997
            $this->_decrease_reserved_for_datetimes($qty);
998
        }
999
        $this->bump(
1000
            [
1001
                'TKT_reserved' =>  absint($qty) * -1
1002
            ]
1003
        );
1004
        do_action(
1005
            'AHEE__EE_Ticket__decrease_reserved',
1006
            $this,
1007
            $qty,
1008
            $reserved
1009
        );
1010
    }
1011
1012
1013
    /**
1014
     * Decreases reserved on related datetimes
1015
     *
1016
     * @param int $qty
1017
     * @return void
1018
     * @throws EE_Error
1019
     * @throws InvalidArgumentException
1020
     * @throws ReflectionException
1021
     * @throws InvalidDataTypeException
1022
     * @throws InvalidInterfaceException
1023
     */
1024
    protected function _decrease_reserved_for_datetimes($qty = 1)
1025
    {
1026
        $this->decreaseReservedForDatetimes($this->datetimes(), $qty);
1027
    }
1028
1029
1030
    /**
1031
     * Gets ticket quantity
1032
     *
1033
     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1034
     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1035
     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1036
     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1037
     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1038
     *                            is therefore the truest measure of tickets that can be purchased at the moment
1039
     * @return int
1040
     * @throws \EE_Error
1041
     */
1042
    public function qty($context = '')
1043
    {
1044
        switch ($context) {
1045
            case 'reg_limit':
1046
                return $this->real_quantity_on_ticket();
1047
            case 'saleable':
1048
                return $this->real_quantity_on_ticket('saleable');
1049
            default:
1050
                return $this->get_raw('TKT_qty');
1051
        }
1052
    }
1053
1054
1055
    /**
1056
     * Gets ticket quantity
1057
     *
1058
     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1059
     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1060
     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1061
     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1062
     *                            is therefore the truest measure of tickets that can be purchased at the moment
1063
     * @param  int   $DTT_ID      the primary key for a particular datetime.
1064
     *                            set to 0 for all related datetimes
1065
     * @return int
1066
     * @throws \EE_Error
1067
     */
1068
    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1069
    {
1070
        $raw = $this->get_raw('TKT_qty');
1071
        // return immediately if it's zero
1072
        if ($raw === 0) {
1073
            return $raw;
1074
        }
1075
        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1076
        // ensure qty doesn't exceed raw value for THIS ticket
1077
        $qty = min(EE_INF, $raw);
1078
        // echo "\n . qty: " . $qty . '<br />';
1079
        // calculate this ticket's total sales and reservations
1080
        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1081
        // echo "\n . sold: " . $this->sold() . '<br />';
1082
        // echo "\n . reserved: " . $this->reserved() . '<br />';
1083
        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1084
        // first we need to calculate the maximum number of tickets available for the datetime
1085
        // do we want data for one datetime or all of them ?
1086
        $query_params = $DTT_ID ? array(array('DTT_ID' => $DTT_ID)) : array();
1087
        $datetimes = $this->datetimes($query_params);
1088
        if (is_array($datetimes) && ! empty($datetimes)) {
1089
            foreach ($datetimes as $datetime) {
1090
                if ($datetime instanceof EE_Datetime) {
1091
                    $datetime->refresh_from_db();
1092
                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1093
                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1094
                    // initialize with no restrictions for each datetime
1095
                    // but adjust datetime qty based on datetime reg limit
1096
                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1097
                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1098
                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1099
                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1100
                    // and reservations for this datetime, that do NOT include sales and reservations
1101
                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1102
                    if ($context === 'saleable') {
1103
                        $datetime_qty = max(
1104
                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1105
                            0
1106
                        );
1107
                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1108
                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1109
                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1110
                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1111
                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1112
                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1113
                    }
1114
                    $qty = min($datetime_qty, $qty);
1115
                    // echo "\n . . qty: " . $qty . '<br />';
1116
                }
1117
            }
1118
        }
1119
        // NOW that we know the  maximum number of tickets available for the datetime
1120
        // we can finally factor in the details for this specific ticket
1121
        if ($qty > 0 && $context === 'saleable') {
1122
            // and subtract the sales for THIS ticket
1123
            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1124
            // echo "\n . qty: " . $qty . '<br />';
1125
        }
1126
        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1127
        return $qty;
1128
    }
1129
1130
1131
    /**
1132
     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1133
     *
1134
     * @param int $qty
1135
     * @return void
1136
     * @throws \EE_Error
1137
     */
1138
    public function set_qty($qty)
1139
    {
1140
        $datetimes = $this->datetimes();
1141
        foreach ($datetimes as $datetime) {
1142
            if ($datetime instanceof EE_Datetime) {
1143
                $qty = min($qty, $datetime->reg_limit());
1144
            }
1145
        }
1146
        $this->set('TKT_qty', $qty);
1147
    }
1148
1149
1150
    /**
1151
     * Gets uses
1152
     *
1153
     * @return int
1154
     * @throws \EE_Error
1155
     */
1156
    public function uses()
1157
    {
1158
        return $this->get('TKT_uses');
1159
    }
1160
1161
1162
    /**
1163
     * Sets uses
1164
     *
1165
     * @param int $uses
1166
     * @return void
1167
     * @throws \EE_Error
1168
     */
1169
    public function set_uses($uses)
1170
    {
1171
        $this->set('TKT_uses', $uses);
1172
    }
1173
1174
1175
    /**
1176
     * returns whether ticket is required or not.
1177
     *
1178
     * @return boolean
1179
     * @throws \EE_Error
1180
     */
1181
    public function required()
1182
    {
1183
        return $this->get('TKT_required');
1184
    }
1185
1186
1187
    /**
1188
     * sets the TKT_required property
1189
     *
1190
     * @param boolean $required
1191
     * @return void
1192
     * @throws \EE_Error
1193
     */
1194
    public function set_required($required)
1195
    {
1196
        $this->set('TKT_required', $required);
1197
    }
1198
1199
1200
    /**
1201
     * Gets taxable
1202
     *
1203
     * @return boolean
1204
     * @throws \EE_Error
1205
     */
1206
    public function taxable()
1207
    {
1208
        return $this->get('TKT_taxable');
1209
    }
1210
1211
1212
    /**
1213
     * Sets taxable
1214
     *
1215
     * @param boolean $taxable
1216
     * @return void
1217
     * @throws \EE_Error
1218
     */
1219
    public function set_taxable($taxable)
1220
    {
1221
        $this->set('TKT_taxable', $taxable);
1222
    }
1223
1224
1225
    /**
1226
     * Gets is_default
1227
     *
1228
     * @return boolean
1229
     * @throws \EE_Error
1230
     */
1231
    public function is_default()
1232
    {
1233
        return $this->get('TKT_is_default');
1234
    }
1235
1236
1237
    /**
1238
     * Sets is_default
1239
     *
1240
     * @param boolean $is_default
1241
     * @return void
1242
     * @throws \EE_Error
1243
     */
1244
    public function set_is_default($is_default)
1245
    {
1246
        $this->set('TKT_is_default', $is_default);
1247
    }
1248
1249
1250
    /**
1251
     * Gets order
1252
     *
1253
     * @return int
1254
     * @throws \EE_Error
1255
     */
1256
    public function order()
1257
    {
1258
        return $this->get('TKT_order');
1259
    }
1260
1261
1262
    /**
1263
     * Sets order
1264
     *
1265
     * @param int $order
1266
     * @return void
1267
     * @throws \EE_Error
1268
     */
1269
    public function set_order($order)
1270
    {
1271
        $this->set('TKT_order', $order);
1272
    }
1273
1274
1275
    /**
1276
     * Gets row
1277
     *
1278
     * @return int
1279
     * @throws \EE_Error
1280
     */
1281
    public function row()
1282
    {
1283
        return $this->get('TKT_row');
1284
    }
1285
1286
1287
    /**
1288
     * Sets row
1289
     *
1290
     * @param int $row
1291
     * @return void
1292
     * @throws \EE_Error
1293
     */
1294
    public function set_row($row)
1295
    {
1296
        $this->set('TKT_row', $row);
1297
    }
1298
1299
1300
    /**
1301
     * Gets deleted
1302
     *
1303
     * @return boolean
1304
     * @throws \EE_Error
1305
     */
1306
    public function deleted()
1307
    {
1308
        return $this->get('TKT_deleted');
1309
    }
1310
1311
1312
    /**
1313
     * Sets deleted
1314
     *
1315
     * @param boolean $deleted
1316
     * @return void
1317
     * @throws \EE_Error
1318
     */
1319
    public function set_deleted($deleted)
1320
    {
1321
        $this->set('TKT_deleted', $deleted);
1322
    }
1323
1324
1325
    /**
1326
     * Gets parent
1327
     *
1328
     * @return int
1329
     * @throws \EE_Error
1330
     */
1331
    public function parent_ID()
1332
    {
1333
        return $this->get('TKT_parent');
1334
    }
1335
1336
1337
    /**
1338
     * Sets parent
1339
     *
1340
     * @param int $parent
1341
     * @return void
1342
     * @throws \EE_Error
1343
     */
1344
    public function set_parent_ID($parent)
1345
    {
1346
        $this->set('TKT_parent', $parent);
1347
    }
1348
1349
1350
    /**
1351
     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1352
     *
1353
     * @return string
1354
     * @throws \EE_Error
1355
     */
1356
    public function name_and_info()
1357
    {
1358
        $times = array();
1359
        foreach ($this->datetimes() as $datetime) {
1360
            $times[] = $datetime->start_date_and_time();
1361
        }
1362
        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1363
    }
1364
1365
1366
    /**
1367
     * Gets name
1368
     *
1369
     * @return string
1370
     * @throws \EE_Error
1371
     */
1372
    public function name()
1373
    {
1374
        return $this->get('TKT_name');
1375
    }
1376
1377
1378
    /**
1379
     * Gets price
1380
     *
1381
     * @return float
1382
     * @throws \EE_Error
1383
     */
1384
    public function price()
1385
    {
1386
        return $this->get('TKT_price');
1387
    }
1388
1389
1390
    /**
1391
     * Gets all the registrations for this ticket
1392
     *
1393
     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1394
     * @return EE_Registration[]|EE_Base_Class[]
1395
     * @throws \EE_Error
1396
     */
1397
    public function registrations($query_params = array())
1398
    {
1399
        return $this->get_many_related('Registration', $query_params);
1400
    }
1401
1402
1403
    /**
1404
     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1405
     * into account
1406
     *
1407
     * @return int
1408
     * @throws \EE_Error
1409
     */
1410 View Code Duplication
    public function update_tickets_sold()
1411
    {
1412
        $count_regs_for_this_ticket = $this->count_registrations(
1413
            array(
1414
                array(
1415
                    'STS_ID'      => EEM_Registration::status_id_approved,
1416
                    'REG_deleted' => 0,
1417
                ),
1418
            )
1419
        );
1420
        $sold = $this->sold();
1421
        if ($count_regs_for_this_ticket > $sold) {
1422
            $this->increase_sold($count_regs_for_this_ticket - $sold);
1423
        } elseif ($count_regs_for_this_ticket < $sold) {
1424
            $this->decrease_sold($count_regs_for_this_ticket - $sold);
1425
        }
1426
        return $count_regs_for_this_ticket;
1427
    }
1428
1429
1430
    /**
1431
     * Counts the registrations for this ticket
1432
     *
1433
     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1434
     * @return int
1435
     */
1436
    public function count_registrations($query_params = array())
1437
    {
1438
        return $this->count_related('Registration', $query_params);
1439
    }
1440
1441
1442
    /**
1443
     * Implementation for EEI_Has_Icon interface method.
1444
     *
1445
     * @see EEI_Visual_Representation for comments
1446
     * @return string
1447
     */
1448
    public function get_icon()
1449
    {
1450
        return '<span class="dashicons dashicons-tickets-alt"></span>';
1451
    }
1452
1453
1454
    /**
1455
     * Implementation of the EEI_Event_Relation interface method
1456
     *
1457
     * @see EEI_Event_Relation for comments
1458
     * @return EE_Event
1459
     * @throws \EE_Error
1460
     * @throws UnexpectedEntityException
1461
     */
1462
    public function get_related_event()
1463
    {
1464
        // get one datetime to use for getting the event
1465
        $datetime = $this->first_datetime();
1466
        if (! $datetime instanceof \EE_Datetime) {
1467
            throw new UnexpectedEntityException(
1468
                $datetime,
1469
                'EE_Datetime',
1470
                sprintf(
1471
                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1472
                    $this->name()
1473
                )
1474
            );
1475
        }
1476
        $event = $datetime->event();
1477
        if (! $event instanceof \EE_Event) {
1478
            throw new UnexpectedEntityException(
1479
                $event,
1480
                'EE_Event',
1481
                sprintf(
1482
                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1483
                    $this->name()
1484
                )
1485
            );
1486
        }
1487
        return $event;
1488
    }
1489
1490
1491
    /**
1492
     * Implementation of the EEI_Event_Relation interface method
1493
     *
1494
     * @see EEI_Event_Relation for comments
1495
     * @return string
1496
     * @throws UnexpectedEntityException
1497
     * @throws \EE_Error
1498
     */
1499
    public function get_event_name()
1500
    {
1501
        $event = $this->get_related_event();
1502
        return $event instanceof EE_Event ? $event->name() : '';
1503
    }
1504
1505
1506
    /**
1507
     * Implementation of the EEI_Event_Relation interface method
1508
     *
1509
     * @see EEI_Event_Relation for comments
1510
     * @return int
1511
     * @throws UnexpectedEntityException
1512
     * @throws \EE_Error
1513
     */
1514
    public function get_event_ID()
1515
    {
1516
        $event = $this->get_related_event();
1517
        return $event instanceof EE_Event ? $event->ID() : 0;
1518
    }
1519
1520
1521
    /**
1522
     * This simply returns whether a ticket can be permanently deleted or not.
1523
     * The criteria for determining this is whether the ticket has any related registrations.
1524
     * If there are none then it can be permanently deleted.
1525
     *
1526
     * @return bool
1527
     */
1528
    public function is_permanently_deleteable()
1529
    {
1530
        return $this->count_registrations() === 0;
1531
    }
1532
}
1533