Completed
Branch BUG/11302/correct-error-messag... (694f28)
by
unknown
29:59 queued 17:10
created

EE_Ticket::first_datetime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 4
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
		do_action(
747
		    'AHEE__EE_Ticket__increase_sold',
748
            $this,
749
            $qty,
750
            $sold
751
        );
752
	}
753
754
755
756
    /**
757
     * Increases sold on related datetimes
758
     *
759
     * @param int $qty
760
     * @return void
761
     * @throws \EE_Error
762
     */
763 View Code Duplication
	protected function _increase_sold_for_datetimes( $qty = 1 ) {
764
		$datetimes = $this->datetimes();
765
		if ( is_array( $datetimes ) ) {
766
			foreach ( $datetimes as $datetime ) {
767
				if ( $datetime instanceof EE_Datetime ) {
768
					$datetime->increase_sold( $qty );
769
					$datetime->save();
770
				}
771
			}
772
		}
773
	}
774
775
776
777
    /**
778
     * decrements (subtracts) sold by amount passed by $qty
779
     *
780
     * @param int $qty
781
     * @return void
782
     * @throws \EE_Error
783
     */
784
	public function decrease_sold( $qty = 1 ) {
785
		$sold = $this->sold() - $qty;
786
		$this->_decrease_sold_for_datetimes( $qty );
787
		$this->set_sold( $sold );
788
        do_action(
789
            'AHEE__EE_Ticket__decrease_sold',
790
            $this,
791
            $qty,
792
            $sold
793
        );
794
    }
795
796
797
798
    /**
799
     * Decreases sold on related datetimes
800
     *
801
     * @param int $qty
802
     * @return void
803
     * @throws \EE_Error
804
     */
805 View Code Duplication
	protected function _decrease_sold_for_datetimes( $qty = 1 ) {
806
		$datetimes = $this->datetimes();
807
		if ( is_array( $datetimes ) ) {
808
			foreach ( $datetimes as $datetime ) {
809
				if ( $datetime instanceof EE_Datetime ) {
810
					$datetime->decrease_sold( $qty );
811
					$datetime->save();
812
				}
813
			}
814
		}
815
	}
816
817
818
819
    /**
820
     * Gets qty of reserved tickets
821
     *
822
     * @return int
823
     * @throws \EE_Error
824
     */
825
	public function reserved() {
826
		return $this->get_raw( 'TKT_reserved' );
827
	}
828
829
830
831
    /**
832
     * Sets reserved
833
     *
834
     * @param int $reserved
835
     * @return void
836
     * @throws \EE_Error
837
     */
838
	public function set_reserved( $reserved ) {
839
		// reserved can not go below zero
840
		$reserved = max( 0, (int) $reserved );
841
		$this->set( 'TKT_reserved', $reserved );
842
	}
843
844
845
846
    /**
847
     * increments reserved by amount passed by $qty
848
     *
849
     * @param int $qty
850
     * @return void
851
     * @throws \EE_Error
852
     */
853 View Code Duplication
	public function increase_reserved( $qty = 1 ) {
854
		$qty = absint( $qty );
855
		$reserved = $this->reserved() + $qty;
856
		$this->_increase_reserved_for_datetimes( $qty );
857
		$this->set_reserved( $reserved );
858
        do_action(
859
            'AHEE__EE_Ticket__increase_reserved',
860
            $this,
861
            $qty,
862
            $reserved
863
        );
864
    }
865
866
867
868
    /**
869
     * Increases sold on related datetimes
870
     *
871
     * @param int $qty
872
     * @return void
873
     * @throws \EE_Error
874
     */
875 View Code Duplication
	protected function _increase_reserved_for_datetimes( $qty = 1 ) {
876
		$datetimes = $this->datetimes();
877
		if ( is_array( $datetimes ) ) {
878
			foreach ( $datetimes as $datetime ) {
879
				if ( $datetime instanceof EE_Datetime ) {
880
					$datetime->increase_reserved( $qty );
881
					$datetime->save();
882
				}
883
			}
884
		}
885
	}
886
887
888
889
    /**
890
     * decrements (subtracts) reserved by amount passed by $qty
891
     *
892
     * @param int  $qty
893
     * @param bool $adjust_datetimes
894
     * @return void
895
     * @throws \EE_Error
896
     */
897 View Code Duplication
	public function decrease_reserved( $qty = 1, $adjust_datetimes = true ) {
898
		$reserved = $this->reserved() - absint( $qty );
899
		if ( $adjust_datetimes ) {
900
			$this->_decrease_reserved_for_datetimes( $qty );
901
		}
902
		$this->set_reserved( $reserved );
903
        do_action(
904
            'AHEE__EE_Ticket__decrease_reserved',
905
            $this,
906
            $qty,
907
            $reserved
908
        );
909
    }
910
911
912
913
    /**
914
     * Increases sold on related datetimes
915
     *
916
     * @param int $qty
917
     * @return void
918
     * @throws \EE_Error
919
     */
920 View Code Duplication
	protected function _decrease_reserved_for_datetimes( $qty = 1 ) {
921
		$datetimes = $this->datetimes();
922
		if ( is_array( $datetimes ) ) {
923
			foreach ( $datetimes as $datetime ) {
924
				if ( $datetime instanceof EE_Datetime ) {
925
					$datetime->decrease_reserved( $qty );
926
					$datetime->save();
927
				}
928
			}
929
		}
930
	}
931
932
933
934
    /**
935
     * Gets ticket quantity
936
     *
937
     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
938
     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
939
     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
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
     * @return int
944
     * @throws \EE_Error
945
     */
946
	public function qty( $context = '' ) {
947
		switch ( $context ) {
948
			case 'reg_limit' :
949
				return $this->real_quantity_on_ticket();
950
			case 'saleable' :
951
				return $this->real_quantity_on_ticket( 'saleable' );
952
			default:
953
				return $this->get_raw( 'TKT_qty' );
954
		}
955
	}
956
957
958
959
    /**
960
     * Gets ticket quantity
961
     *
962
     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
963
     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
964
     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
965
     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
966
     *                            is therefore the truest measure of tickets that can be purchased at the moment
967
     * @param  int   $DTT_ID      the primary key for a particular datetime.
968
     *                            set to 0 for all related datetimes
969
     * @return int
970
     * @throws \EE_Error
971
     */
972
	public function real_quantity_on_ticket( $context = 'reg_limit', $DTT_ID = 0 ) {
973
		$raw = $this->get_raw( 'TKT_qty' );
974
		// return immediately if it's zero
975
		if ( $raw === 0 ) {
976
			return $raw;
977
		}
978
		//echo "\n\n<br />Ticket: " . $this->name() . '<br />';
979
		// ensure qty doesn't exceed raw value for THIS ticket
980
		$qty = min( EE_INF, $raw );
981
		//echo "\n . qty: " . $qty . '<br />';
982
		// calculate this ticket's total sales and reservations
983
		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
984
		//echo "\n . sold: " . $this->sold() . '<br />';
985
		//echo "\n . reserved: " . $this->reserved() . '<br />';
986
		//echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
987
		// first we need to calculate the maximum number of tickets available for the datetime
988
		// do we want data for one datetime or all of them ?
989
		$query_params = $DTT_ID ? array( array( 'DTT_ID' => $DTT_ID ) ) : array();
990
		$datetimes = $this->datetimes( $query_params );
991
		if ( is_array( $datetimes ) && ! empty( $datetimes ) ) {
992
			foreach ( $datetimes as $datetime ) {
993
				if ( $datetime instanceof EE_Datetime ) {
994
					$datetime->refresh_from_db();
995
					//echo "\n . . datetime name: " . $datetime->name() . '<br />';
996
					//echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
997
					// initialize with no restrictions for each datetime
998
					// but adjust datetime qty based on datetime reg limit
999
					$datetime_qty = min( EE_INF, $datetime->reg_limit() );
1000
					//echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1001
					//echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1002
					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1003
					// and reservations for this datetime, that do NOT include sales and reservations
1004
					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1005
					if ( $context === 'saleable' ) {
1006
						$datetime_qty = max(
1007
							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1008
							0
1009
						);
1010
						//echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1011
						//echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1012
						//echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1013
						//echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1014
						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1015
						//echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1016
					}
1017
					$qty = min( $datetime_qty, $qty );
1018
					//echo "\n . . qty: " . $qty . '<br />';
1019
				}
1020
			}
1021
		}
1022
		// NOW that we know the  maximum number of tickets available for the datetime
1023
		// we can finally factor in the details for this specific ticket
1024
		if ( $qty > 0 && $context === 'saleable' ) {
1025
			// and subtract the sales for THIS ticket
1026
			$qty = max( $qty - $sold_and_reserved_for_this_ticket, 0 );
1027
			//echo "\n . qty: " . $qty . '<br />';
1028
		}
1029
		//echo "\nFINAL QTY: " . $qty . "<br /><br />";
1030
		return $qty;
1031
	}
1032
1033
1034
1035
	/**
1036
	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1037
	 *
1038
	 * @param int  $qty
1039
	 * @return void
1040
	 * @throws \EE_Error
1041
	 */
1042 View Code Duplication
	public function set_qty( $qty ) {
1043
		$datetimes = $this->datetimes();
1044
		foreach ( $datetimes as $datetime ) {
1045
			if ( $datetime instanceof EE_Datetime ) {
1046
				$qty = min( $qty, $datetime->reg_limit() );
1047
			}
1048
		}
1049
		$this->set( 'TKT_qty', $qty );
1050
	}
1051
1052
1053
1054
    /**
1055
     * Gets uses
1056
     *
1057
     * @return int
1058
     * @throws \EE_Error
1059
     */
1060
	public function uses() {
1061
		return $this->get( 'TKT_uses' );
1062
	}
1063
1064
1065
1066
    /**
1067
     * Sets uses
1068
     *
1069
     * @param int $uses
1070
     * @return void
1071
     * @throws \EE_Error
1072
     */
1073
	public function set_uses( $uses ) {
1074
		$this->set( 'TKT_uses', $uses );
1075
	}
1076
1077
1078
1079
    /**
1080
     * returns whether ticket is required or not.
1081
     *
1082
     * @return boolean
1083
     * @throws \EE_Error
1084
     */
1085
	public function required() {
1086
		return $this->get( 'TKT_required' );
1087
	}
1088
1089
1090
1091
    /**
1092
     * sets the TKT_required property
1093
     *
1094
     * @param boolean $required
1095
     * @return void
1096
     * @throws \EE_Error
1097
     */
1098
	public function set_required( $required ) {
1099
		$this->set( 'TKT_required', $required );
1100
	}
1101
1102
1103
1104
    /**
1105
     * Gets taxable
1106
     *
1107
     * @return boolean
1108
     * @throws \EE_Error
1109
     */
1110
	public function taxable() {
1111
		return $this->get( 'TKT_taxable' );
1112
	}
1113
1114
1115
1116
    /**
1117
     * Sets taxable
1118
     *
1119
     * @param boolean $taxable
1120
     * @return void
1121
     * @throws \EE_Error
1122
     */
1123
	public function set_taxable( $taxable ) {
1124
		$this->set( 'TKT_taxable', $taxable );
1125
	}
1126
1127
1128
1129
    /**
1130
     * Gets is_default
1131
     *
1132
     * @return boolean
1133
     * @throws \EE_Error
1134
     */
1135
	public function is_default() {
1136
		return $this->get( 'TKT_is_default' );
1137
	}
1138
1139
1140
1141
    /**
1142
     * Sets is_default
1143
     *
1144
     * @param boolean $is_default
1145
     * @return void
1146
     * @throws \EE_Error
1147
     */
1148
	public function set_is_default( $is_default ) {
1149
		$this->set( 'TKT_is_default', $is_default );
1150
	}
1151
1152
1153
1154
    /**
1155
     * Gets order
1156
     *
1157
     * @return int
1158
     * @throws \EE_Error
1159
     */
1160
	public function order() {
1161
		return $this->get( 'TKT_order' );
1162
	}
1163
1164
1165
1166
    /**
1167
     * Sets order
1168
     *
1169
     * @param int $order
1170
     * @return void
1171
     * @throws \EE_Error
1172
     */
1173
	public function set_order( $order ) {
1174
		$this->set( 'TKT_order', $order );
1175
	}
1176
1177
1178
1179
    /**
1180
     * Gets row
1181
     *
1182
     * @return int
1183
     * @throws \EE_Error
1184
     */
1185
	public function row() {
1186
		return $this->get( 'TKT_row' );
1187
	}
1188
1189
1190
1191
    /**
1192
     * Sets row
1193
     *
1194
     * @param int $row
1195
     * @return void
1196
     * @throws \EE_Error
1197
     */
1198
	public function set_row( $row ) {
1199
		$this->set( 'TKT_row', $row );
1200
	}
1201
1202
1203
1204
    /**
1205
     * Gets deleted
1206
     *
1207
     * @return boolean
1208
     * @throws \EE_Error
1209
     */
1210
	public function deleted() {
1211
		return $this->get( 'TKT_deleted' );
1212
	}
1213
1214
1215
1216
    /**
1217
     * Sets deleted
1218
     *
1219
     * @param boolean $deleted
1220
     * @return void
1221
     * @throws \EE_Error
1222
     */
1223
	public function set_deleted( $deleted ) {
1224
		$this->set( 'TKT_deleted', $deleted );
1225
	}
1226
1227
1228
1229
    /**
1230
     * Gets parent
1231
     *
1232
     * @return int
1233
     * @throws \EE_Error
1234
     */
1235
	public function parent_ID() {
1236
		return $this->get( 'TKT_parent' );
1237
	}
1238
1239
1240
1241
    /**
1242
     * Sets parent
1243
     *
1244
     * @param int $parent
1245
     * @return void
1246
     * @throws \EE_Error
1247
     */
1248
	public function set_parent_ID( $parent ) {
1249
		$this->set( 'TKT_parent', $parent );
1250
	}
1251
1252
1253
1254
    /**
1255
     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1256
     *
1257
     * @return string
1258
     * @throws \EE_Error
1259
     */
1260
	public function name_and_info() {
1261
		$times = array();
1262
		foreach ( $this->datetimes() as $datetime ) {
1263
			$times[] = $datetime->start_date_and_time();
1264
		}
1265
		return $this->name() . ' @ ' . implode( ', ', $times ) . ' for ' . $this->pretty_price();
1266
	}
1267
1268
1269
1270
    /**
1271
     * Gets name
1272
     *
1273
     * @return string
1274
     * @throws \EE_Error
1275
     */
1276
	public function name() {
1277
		return $this->get( 'TKT_name' );
1278
	}
1279
1280
1281
1282
    /**
1283
     * Gets price
1284
     *
1285
     * @return float
1286
     * @throws \EE_Error
1287
     */
1288
	public function price() {
1289
		return $this->get( 'TKT_price' );
1290
	}
1291
1292
1293
1294
    /**
1295
     * Gets all the registrations for this ticket
1296
     *
1297
     * @param array $query_params like EEM_Base::get_all's
1298
     * @return EE_Registration[]|EE_Base_Class[]
1299
     * @throws \EE_Error
1300
     */
1301
	public function registrations( $query_params = array() ) {
1302
		return $this->get_many_related( 'Registration', $query_params );
1303
	}
1304
1305
1306
1307
    /**
1308
     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1309
     * into account
1310
     *
1311
     * @return int
1312
     * @throws \EE_Error
1313
     */
1314 View Code Duplication
	public function update_tickets_sold() {
1315
        $count_regs_for_this_ticket = $this->count_registrations(
1316
            array(
1317
                array(
1318
                    'STS_ID'      => EEM_Registration::status_id_approved,
1319
                    'REG_deleted' => 0,
1320
                ),
1321
            )
1322
        );
1323
        $sold = $this->sold();
1324
        if ($count_regs_for_this_ticket > $sold) {
1325
            $this->increase_sold($count_regs_for_this_ticket - $sold);
1326
            $this->save();
1327
        } else if ($count_regs_for_this_ticket < $sold) {
1328
            $this->decrease_sold($count_regs_for_this_ticket - $sold);
1329
            $this->save();
1330
        }
1331
		return $count_regs_for_this_ticket;
1332
	}
1333
1334
1335
1336
	/**
1337
	 * Counts the registrations for this ticket
1338
	 * @param array $query_params like EEM_Base::get_all's
1339
	 * @return int
1340
	 */
1341
	public function count_registrations( $query_params = array() ) {
1342
		return $this->count_related('Registration', $query_params);
1343
	}
1344
1345
1346
1347
	/**
1348
	 * Implementation for EEI_Has_Icon interface method.
1349
	 * @see EEI_Visual_Representation for comments
1350
	 * @return string
1351
	 */
1352
	public function get_icon() {
1353
		return '<span class="dashicons dashicons-tickets-alt"></span>';
1354
	}
1355
1356
1357
1358
    /**
1359
     * Implementation of the EEI_Event_Relation interface method
1360
     *
1361
     * @see EEI_Event_Relation for comments
1362
     * @return EE_Event
1363
     * @throws \EE_Error
1364
     * @throws UnexpectedEntityException
1365
     */
1366
	public function get_related_event() {
1367
		//get one datetime to use for getting the event
1368
		$datetime = $this->first_datetime();
1369
		if ( ! $datetime instanceof \EE_Datetime ) {
1370
			throw new UnexpectedEntityException(
1371
				$datetime,
1372
                'EE_Datetime',
1373
				sprintf(
1374
					__( 'The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1375
					$this->name()
1376
				)
1377
			);
1378
		}
1379
		$event = $datetime->event();
1380
		if ( ! $event instanceof \EE_Event ) {
1381
			throw new UnexpectedEntityException(
1382
				$event,
1383
                'EE_Event',
1384
				sprintf(
1385
					__( 'The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1386
					$this->name()
1387
				)
1388
			);
1389
		}
1390
		return $event;
1391
	}
1392
1393
1394
1395
    /**
1396
     * Implementation of the EEI_Event_Relation interface method
1397
     *
1398
     * @see EEI_Event_Relation for comments
1399
     * @return string
1400
     * @throws UnexpectedEntityException
1401
     * @throws \EE_Error
1402
     */
1403
	public function get_event_name() {
1404
		$event = $this->get_related_event();
1405
		return $event instanceof EE_Event ? $event->name() : '';
1406
	}
1407
1408
1409
1410
    /**
1411
     * Implementation of the EEI_Event_Relation interface method
1412
     *
1413
     * @see EEI_Event_Relation for comments
1414
     * @return int
1415
     * @throws UnexpectedEntityException
1416
     * @throws \EE_Error
1417
     */
1418
	public function get_event_ID() {
1419
		$event = $this->get_related_event();
1420
		return $event instanceof EE_Event ? $event->ID() : 0;
1421
	}
1422
1423
1424
    /**
1425
     * This simply returns whether a ticket can be permanently deleted or not.
1426
     * The criteria for determining this is whether the ticket has any related registrations.
1427
     * If there are none then it can be permanently deleted.
1428
     *
1429
     * @return bool
1430
     */
1431
	public function is_permanently_deleteable() {
1432
	    return $this->count_registrations() === 0;
1433
    }
1434
} //end EE_Ticket class
1435