Completed
Branch CASC/base (79f9d1)
by
unknown
16:50 queued 08:50
created

EE_Registration::ticket()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\domain\entities\contexts\ContextInterface;
4
use EventEspresso\core\exceptions\EntityNotFoundException;
5
use EventEspresso\core\exceptions\InvalidDataTypeException;
6
use EventEspresso\core\exceptions\InvalidInterfaceException;
7
use EventEspresso\core\exceptions\UnexpectedEntityException;
8
9
/**
10
 * EE_Registration class
11
 *
12
 * @package               Event Espresso
13
 * @subpackage            includes/classes/EE_Registration.class.php
14
 * @author                Mike Nelson, Brent Christensen
15
 */
16
class EE_Registration extends EE_Soft_Delete_Base_Class implements EEI_Registration, EEI_Admin_Links
17
{
18
19
20
    /**
21
     * Used to reference when a registration has never been checked in.
22
     *
23
     * @deprecated use \EE_Checkin::status_checked_never instead
24
     * @type int
25
     */
26
    const checkin_status_never = 2;
27
28
    /**
29
     * Used to reference when a registration has been checked in.
30
     *
31
     * @deprecated use \EE_Checkin::status_checked_in instead
32
     * @type int
33
     */
34
    const checkin_status_in = 1;
35
36
37
    /**
38
     * Used to reference when a registration has been checked out.
39
     *
40
     * @deprecated use \EE_Checkin::status_checked_out instead
41
     * @type int
42
     */
43
    const checkin_status_out = 0;
44
45
46
    /**
47
     * extra meta key for tracking reg status os trashed registrations
48
     *
49
     * @type string
50
     */
51
    const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
52
53
54
    /**
55
     * extra meta key for tracking if registration has reserved ticket
56
     *
57
     * @type string
58
     */
59
    const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
60
61
62
    /**
63
     * @param array  $props_n_values          incoming values
64
     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
65
     *                                        used.)
66
     * @param array  $date_formats            incoming date_formats in an array where the first value is the
67
     *                                        date_format and the second value is the time format
68
     * @return EE_Registration
69
     * @throws EE_Error
70
     */
71
    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
72
    {
73
        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
74
        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
75
    }
76
77
78
    /**
79
     * @param array  $props_n_values  incoming values from the database
80
     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
81
     *                                the website will be used.
82
     * @return EE_Registration
83
     */
84
    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
85
    {
86
        return new self($props_n_values, true, $timezone);
87
    }
88
89
90
    /**
91
     *        Set Event ID
92
     *
93
     * @param        int $EVT_ID Event ID
94
     * @throws EE_Error
95
     * @throws RuntimeException
96
     */
97
    public function set_event($EVT_ID = 0)
98
    {
99
        $this->set('EVT_ID', $EVT_ID);
100
    }
101
102
103
    /**
104
     * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
105
     * be routed to internal methods
106
     *
107
     * @param string $field_name
108
     * @param mixed  $field_value
109
     * @param bool   $use_default
110
     * @throws EE_Error
111
     * @throws EntityNotFoundException
112
     * @throws InvalidArgumentException
113
     * @throws InvalidDataTypeException
114
     * @throws InvalidInterfaceException
115
     * @throws ReflectionException
116
     * @throws RuntimeException
117
     */
118
    public function set($field_name, $field_value, $use_default = false)
119
    {
120
        switch ($field_name) {
121
            case 'REG_code':
122
                if (! empty($field_value) && $this->reg_code() === null) {
123
                    $this->set_reg_code($field_value, $use_default);
124
                }
125
                break;
126
            case 'STS_ID':
127
                $this->set_status($field_value, $use_default);
128
                break;
129
            default:
130
                parent::set($field_name, $field_value, $use_default);
131
        }
132
    }
133
134
135
    /**
136
     * Set Status ID
137
     * updates the registration status and ALSO...
138
     * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
139
     * calls release_registration_space() if the reg status changes FROM approved to any other reg status
140
     *
141
     * @param string                $new_STS_ID
142
     * @param boolean               $use_default
143
     * @param ContextInterface|null $context
144
     * @return bool
145
     * @throws DomainException
146
     * @throws EE_Error
147
     * @throws EntityNotFoundException
148
     * @throws InvalidArgumentException
149
     * @throws InvalidDataTypeException
150
     * @throws InvalidInterfaceException
151
     * @throws ReflectionException
152
     * @throws RuntimeException
153
     * @throws UnexpectedEntityException
154
     */
155
    public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
156
    {
157
        // get current REG_Status
158
        $old_STS_ID = $this->status_ID();
159
        // if status has changed
160
        if ($old_STS_ID !== $new_STS_ID // and that status has actually changed
161
            && ! empty($old_STS_ID) // and that old status is actually set
162
            && ! empty($new_STS_ID) // as well as the new status
163
            && $this->ID() // ensure registration is in the db
164
        ) {
165
            // update internal status first
166
            parent::set('STS_ID', $new_STS_ID, $use_default);
167
            // THEN handle other changes that occur when reg status changes
168
            // TO approved
169
            if ($new_STS_ID === EEM_Registration::status_id_approved) {
170
                // reserve a space by incrementing ticket and datetime sold values
171
                $this->reserveRegistrationSpace();
172
                do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
173
                // OR FROM  approved
174
            } elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
175
                // release a space by decrementing ticket and datetime sold values
176
                $this->releaseRegistrationSpace();
177
                do_action(
178
                    'AHEE__EE_Registration__set_status__from_approved',
179
                    $this,
180
                    $old_STS_ID,
181
                    $new_STS_ID,
182
                    $context
183
                );
184
            }
185
            // update status
186
            parent::set('STS_ID', $new_STS_ID, $use_default);
187
            $this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
188
            if ($this->statusChangeUpdatesTransaction($context)) {
189
                $this->updateTransactionAfterStatusChange();
190
            }
191
            do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
192
            return true;
193
        }
194
        // even though the old value matches the new value, it's still good to
195
        // allow the parent set method to have a say
196
        parent::set('STS_ID', $new_STS_ID, $use_default);
197
        return true;
198
    }
199
200
201
    /**
202
     * update REGs and TXN when cancelled or declined registrations involved
203
     *
204
     * @param string                $new_STS_ID
205
     * @param string                $old_STS_ID
206
     * @param ContextInterface|null $context
207
     * @throws EE_Error
208
     * @throws InvalidArgumentException
209
     * @throws InvalidDataTypeException
210
     * @throws InvalidInterfaceException
211
     * @throws ReflectionException
212
     * @throws RuntimeException
213
     */
214
    private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
215
    {
216
        // these reg statuses should not be considered in any calculations involving monies owing
217
        $closed_reg_statuses = EEM_Registration::closed_reg_statuses();
218
        // true if registration has been cancelled or declined
219
        $this->updateIfCanceled(
220
            $closed_reg_statuses,
221
            $new_STS_ID,
222
            $old_STS_ID,
223
            $context
224
        );
225
        $this->updateIfReinstated(
226
            $closed_reg_statuses,
227
            $new_STS_ID,
228
            $old_STS_ID,
229
            $context
230
        );
231
    }
232
233
234
    /**
235
     * update REGs and TXN when cancelled or declined registrations involved
236
     *
237
     * @param array                 $closed_reg_statuses
238
     * @param string                $new_STS_ID
239
     * @param string                $old_STS_ID
240
     * @param ContextInterface|null $context
241
     * @throws EE_Error
242
     * @throws InvalidArgumentException
243
     * @throws InvalidDataTypeException
244
     * @throws InvalidInterfaceException
245
     * @throws ReflectionException
246
     * @throws RuntimeException
247
     */
248 View Code Duplication
    private function updateIfCanceled(
249
        array $closed_reg_statuses,
250
        $new_STS_ID,
251
        $old_STS_ID,
252
        ContextInterface $context = null
253
    ) {
254
        // true if registration has been cancelled or declined
255
        if (in_array($new_STS_ID, $closed_reg_statuses, true)
256
            && ! in_array($old_STS_ID, $closed_reg_statuses, true)
257
        ) {
258
            /** @type EE_Registration_Processor $registration_processor */
259
            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
260
            /** @type EE_Transaction_Processor $transaction_processor */
261
            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
262
            // cancelled or declined registration
263
            $registration_processor->update_registration_after_being_canceled_or_declined(
264
                $this,
265
                $closed_reg_statuses
266
            );
267
            $transaction_processor->update_transaction_after_canceled_or_declined_registration(
268
                $this,
269
                $closed_reg_statuses,
270
                false
271
            );
272
            do_action(
273
                'AHEE__EE_Registration__set_status__canceled_or_declined',
274
                $this,
275
                $old_STS_ID,
276
                $new_STS_ID,
277
                $context
278
            );
279
            return;
280
        }
281
    }
282
283
284
    /**
285
     * update REGs and TXN when cancelled or declined registrations involved
286
     *
287
     * @param array                 $closed_reg_statuses
288
     * @param string                $new_STS_ID
289
     * @param string                $old_STS_ID
290
     * @param ContextInterface|null $context
291
     * @throws EE_Error
292
     * @throws InvalidArgumentException
293
     * @throws InvalidDataTypeException
294
     * @throws InvalidInterfaceException
295
     * @throws ReflectionException
296
     */
297 View Code Duplication
    private function updateIfReinstated(
298
        array $closed_reg_statuses,
299
        $new_STS_ID,
300
        $old_STS_ID,
301
        ContextInterface $context = null
302
    ) {
303
        // true if reinstating cancelled or declined registration
304
        if (in_array($old_STS_ID, $closed_reg_statuses, true)
305
            && ! in_array($new_STS_ID, $closed_reg_statuses, true)
306
        ) {
307
            /** @type EE_Registration_Processor $registration_processor */
308
            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
309
            /** @type EE_Transaction_Processor $transaction_processor */
310
            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
311
            // reinstating cancelled or declined registration
312
            $registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
313
                $this,
314
                $closed_reg_statuses
315
            );
316
            $transaction_processor->update_transaction_after_reinstating_canceled_registration(
317
                $this,
318
                $closed_reg_statuses,
319
                false
320
            );
321
            do_action(
322
                'AHEE__EE_Registration__set_status__after_reinstated',
323
                $this,
324
                $old_STS_ID,
325
                $new_STS_ID,
326
                $context
327
            );
328
        }
329
    }
330
331
332
    /**
333
     * @param ContextInterface|null $context
334
     * @return bool
335
     */
336
    private function statusChangeUpdatesTransaction(ContextInterface $context = null)
337
    {
338
        $contexts_that_do_not_update_transaction = (array) apply_filters(
339
            'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
340
            array('spco_reg_step_attendee_information_process_registrations'),
341
            $context,
342
            $this
343
        );
344
        return ! (
345
            $context instanceof ContextInterface
346
            && in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
347
        );
348
    }
349
350
351
    /**
352
     * @throws EE_Error
353
     * @throws EntityNotFoundException
354
     * @throws InvalidArgumentException
355
     * @throws InvalidDataTypeException
356
     * @throws InvalidInterfaceException
357
     * @throws ReflectionException
358
     * @throws RuntimeException
359
     */
360
    private function updateTransactionAfterStatusChange()
361
    {
362
        /** @type EE_Transaction_Payments $transaction_payments */
363
        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
364
        $transaction_payments->recalculate_transaction_total($this->transaction(), false);
365
        $this->transaction()->update_status_based_on_total_paid(true);
366
    }
367
368
369
    /**
370
     *        get Status ID
371
     */
372
    public function status_ID()
373
    {
374
        return $this->get('STS_ID');
375
    }
376
377
378
    /**
379
     * Gets the ticket this registration is for
380
     *
381
     * @param boolean $include_archived whether to include archived tickets or not.
382
     *
383
     * @return EE_Ticket|EE_Base_Class
384
     * @throws EE_Error
385
     */
386
    public function ticket($include_archived = true)
387
    {
388
        $query_params = array();
389
        if ($include_archived) {
390
            $query_params['default_where_conditions'] = 'none';
391
        }
392
        return $this->get_first_related('Ticket', $query_params);
393
    }
394
395
396
    /**
397
     * Gets the event this registration is for
398
     *
399
     * @return EE_Event
400
     * @throws EE_Error
401
     * @throws EntityNotFoundException
402
     */
403
    public function event()
404
    {
405
        $event = $this->get_first_related('Event');
406
        if (! $event instanceof \EE_Event) {
407
            throw new EntityNotFoundException('Event ID', $this->event_ID());
408
        }
409
        return $event;
410
    }
411
412
413
    /**
414
     * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
415
     * with the author of the event this registration is for.
416
     *
417
     * @since 4.5.0
418
     * @return int
419
     * @throws EE_Error
420
     * @throws EntityNotFoundException
421
     */
422
    public function wp_user()
423
    {
424
        $event = $this->event();
425
        if ($event instanceof EE_Event) {
426
            return $event->wp_user();
427
        }
428
        return 0;
429
    }
430
431
432
    /**
433
     * increments this registration's related ticket sold and corresponding datetime sold values
434
     *
435
     * @return void
436
     * @throws DomainException
437
     * @throws EE_Error
438
     * @throws EntityNotFoundException
439
     * @throws InvalidArgumentException
440
     * @throws InvalidDataTypeException
441
     * @throws InvalidInterfaceException
442
     * @throws ReflectionException
443
     * @throws UnexpectedEntityException
444
     */
445
    private function reserveRegistrationSpace()
446
    {
447
        // reserved ticket and datetime counts will be decremented as sold counts are incremented
448
        // so stop tracking that this reg has a ticket reserved
449
        $this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
450
        $ticket = $this->ticket();
451
        $ticket->increaseSold();
452
        // possibly set event status to sold out
453
        $this->event()->perform_sold_out_status_check();
454
    }
455
456
457
    /**
458
     * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
459
     *
460
     * @return void
461
     * @throws DomainException
462
     * @throws EE_Error
463
     * @throws EntityNotFoundException
464
     * @throws InvalidArgumentException
465
     * @throws InvalidDataTypeException
466
     * @throws InvalidInterfaceException
467
     * @throws ReflectionException
468
     * @throws UnexpectedEntityException
469
     */
470
    private function releaseRegistrationSpace()
471
    {
472
        $ticket = $this->ticket();
473
        $ticket->decreaseSold();
474
        // possibly change event status from sold out back to previous status
475
        $this->event()->perform_sold_out_status_check();
476
    }
477
478
479
    /**
480
     * tracks this registration's ticket reservation in extra meta
481
     * and can increment related ticket reserved and corresponding datetime reserved values
482
     *
483
     * @param bool $update_ticket if true, will increment ticket and datetime reserved count
484
     * @return void
485
     * @throws EE_Error
486
     * @throws InvalidArgumentException
487
     * @throws InvalidDataTypeException
488
     * @throws InvalidInterfaceException
489
     * @throws ReflectionException
490
     */
491 View Code Duplication
    public function reserve_ticket($update_ticket = false, $source = 'unknown')
492
    {
493
        // only reserve ticket if space is not currently reserved
494
        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
495
            $this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
496
            // IMPORTANT !!!
497
            // although checking $update_ticket first would be more efficient,
498
            // we NEED to ALWAYS call update_extra_meta(), which is why that is done first
499
            if ($this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true)
500
                && $update_ticket
501
            ) {
502
                $ticket = $this->ticket();
503
                $ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
504
                $ticket->save();
505
            }
506
        }
507
    }
508
509
510
    /**
511
     * stops tracking this registration's ticket reservation in extra meta
512
     * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
513
     *
514
     * @param bool $update_ticket if true, will decrement ticket and datetime reserved count
515
     * @return void
516
     * @throws EE_Error
517
     * @throws InvalidArgumentException
518
     * @throws InvalidDataTypeException
519
     * @throws InvalidInterfaceException
520
     * @throws ReflectionException
521
     */
522 View Code Duplication
    public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
523
    {
524
        // only release ticket if space is currently reserved
525
        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
526
            $this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
527
            // IMPORTANT !!!
528
            // although checking $update_ticket first would be more efficient,
529
            // we NEED to ALWAYS call update_extra_meta(), which is why that is done first
530
            if ($this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false)
531
                && $update_ticket
532
            ) {
533
                $ticket = $this->ticket();
534
                $ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
535
            }
536
        }
537
    }
538
539
540
    /**
541
     * Set Attendee ID
542
     *
543
     * @param        int $ATT_ID Attendee ID
544
     * @throws EE_Error
545
     * @throws RuntimeException
546
     */
547
    public function set_attendee_id($ATT_ID = 0)
548
    {
549
        $this->set('ATT_ID', $ATT_ID);
550
    }
551
552
553
    /**
554
     *        Set Transaction ID
555
     *
556
     * @param        int $TXN_ID Transaction ID
557
     * @throws EE_Error
558
     * @throws RuntimeException
559
     */
560
    public function set_transaction_id($TXN_ID = 0)
561
    {
562
        $this->set('TXN_ID', $TXN_ID);
563
    }
564
565
566
    /**
567
     *        Set Session
568
     *
569
     * @param    string $REG_session PHP Session ID
570
     * @throws EE_Error
571
     * @throws RuntimeException
572
     */
573
    public function set_session($REG_session = '')
574
    {
575
        $this->set('REG_session', $REG_session);
576
    }
577
578
579
    /**
580
     *        Set Registration URL Link
581
     *
582
     * @param    string $REG_url_link Registration URL Link
583
     * @throws EE_Error
584
     * @throws RuntimeException
585
     */
586
    public function set_reg_url_link($REG_url_link = '')
587
    {
588
        $this->set('REG_url_link', $REG_url_link);
589
    }
590
591
592
    /**
593
     *        Set Attendee Counter
594
     *
595
     * @param        int $REG_count Primary Attendee
596
     * @throws EE_Error
597
     * @throws RuntimeException
598
     */
599
    public function set_count($REG_count = 1)
600
    {
601
        $this->set('REG_count', $REG_count);
602
    }
603
604
605
    /**
606
     *        Set Group Size
607
     *
608
     * @param        boolean $REG_group_size Group Registration
609
     * @throws EE_Error
610
     * @throws RuntimeException
611
     */
612
    public function set_group_size($REG_group_size = false)
613
    {
614
        $this->set('REG_group_size', $REG_group_size);
615
    }
616
617
618
    /**
619
     *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
620
     *    EEM_Registration::status_id_not_approved
621
     *
622
     * @return        boolean
623
     */
624
    public function is_not_approved()
625
    {
626
        return $this->status_ID() == EEM_Registration::status_id_not_approved ? true : false;
627
    }
628
629
630
    /**
631
     *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
632
     *    EEM_Registration::status_id_pending_payment
633
     *
634
     * @return        boolean
635
     */
636
    public function is_pending_payment()
637
    {
638
        return $this->status_ID() == EEM_Registration::status_id_pending_payment ? true : false;
639
    }
640
641
642
    /**
643
     *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
644
     *
645
     * @return        boolean
646
     */
647
    public function is_approved()
648
    {
649
        return $this->status_ID() == EEM_Registration::status_id_approved ? true : false;
650
    }
651
652
653
    /**
654
     *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
655
     *
656
     * @return        boolean
657
     */
658
    public function is_cancelled()
659
    {
660
        return $this->status_ID() == EEM_Registration::status_id_cancelled ? true : false;
661
    }
662
663
664
    /**
665
     *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
666
     *
667
     * @return        boolean
668
     */
669
    public function is_declined()
670
    {
671
        return $this->status_ID() == EEM_Registration::status_id_declined ? true : false;
672
    }
673
674
675
    /**
676
     *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
677
     *    EEM_Registration::status_id_incomplete
678
     *
679
     * @return        boolean
680
     */
681
    public function is_incomplete()
682
    {
683
        return $this->status_ID() == EEM_Registration::status_id_incomplete ? true : false;
684
    }
685
686
687
    /**
688
     *        Set Registration Date
689
     *
690
     * @param        mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
691
     *                                                 Date
692
     * @throws EE_Error
693
     * @throws RuntimeException
694
     */
695
    public function set_reg_date($REG_date = false)
696
    {
697
        $this->set('REG_date', $REG_date);
698
    }
699
700
701
    /**
702
     *    Set final price owing for this registration after all ticket/price modifications
703
     *
704
     * @access    public
705
     * @param    float $REG_final_price
706
     * @throws EE_Error
707
     * @throws RuntimeException
708
     */
709
    public function set_final_price($REG_final_price = 0.00)
710
    {
711
        $this->set('REG_final_price', $REG_final_price);
712
    }
713
714
715
    /**
716
     *    Set amount paid towards this registration's final price
717
     *
718
     * @access    public
719
     * @param    float $REG_paid
720
     * @throws EE_Error
721
     * @throws RuntimeException
722
     */
723
    public function set_paid($REG_paid = 0.00)
724
    {
725
        $this->set('REG_paid', $REG_paid);
726
    }
727
728
729
    /**
730
     *        Attendee Is Going
731
     *
732
     * @param        boolean $REG_att_is_going Attendee Is Going
733
     * @throws EE_Error
734
     * @throws RuntimeException
735
     */
736
    public function set_att_is_going($REG_att_is_going = false)
737
    {
738
        $this->set('REG_att_is_going', $REG_att_is_going);
739
    }
740
741
742
    /**
743
     * Gets the related attendee
744
     *
745
     * @return EE_Attendee
746
     * @throws EE_Error
747
     */
748
    public function attendee()
749
    {
750
        return $this->get_first_related('Attendee');
751
    }
752
753
    /**
754
     * Gets the name of the attendee.
755
     * @since $VID:$
756
     * @param bool $apply_html_entities set to true if you want to use HTML entities.
757
     * @return string
758
     * @throws EE_Error
759
     * @throws InvalidArgumentException
760
     * @throws InvalidDataTypeException
761
     * @throws InvalidInterfaceException
762
     * @throws ReflectionException
763
     */
764
    public function attendeeName($apply_html_entities = false)
765
    {
766
        $attendee = $this->get_first_related('Attendee');
767
        if ($attendee instanceof EE_Attendee) {
768
            $attendee_name = $attendee->full_name($apply_html_entities);
769
        } else {
770
            $attendee_name = esc_html__('Unknown', 'event_espresso');
771
        }
772
        return $attendee_name;
773
    }
774
775
776
    /**
777
     *        get Event ID
778
     */
779
    public function event_ID()
780
    {
781
        return $this->get('EVT_ID');
782
    }
783
784
785
    /**
786
     *        get Event ID
787
     */
788
    public function event_name()
789
    {
790
        $event = $this->event_obj();
791
        if ($event) {
792
            return $event->name();
793
        } else {
794
            return null;
795
        }
796
    }
797
798
799
    /**
800
     * Fetches the event this registration is for
801
     *
802
     * @return EE_Event
803
     * @throws EE_Error
804
     */
805
    public function event_obj()
806
    {
807
        return $this->get_first_related('Event');
808
    }
809
810
811
    /**
812
     *        get Attendee ID
813
     */
814
    public function attendee_ID()
815
    {
816
        return $this->get('ATT_ID');
817
    }
818
819
820
    /**
821
     *        get PHP Session ID
822
     */
823
    public function session_ID()
824
    {
825
        return $this->get('REG_session');
826
    }
827
828
829
    /**
830
     * Gets the string which represents the URL trigger for the receipt template in the message template system.
831
     *
832
     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
833
     * @return string
834
     */
835
    public function receipt_url($messenger = 'html')
836
    {
837
838
        /**
839
         * The below will be deprecated one version after this.  We check first if there is a custom receipt template
840
         * already in use on old system.  If there is then we just return the standard url for it.
841
         *
842
         * @since 4.5.0
843
         */
844
        $template_relative_path = 'modules/gateways/Invoice/lib/templates/receipt_body.template.php';
845
        $has_custom = EEH_Template::locate_template(
846
            $template_relative_path,
847
            array(),
848
            true,
849
            true,
850
            true
851
        );
852
853
        if ($has_custom) {
854
            return add_query_arg(array('receipt' => 'true'), $this->invoice_url('launch'));
855
        }
856
        return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
857
    }
858
859
860
    /**
861
     * Gets the string which represents the URL trigger for the invoice template in the message template system.
862
     *
863
     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
864
     * @return string
865
     * @throws EE_Error
866
     */
867
    public function invoice_url($messenger = 'html')
868
    {
869
        /**
870
         * The below will be deprecated one version after this.  We check first if there is a custom invoice template
871
         * already in use on old system.  If there is then we just return the standard url for it.
872
         *
873
         * @since 4.5.0
874
         */
875
        $template_relative_path = 'modules/gateways/Invoice/lib/templates/invoice_body.template.php';
876
        $has_custom = EEH_Template::locate_template(
877
            $template_relative_path,
878
            array(),
879
            true,
880
            true,
881
            true
882
        );
883
884
        if ($has_custom) {
885
            if ($messenger == 'html') {
886
                return $this->invoice_url('launch');
887
            }
888
            $route = $messenger == 'download' || $messenger == 'pdf' ? 'download_invoice' : 'launch_invoice';
889
890
            $query_args = array('ee' => $route, 'id' => $this->reg_url_link());
891
            if ($messenger == 'html') {
892
                $query_args['html'] = true;
893
            }
894
            return add_query_arg($query_args, get_permalink(EE_Registry::instance()->CFG->core->thank_you_page_id));
895
        }
896
        return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
897
    }
898
899
900
    /**
901
     * get Registration URL Link
902
     *
903
     * @access public
904
     * @return string
905
     * @throws EE_Error
906
     */
907
    public function reg_url_link()
908
    {
909
        return (string) $this->get('REG_url_link');
910
    }
911
912
913
    /**
914
     * Echoes out invoice_url()
915
     *
916
     * @param string $type 'download','launch', or 'html' (default is 'launch')
917
     * @return void
918
     * @throws EE_Error
919
     */
920
    public function e_invoice_url($type = 'launch')
921
    {
922
        echo $this->invoice_url($type);
923
    }
924
925
926
    /**
927
     * Echoes out payment_overview_url
928
     */
929
    public function e_payment_overview_url()
930
    {
931
        echo $this->payment_overview_url();
932
    }
933
934
935
    /**
936
     * Gets the URL for the checkout payment options reg step
937
     * with this registration's REG_url_link added as a query parameter
938
     *
939
     * @param bool $clear_session Set to true when you want to clear the session on revisiting the
940
     *                            payment overview url.
941
     * @return string
942
     * @throws InvalidInterfaceException
943
     * @throws InvalidDataTypeException
944
     * @throws EE_Error
945
     * @throws InvalidArgumentException
946
     */
947 View Code Duplication
    public function payment_overview_url($clear_session = false)
948
    {
949
        return add_query_arg(
950
            (array) apply_filters(
951
                'FHEE__EE_Registration__payment_overview_url__query_args',
952
                array(
953
                    'e_reg_url_link' => $this->reg_url_link(),
954
                    'step'           => 'payment_options',
955
                    'revisit'        => true,
956
                    'clear_session'  => (bool) $clear_session,
957
                ),
958
                $this
959
            ),
960
            EE_Registry::instance()->CFG->core->reg_page_url()
961
        );
962
    }
963
964
965
    /**
966
     * Gets the URL for the checkout attendee information reg step
967
     * with this registration's REG_url_link added as a query parameter
968
     *
969
     * @return string
970
     * @throws InvalidInterfaceException
971
     * @throws InvalidDataTypeException
972
     * @throws EE_Error
973
     * @throws InvalidArgumentException
974
     */
975 View Code Duplication
    public function edit_attendee_information_url()
976
    {
977
        return add_query_arg(
978
            (array) apply_filters(
979
                'FHEE__EE_Registration__edit_attendee_information_url__query_args',
980
                array(
981
                    'e_reg_url_link' => $this->reg_url_link(),
982
                    'step'           => 'attendee_information',
983
                    'revisit'        => true,
984
                ),
985
                $this
986
            ),
987
            EE_Registry::instance()->CFG->core->reg_page_url()
988
        );
989
    }
990
991
992
    /**
993
     * Simply generates and returns the appropriate admin_url link to edit this registration
994
     *
995
     * @return string
996
     * @throws EE_Error
997
     */
998
    public function get_admin_edit_url()
999
    {
1000
        return EEH_URL::add_query_args_and_nonce(
1001
            array(
1002
                'page'    => 'espresso_registrations',
1003
                'action'  => 'view_registration',
1004
                '_REG_ID' => $this->ID(),
1005
            ),
1006
            admin_url('admin.php')
1007
        );
1008
    }
1009
1010
1011
    /**
1012
     *    is_primary_registrant?
1013
     */
1014
    public function is_primary_registrant()
1015
    {
1016
        return $this->get('REG_count') === 1 ? true : false;
1017
    }
1018
1019
1020
    /**
1021
     * This returns the primary registration object for this registration group (which may be this object).
1022
     *
1023
     * @return EE_Registration
1024
     * @throws EE_Error
1025
     */
1026
    public function get_primary_registration()
1027
    {
1028
        if ($this->is_primary_registrant()) {
1029
            return $this;
1030
        }
1031
1032
        // k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1033
        /** @var EE_Registration $primary_registrant */
1034
        $primary_registrant = EEM_Registration::instance()->get_one(
1035
            array(
1036
                array(
1037
                    'TXN_ID'    => $this->transaction_ID(),
1038
                    'REG_count' => 1,
1039
                ),
1040
            )
1041
        );
1042
        return $primary_registrant;
1043
    }
1044
1045
1046
    /**
1047
     *        get  Attendee Number
1048
     *
1049
     * @access        public
1050
     */
1051
    public function count()
1052
    {
1053
        return $this->get('REG_count');
1054
    }
1055
1056
1057
    /**
1058
     *        get Group Size
1059
     */
1060
    public function group_size()
1061
    {
1062
        return $this->get('REG_group_size');
1063
    }
1064
1065
1066
    /**
1067
     *        get Registration Date
1068
     */
1069
    public function date()
1070
    {
1071
        return $this->get('REG_date');
1072
    }
1073
1074
1075
    /**
1076
     * gets a pretty date
1077
     *
1078
     * @param string $date_format
1079
     * @param string $time_format
1080
     * @return string
1081
     * @throws EE_Error
1082
     */
1083
    public function pretty_date($date_format = null, $time_format = null)
1084
    {
1085
        return $this->get_datetime('REG_date', $date_format, $time_format);
1086
    }
1087
1088
1089
    /**
1090
     * final_price
1091
     * the registration's share of the transaction total, so that the
1092
     * sum of all the transaction's REG_final_prices equal the transaction's total
1093
     *
1094
     * @return float
1095
     * @throws EE_Error
1096
     */
1097
    public function final_price()
1098
    {
1099
        return $this->get('REG_final_price');
1100
    }
1101
1102
1103
    /**
1104
     * pretty_final_price
1105
     *  final price as formatted string, with correct decimal places and currency symbol
1106
     *
1107
     * @return string
1108
     * @throws EE_Error
1109
     */
1110
    public function pretty_final_price()
1111
    {
1112
        return $this->get_pretty('REG_final_price');
1113
    }
1114
1115
1116
    /**
1117
     * get paid (yeah)
1118
     *
1119
     * @return float
1120
     * @throws EE_Error
1121
     */
1122
    public function paid()
1123
    {
1124
        return $this->get('REG_paid');
1125
    }
1126
1127
1128
    /**
1129
     * pretty_paid
1130
     *
1131
     * @return float
1132
     * @throws EE_Error
1133
     */
1134
    public function pretty_paid()
1135
    {
1136
        return $this->get_pretty('REG_paid');
1137
    }
1138
1139
1140
    /**
1141
     * owes_monies_and_can_pay
1142
     * whether or not this registration has monies owing and it's' status allows payment
1143
     *
1144
     * @param array $requires_payment
1145
     * @return bool
1146
     * @throws EE_Error
1147
     */
1148
    public function owes_monies_and_can_pay($requires_payment = array())
1149
    {
1150
        // these reg statuses require payment (if event is not free)
1151
        $requires_payment = ! empty($requires_payment)
1152
            ? $requires_payment
1153
            : EEM_Registration::reg_statuses_that_allow_payment();
1154
        if (in_array($this->status_ID(), $requires_payment) &&
1155
            $this->final_price() != 0 &&
1156
            $this->final_price() != $this->paid()
1157
        ) {
1158
            return true;
1159
        } else {
1160
            return false;
1161
        }
1162
    }
1163
1164
1165
    /**
1166
     * Prints out the return value of $this->pretty_status()
1167
     *
1168
     * @param bool $show_icons
1169
     * @return void
1170
     * @throws EE_Error
1171
     */
1172
    public function e_pretty_status($show_icons = false)
1173
    {
1174
        echo $this->pretty_status($show_icons);
1175
    }
1176
1177
1178
    /**
1179
     * Returns a nice version of the status for displaying to customers
1180
     *
1181
     * @param bool $show_icons
1182
     * @return string
1183
     * @throws EE_Error
1184
     */
1185
    public function pretty_status($show_icons = false)
1186
    {
1187
        $status = EEM_Status::instance()->localized_status(
1188
            array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
1189
            false,
1190
            'sentence'
1191
        );
1192
        $icon = '';
1193
        switch ($this->status_ID()) {
1194
            case EEM_Registration::status_id_approved:
1195
                $icon = $show_icons
1196
                    ? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1197
                    : '';
1198
                break;
1199
            case EEM_Registration::status_id_pending_payment:
1200
                $icon = $show_icons
1201
                    ? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1202
                    : '';
1203
                break;
1204
            case EEM_Registration::status_id_not_approved:
1205
                $icon = $show_icons
1206
                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1207
                    : '';
1208
                break;
1209
            case EEM_Registration::status_id_cancelled:
1210
                $icon = $show_icons
1211
                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1212
                    : '';
1213
                break;
1214
            case EEM_Registration::status_id_incomplete:
1215
                $icon = $show_icons
1216
                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1217
                    : '';
1218
                break;
1219
            case EEM_Registration::status_id_declined:
1220
                $icon = $show_icons
1221
                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1222
                    : '';
1223
                break;
1224
            case EEM_Registration::status_id_wait_list:
1225
                $icon = $show_icons
1226
                    ? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1227
                    : '';
1228
                break;
1229
        }
1230
        return $icon . $status[ $this->status_ID() ];
1231
    }
1232
1233
1234
    /**
1235
     *        get Attendee Is Going
1236
     */
1237
    public function att_is_going()
1238
    {
1239
        return $this->get('REG_att_is_going');
1240
    }
1241
1242
1243
    /**
1244
     * Gets related answers
1245
     *
1246
     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1247
     * @return EE_Answer[]
1248
     * @throws EE_Error
1249
     */
1250
    public function answers($query_params = null)
1251
    {
1252
        return $this->get_many_related('Answer', $query_params);
1253
    }
1254
1255
1256
    /**
1257
     * Gets the registration's answer value to the specified question
1258
     * (either the question's ID or a question object)
1259
     *
1260
     * @param EE_Question|int $question
1261
     * @param bool            $pretty_value
1262
     * @return array|string if pretty_value= true, the result will always be a string
1263
     * (because the answer might be an array of answer values, so passing pretty_value=true
1264
     * will convert it into some kind of string)
1265
     * @throws EE_Error
1266
     */
1267
    public function answer_value_to_question($question, $pretty_value = true)
1268
    {
1269
        $question_id = EEM_Question::instance()->ensure_is_ID($question);
1270
        return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1271
    }
1272
1273
1274
    /**
1275
     * question_groups
1276
     * returns an array of EE_Question_Group objects for this registration
1277
     *
1278
     * @return EE_Question_Group[]
1279
     * @throws EE_Error
1280
     * @throws InvalidArgumentException
1281
     * @throws InvalidDataTypeException
1282
     * @throws InvalidInterfaceException
1283
     * @throws ReflectionException
1284
     */
1285
    public function question_groups()
1286
    {
1287
        return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1288
    }
1289
1290
1291
    /**
1292
     * count_question_groups
1293
     * returns a count of the number of EE_Question_Group objects for this registration
1294
     *
1295
     * @return int
1296
     * @throws EE_Error
1297
     * @throws EntityNotFoundException
1298
     * @throws InvalidArgumentException
1299
     * @throws InvalidDataTypeException
1300
     * @throws InvalidInterfaceException
1301
     * @throws ReflectionException
1302
     */
1303
    public function count_question_groups()
1304
    {
1305
        return EEM_Event::instance()->count_related(
1306
            $this->event_ID(),
1307
            'Question_Group',
1308
            [
1309
                [
1310
                    'Event_Question_Group.'
1311
                    . EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1312
                ]
1313
            ]
1314
        );
1315
    }
1316
1317
1318
    /**
1319
     * Returns the registration date in the 'standard' string format
1320
     * (function may be improved in the future to allow for different formats and timezones)
1321
     *
1322
     * @return string
1323
     * @throws EE_Error
1324
     */
1325
    public function reg_date()
1326
    {
1327
        return $this->get_datetime('REG_date');
1328
    }
1329
1330
1331
    /**
1332
     * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1333
     * the ticket this registration purchased, or the datetime they have registered
1334
     * to attend)
1335
     *
1336
     * @return EE_Datetime_Ticket
1337
     * @throws EE_Error
1338
     */
1339
    public function datetime_ticket()
1340
    {
1341
        return $this->get_first_related('Datetime_Ticket');
1342
    }
1343
1344
1345
    /**
1346
     * Sets the registration's datetime_ticket.
1347
     *
1348
     * @param EE_Datetime_Ticket $datetime_ticket
1349
     * @return EE_Datetime_Ticket
1350
     * @throws EE_Error
1351
     */
1352
    public function set_datetime_ticket($datetime_ticket)
1353
    {
1354
        return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1355
    }
1356
1357
    /**
1358
     * Gets deleted
1359
     *
1360
     * @return bool
1361
     * @throws EE_Error
1362
     */
1363
    public function deleted()
1364
    {
1365
        return $this->get('REG_deleted');
1366
    }
1367
1368
    /**
1369
     * Sets deleted
1370
     *
1371
     * @param boolean $deleted
1372
     * @return bool
1373
     * @throws EE_Error
1374
     * @throws RuntimeException
1375
     */
1376
    public function set_deleted($deleted)
1377
    {
1378
        if ($deleted) {
1379
            $this->delete();
1380
        } else {
1381
            $this->restore();
1382
        }
1383
    }
1384
1385
1386
    /**
1387
     * Get the status object of this object
1388
     *
1389
     * @return EE_Status
1390
     * @throws EE_Error
1391
     */
1392
    public function status_obj()
1393
    {
1394
        return $this->get_first_related('Status');
1395
    }
1396
1397
1398
    /**
1399
     * Returns the number of times this registration has checked into any of the datetimes
1400
     * its available for
1401
     *
1402
     * @return int
1403
     * @throws EE_Error
1404
     */
1405
    public function count_checkins()
1406
    {
1407
        return $this->get_model()->count_related($this, 'Checkin');
1408
    }
1409
1410
1411
    /**
1412
     * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1413
     * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1414
     *
1415
     * @return int
1416
     * @throws EE_Error
1417
     */
1418
    public function count_checkins_not_checkedout()
1419
    {
1420
        return $this->get_model()->count_related($this, 'Checkin', array(array('CHK_in' => 1)));
1421
    }
1422
1423
1424
    /**
1425
     * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1426
     *
1427
     * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1428
     * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1429
     *                                          consider registration status as well as datetime access.
1430
     * @return bool
1431
     * @throws EE_Error
1432
     */
1433
    public function can_checkin($DTT_OR_ID, $check_approved = true)
1434
    {
1435
        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1436
1437
        // first check registration status
1438
        if (($check_approved && ! $this->is_approved()) || ! $DTT_ID) {
1439
            return false;
1440
        }
1441
        // is there a datetime ticket that matches this dtt_ID?
1442
        if (! (EEM_Datetime_Ticket::instance()->exists(
1443
            array(
1444
                array(
1445
                    'TKT_ID' => $this->get('TKT_ID'),
1446
                    'DTT_ID' => $DTT_ID,
1447
                ),
1448
            )
1449
        ))
1450
        ) {
1451
            return false;
1452
        }
1453
1454
        // final check is against TKT_uses
1455
        return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1456
    }
1457
1458
1459
    /**
1460
     * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1461
     * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1462
     * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1463
     * then return false.  Otherwise return true.
1464
     *
1465
     * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1466
     * @return bool true means can checkin.  false means cannot checkin.
1467
     * @throws EE_Error
1468
     */
1469
    public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1470
    {
1471
        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1472
1473
        if (! $DTT_ID) {
1474
            return false;
1475
        }
1476
1477
        $max_uses = $this->ticket() instanceof EE_Ticket ? $this->ticket()->uses() : EE_INF;
1478
1479
        // if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1480
        // check-in or not.
1481
        if (! $max_uses || $max_uses === EE_INF) {
1482
            return true;
1483
        }
1484
1485
        // does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1486
        // go ahead and toggle.
1487
        if (EEM_Checkin::instance()->exists(array(array('REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID)))) {
1488
            return true;
1489
        }
1490
1491
        // made it here so the last check is whether the number of checkins per unique datetime on this registration
1492
        // disallows further check-ins.
1493
        $count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1494
            array(
1495
                array(
1496
                    'REG_ID' => $this->ID(),
1497
                    'CHK_in' => true,
1498
                ),
1499
            ),
1500
            'DTT_ID',
1501
            true
1502
        );
1503
        // checkins have already reached their max number of uses
1504
        // so registrant can NOT checkin
1505
        if ($count_unique_dtt_checkins >= $max_uses) {
1506
            EE_Error::add_error(
1507
                esc_html__(
1508
                    'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1509
                    'event_espresso'
1510
                ),
1511
                __FILE__,
1512
                __FUNCTION__,
1513
                __LINE__
1514
            );
1515
            return false;
1516
        }
1517
        return true;
1518
    }
1519
1520
1521
    /**
1522
     * toggle Check-in status for this registration
1523
     * Check-ins are toggled in the following order:
1524
     * never checked in -> checked in
1525
     * checked in -> checked out
1526
     * checked out -> checked in
1527
     *
1528
     * @param  int $DTT_ID  include specific datetime to toggle Check-in for.
1529
     *                      If not included or null, then it is assumed latest datetime is being toggled.
1530
     * @param bool $verify  If true then can_checkin() is used to verify whether the person
1531
     *                      can be checked in or not.  Otherwise this forces change in checkin status.
1532
     * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1533
     * @throws EE_Error
1534
     */
1535
    public function toggle_checkin_status($DTT_ID = null, $verify = false)
1536
    {
1537
        if (empty($DTT_ID)) {
1538
            $datetime = $this->get_latest_related_datetime();
1539
            $DTT_ID = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1540
            // verify the registration can checkin for the given DTT_ID
1541 View Code Duplication
        } elseif (! $this->can_checkin($DTT_ID, $verify)) {
1542
            EE_Error::add_error(
1543
                sprintf(
1544
                    esc_html__(
1545
                        'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1546
                        'event_espresso'
1547
                    ),
1548
                    $this->ID(),
1549
                    $DTT_ID
1550
                ),
1551
                __FILE__,
1552
                __FUNCTION__,
1553
                __LINE__
1554
            );
1555
            return false;
1556
        }
1557
        $status_paths = array(
1558
            EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1559
            EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1560
            EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1561
        );
1562
        // start by getting the current status so we know what status we'll be changing to.
1563
        $cur_status = $this->check_in_status_for_datetime($DTT_ID, null);
1564
        $status_to = $status_paths[ $cur_status ];
1565
        // database only records true for checked IN or false for checked OUT
1566
        // no record ( null ) means checked in NEVER, but we obviously don't save that
1567
        $new_status = $status_to === EE_Checkin::status_checked_in ? true : false;
1568
        // add relation - note Check-ins are always creating new rows
1569
        // because we are keeping track of Check-ins over time.
1570
        // Eventually we'll probably want to show a list table
1571
        // for the individual Check-ins so that they can be managed.
1572
        $checkin = EE_Checkin::new_instance(
1573
            array(
1574
                'REG_ID' => $this->ID(),
1575
                'DTT_ID' => $DTT_ID,
1576
                'CHK_in' => $new_status,
1577
            )
1578
        );
1579
        // if the record could not be saved then return false
1580
        if ($checkin->save() === 0) {
1581
            if (WP_DEBUG) {
1582
                global $wpdb;
1583
                $error = sprintf(
1584
                    esc_html__(
1585
                        'Registration check in update failed because of the following database error: %1$s%2$s',
1586
                        'event_espresso'
1587
                    ),
1588
                    '<br />',
1589
                    $wpdb->last_error
1590
                );
1591
            } else {
1592
                $error = esc_html__(
1593
                    'Registration check in update failed because of an unknown database error',
1594
                    'event_espresso'
1595
                );
1596
            }
1597
            EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1598
            return false;
1599
        }
1600
        return $status_to;
1601
    }
1602
1603
1604
    /**
1605
     * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1606
     * "Latest" is defined by the `DTT_EVT_start` column.
1607
     *
1608
     * @return EE_Datetime|null
1609
     * @throws EE_Error
1610
     */
1611 View Code Duplication
    public function get_latest_related_datetime()
1612
    {
1613
        return EEM_Datetime::instance()->get_one(
1614
            array(
1615
                array(
1616
                    'Ticket.Registration.REG_ID' => $this->ID(),
1617
                ),
1618
                'order_by' => array('DTT_EVT_start' => 'DESC'),
1619
            )
1620
        );
1621
    }
1622
1623
1624
    /**
1625
     * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1626
     * "Earliest" is defined by the `DTT_EVT_start` column.
1627
     *
1628
     * @throws EE_Error
1629
     */
1630 View Code Duplication
    public function get_earliest_related_datetime()
1631
    {
1632
        return EEM_Datetime::instance()->get_one(
1633
            array(
1634
                array(
1635
                    'Ticket.Registration.REG_ID' => $this->ID(),
1636
                ),
1637
                'order_by' => array('DTT_EVT_start' => 'ASC'),
1638
            )
1639
        );
1640
    }
1641
1642
1643
    /**
1644
     * This method simply returns the check-in status for this registration and the given datetime.
1645
     * If neither the datetime nor the checkin values are provided as arguments,
1646
     * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1647
     *
1648
     * @param  int       $DTT_ID  The ID of the datetime we're checking against
1649
     *                            (if empty we'll get the primary datetime for
1650
     *                            this registration (via event) and use it's ID);
1651
     * @param EE_Checkin $checkin If present, we use the given checkin object rather than the dtt_id.
1652
     *
1653
     * @return int                Integer representing Check-in status.
1654
     * @throws EE_Error
1655
     */
1656
    public function check_in_status_for_datetime($DTT_ID = 0, $checkin = null)
1657
    {
1658
        $checkin_query_params = array(
1659
            'order_by' => array('CHK_timestamp' => 'DESC'),
1660
        );
1661
1662
        if ($DTT_ID > 0) {
1663
            $checkin_query_params[0] = array('DTT_ID' => $DTT_ID);
1664
        }
1665
1666
        // get checkin object (if exists)
1667
        $checkin = $checkin instanceof EE_Checkin
1668
            ? $checkin
1669
            : $this->get_first_related('Checkin', $checkin_query_params);
1670
        if ($checkin instanceof EE_Checkin) {
1671
            if ($checkin->get('CHK_in')) {
1672
                return EE_Checkin::status_checked_in; // checked in
1673
            }
1674
            return EE_Checkin::status_checked_out; // had checked in but is now checked out.
1675
        }
1676
        return EE_Checkin::status_checked_never; // never been checked in
1677
    }
1678
1679
1680
    /**
1681
     * This method returns a localized message for the toggled Check-in message.
1682
     *
1683
     * @param  int $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1684
     *                     then it is assumed Check-in for primary datetime was toggled.
1685
     * @param bool $error  This just flags that you want an error message returned. This is put in so that the error
1686
     *                     message can be customized with the attendee name.
1687
     * @return string internationalized message
1688
     * @throws EE_Error
1689
     */
1690
    public function get_checkin_msg($DTT_ID, $error = false)
1691
    {
1692
        // let's get the attendee first so we can include the name of the attendee
1693
        $attendee = $this->get_first_related('Attendee');
1694
        if ($attendee instanceof EE_Attendee) {
1695
            if ($error) {
1696
                return sprintf(__("%s's check-in status was not changed.", "event_espresso"), $attendee->full_name());
1697
            }
1698
            $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1699
            // what is the status message going to be?
1700
            switch ($cur_status) {
1701
                case EE_Checkin::status_checked_never:
1702
                    return sprintf(
1703
                        __("%s has been removed from Check-in records", "event_espresso"),
1704
                        $attendee->full_name()
1705
                    );
1706
                    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...
1707
                case EE_Checkin::status_checked_in:
1708
                    return sprintf(__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1709
                    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...
1710
                case EE_Checkin::status_checked_out:
1711
                    return sprintf(__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1712
                    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...
1713
            }
1714
        }
1715
        return esc_html__("The check-in status could not be determined.", "event_espresso");
1716
    }
1717
1718
1719
    /**
1720
     * Returns the related EE_Transaction to this registration
1721
     *
1722
     * @return EE_Transaction
1723
     * @throws EE_Error
1724
     * @throws EntityNotFoundException
1725
     */
1726
    public function transaction()
1727
    {
1728
        $transaction = $this->get_first_related('Transaction');
1729
        if (! $transaction instanceof \EE_Transaction) {
1730
            throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1731
        }
1732
        return $transaction;
1733
    }
1734
1735
1736
    /**
1737
     *        get Registration Code
1738
     */
1739
    public function reg_code()
1740
    {
1741
        return $this->get('REG_code');
1742
    }
1743
1744
1745
    /**
1746
     *        get Transaction ID
1747
     */
1748
    public function transaction_ID()
1749
    {
1750
        return $this->get('TXN_ID');
1751
    }
1752
1753
1754
    /**
1755
     * @return int
1756
     * @throws EE_Error
1757
     */
1758
    public function ticket_ID()
1759
    {
1760
        return $this->get('TKT_ID');
1761
    }
1762
1763
1764
    /**
1765
     *        Set Registration Code
1766
     *
1767
     * @access    public
1768
     * @param    string  $REG_code Registration Code
1769
     * @param    boolean $use_default
1770
     * @throws EE_Error
1771
     */
1772
    public function set_reg_code($REG_code, $use_default = false)
1773
    {
1774
        if (empty($REG_code)) {
1775
            EE_Error::add_error(
1776
                esc_html__('REG_code can not be empty.', 'event_espresso'),
1777
                __FILE__,
1778
                __FUNCTION__,
1779
                __LINE__
1780
            );
1781
            return;
1782
        }
1783
        if (! $this->reg_code()) {
1784
            parent::set('REG_code', $REG_code, $use_default);
1785
        } else {
1786
            EE_Error::doing_it_wrong(
1787
                __CLASS__ . '::' . __FUNCTION__,
1788
                esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
1789
                '4.6.0'
1790
            );
1791
        }
1792
    }
1793
1794
1795
    /**
1796
     * Returns all other registrations in the same group as this registrant who have the same ticket option.
1797
     * Note, if you want to just get all registrations in the same transaction (group), use:
1798
     *    $registration->transaction()->registrations();
1799
     *
1800
     * @since 4.5.0
1801
     * @return EE_Registration[] or empty array if this isn't a group registration.
1802
     * @throws EE_Error
1803
     */
1804
    public function get_all_other_registrations_in_group()
1805
    {
1806
        if ($this->group_size() < 2) {
1807
            return array();
1808
        }
1809
1810
        $query[0] = array(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1811
            'TXN_ID' => $this->transaction_ID(),
1812
            'REG_ID' => array('!=', $this->ID()),
1813
            'TKT_ID' => $this->ticket_ID(),
1814
        );
1815
        /** @var EE_Registration[] $registrations */
1816
        $registrations = $this->get_model()->get_all($query);
1817
        return $registrations;
1818
    }
1819
1820
    /**
1821
     * Return the link to the admin details for the object.
1822
     *
1823
     * @return string
1824
     * @throws EE_Error
1825
     */
1826 View Code Duplication
    public function get_admin_details_link()
1827
    {
1828
        EE_Registry::instance()->load_helper('URL');
1829
        return EEH_URL::add_query_args_and_nonce(
1830
            array(
1831
                'page'    => 'espresso_registrations',
1832
                'action'  => 'view_registration',
1833
                '_REG_ID' => $this->ID(),
1834
            ),
1835
            admin_url('admin.php')
1836
        );
1837
    }
1838
1839
    /**
1840
     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
1841
     *
1842
     * @return string
1843
     * @throws EE_Error
1844
     */
1845
    public function get_admin_edit_link()
1846
    {
1847
        return $this->get_admin_details_link();
1848
    }
1849
1850
    /**
1851
     * Returns the link to a settings page for the object.
1852
     *
1853
     * @return string
1854
     * @throws EE_Error
1855
     */
1856
    public function get_admin_settings_link()
1857
    {
1858
        return $this->get_admin_details_link();
1859
    }
1860
1861
    /**
1862
     * Returns the link to the "overview" for the object (typically the "list table" view).
1863
     *
1864
     * @return string
1865
     */
1866
    public function get_admin_overview_link()
1867
    {
1868
        EE_Registry::instance()->load_helper('URL');
1869
        return EEH_URL::add_query_args_and_nonce(
1870
            array(
1871
                'page' => 'espresso_registrations',
1872
            ),
1873
            admin_url('admin.php')
1874
        );
1875
    }
1876
1877
1878
    /**
1879
     * @param array $query_params
1880
     *
1881
     * @return \EE_Registration[]
1882
     * @throws EE_Error
1883
     */
1884
    public function payments($query_params = array())
1885
    {
1886
        return $this->get_many_related('Payment', $query_params);
1887
    }
1888
1889
1890
    /**
1891
     * @param array $query_params
1892
     *
1893
     * @return \EE_Registration_Payment[]
1894
     * @throws EE_Error
1895
     */
1896
    public function registration_payments($query_params = array())
1897
    {
1898
        return $this->get_many_related('Registration_Payment', $query_params);
1899
    }
1900
1901
1902
    /**
1903
     * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
1904
     * Note: if there are no payments on the registration there will be no payment method returned.
1905
     *
1906
     * @return EE_Payment_Method|null
1907
     */
1908
    public function payment_method()
1909
    {
1910
        return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
1911
    }
1912
1913
1914
    /**
1915
     * @return \EE_Line_Item
1916
     * @throws EntityNotFoundException
1917
     * @throws EE_Error
1918
     */
1919
    public function ticket_line_item()
1920
    {
1921
        $ticket = $this->ticket();
1922
        $transaction = $this->transaction();
1923
        $line_item = null;
1924
        $ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
1925
            $transaction->total_line_item(),
1926
            'Ticket',
1927
            array($ticket->ID())
1928
        );
1929 View Code Duplication
        foreach ($ticket_line_items as $ticket_line_item) {
1930
            if ($ticket_line_item instanceof \EE_Line_Item
1931
                && $ticket_line_item->OBJ_type() === 'Ticket'
1932
                && $ticket_line_item->OBJ_ID() === $ticket->ID()
1933
            ) {
1934
                $line_item = $ticket_line_item;
1935
                break;
1936
            }
1937
        }
1938 View Code Duplication
        if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
1939
            throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
1940
        }
1941
        return $line_item;
1942
    }
1943
1944
1945
    /**
1946
     * Soft Deletes this model object.
1947
     *
1948
     * @return boolean | int
1949
     * @throws RuntimeException
1950
     * @throws EE_Error
1951
     */
1952
    public function delete()
1953
    {
1954
        if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
1955
            $this->set_status(EEM_Registration::status_id_cancelled);
1956
        }
1957
        return parent::delete();
1958
    }
1959
1960
1961
    /**
1962
     * Restores whatever the previous status was on a registration before it was trashed (if possible)
1963
     *
1964
     * @throws EE_Error
1965
     * @throws RuntimeException
1966
     */
1967
    public function restore()
1968
    {
1969
        $previous_status = $this->get_extra_meta(
1970
            EE_Registration::PRE_TRASH_REG_STATUS_KEY,
1971
            true,
1972
            EEM_Registration::status_id_cancelled
1973
        );
1974
        if ($previous_status) {
1975
            $this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
1976
            $this->set_status($previous_status);
1977
        }
1978
        return parent::restore();
1979
    }
1980
1981
1982
    /**
1983
     * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
1984
     *
1985
     * @param  boolean $trigger_set_status_logic EE_Registration::set_status() can trigger additional logic
1986
     *                                           depending on whether the reg status changes to or from "Approved"
1987
     * @return boolean whether the Registration status was updated
1988
     * @throws EE_Error
1989
     * @throws RuntimeException
1990
     */
1991
    public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
1992
    {
1993
        $paid = $this->paid();
1994
        $price = $this->final_price();
1995
        switch (true) {
1996
            // overpaid or paid
1997
            case EEH_Money::compare_floats($paid, $price, '>'):
1998
            case EEH_Money::compare_floats($paid, $price):
1999
                $new_status = EEM_Registration::status_id_approved;
2000
                break;
2001
            //  underpaid
2002
            case EEH_Money::compare_floats($paid, $price, '<'):
2003
                $new_status = EEM_Registration::status_id_pending_payment;
2004
                break;
2005
            // uhhh Houston...
2006
            default:
2007
                throw new RuntimeException(
2008
                    esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2009
                );
2010
        }
2011
        if ($new_status !== $this->status_ID()) {
2012
            if ($trigger_set_status_logic) {
2013
                return $this->set_status($new_status);
2014
            }
2015
            parent::set('STS_ID', $new_status);
2016
            return true;
2017
        }
2018
        return false;
2019
    }
2020
2021
2022
    /*************************** DEPRECATED ***************************/
2023
2024
2025
    /**
2026
     * @deprecated
2027
     * @since     4.7.0
2028
     * @access    public
2029
     */
2030
    public function price_paid()
2031
    {
2032
        EE_Error::doing_it_wrong(
2033
            'EE_Registration::price_paid()',
2034
            esc_html__(
2035
                'This method is deprecated, please use EE_Registration::final_price() instead.',
2036
                'event_espresso'
2037
            ),
2038
            '4.7.0'
2039
        );
2040
        return $this->final_price();
2041
    }
2042
2043
2044
    /**
2045
     * @deprecated
2046
     * @since     4.7.0
2047
     * @access    public
2048
     * @param    float $REG_final_price
2049
     * @throws EE_Error
2050
     * @throws RuntimeException
2051
     */
2052
    public function set_price_paid($REG_final_price = 0.00)
2053
    {
2054
        EE_Error::doing_it_wrong(
2055
            'EE_Registration::set_price_paid()',
2056
            esc_html__(
2057
                'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2058
                'event_espresso'
2059
            ),
2060
            '4.7.0'
2061
        );
2062
        $this->set_final_price($REG_final_price);
2063
    }
2064
2065
2066
    /**
2067
     * @deprecated
2068
     * @since 4.7.0
2069
     * @return string
2070
     * @throws EE_Error
2071
     */
2072
    public function pretty_price_paid()
2073
    {
2074
        EE_Error::doing_it_wrong(
2075
            'EE_Registration::pretty_price_paid()',
2076
            esc_html__(
2077
                'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2078
                'event_espresso'
2079
            ),
2080
            '4.7.0'
2081
        );
2082
        return $this->pretty_final_price();
2083
    }
2084
2085
2086
    /**
2087
     * Gets the primary datetime related to this registration via the related Event to this registration
2088
     *
2089
     * @deprecated 4.9.17
2090
     * @return EE_Datetime
2091
     * @throws EE_Error
2092
     * @throws EntityNotFoundException
2093
     */
2094
    public function get_related_primary_datetime()
2095
    {
2096
        EE_Error::doing_it_wrong(
2097
            __METHOD__,
2098
            esc_html__(
2099
                'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2100
                'event_espresso'
2101
            ),
2102
            '4.9.17',
2103
            '5.0.0'
2104
        );
2105
        return $this->event()->primary_datetime();
2106
    }
2107
2108
    /**
2109
     * Returns the contact's name (or "Unknown" if there is no contact.)
2110
     * @since $VID:$
2111
     * @return string
2112
     * @throws EE_Error
2113
     * @throws InvalidArgumentException
2114
     * @throws InvalidDataTypeException
2115
     * @throws InvalidInterfaceException
2116
     * @throws ReflectionException
2117
     */
2118
    public function name()
2119
    {
2120
        return $this->attendeeName();
2121
    }
2122
}
2123