Completed
Branch FET/conditional-update-queries (67a610)
by
unknown
25:02 queued 16:45
created

EE_Registration::releaseRegistrationSpace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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