Completed
Branch FET-3467-waitlists (cd393b)
by
unknown
67:24 queued 55:30
created

EE_Ticket::is_permanently_deleteable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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