Completed
Branch FET-3467-waitlists (f9ed0c)
by
unknown
90:26 queued 78:09
created

EE_Event::spaces_remaining()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 12
nop 2
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
1
<?php if ( ! defined('EVENT_ESPRESSO_VERSION')) {
2
    exit('No direct script access allowed');
3
}
4
5
6
7
/**
8
 * EE_Event
9
 *
10
 * @package               Event Espresso
11
 * @subpackage            includes/models/
12
 * @author                Mike Nelson
13
 */
14
class EE_Event extends EE_CPT_Base implements EEI_Line_Item_Object, EEI_Admin_Links, EEI_Has_Icon, EEI_Event
15
{
16
17
    /**
18
     * cached value for the the logical active status for the event
19
     *
20
     * @see get_active_status()
21
     * @var string
22
     */
23
    protected $_active_status = '';
24
25
    /**
26
     * This is just used for caching the Primary Datetime for the Event on initial retrieval
27
     *
28
     * @var EE_Datetime
29
     */
30
    protected $_Primary_Datetime;
31
32
33
34
    /**
35
     * @param array  $props_n_values          incoming values
36
     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
37
     *                                        used.)
38
     * @param array  $date_formats            incoming date_formats in an array where the first value is the
39
     *                                        date_format and the second value is the time format
40
     * @return EE_Event
41
     * @throws \EE_Error
42
     */
43
    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
44
    {
45
        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
47
    }
48
49
50
51
    /**
52
     * @param array  $props_n_values  incoming values from the database
53
     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
54
     *                                the website will be used.
55
     * @return EE_Event
56
     * @throws \EE_Error
57
     */
58
    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
59
    {
60
        return new self($props_n_values, true, $timezone);
61
    }
62
63
64
65
    /**
66
     * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
67
     *
68
     * @param string $field_name
69
     * @param mixed  $field_value
70
     * @param bool   $use_default
71
     * @throws \EE_Error
72
     */
73
    public function set($field_name, $field_value, $use_default = false)
74
    {
75
        switch ($field_name) {
76
            case 'status' :
77
                $this->set_status($field_value, $use_default);
78
                break;
79
            default :
80
                parent::set($field_name, $field_value, $use_default);
81
        }
82
    }
83
84
85
86
    /**
87
     *    set_status
88
     * Checks if event status is being changed to SOLD OUT
89
     * and updates event meta data with previous event status
90
     * so that we can revert things if/when the event is no longer sold out
91
     *
92
     * @access public
93
     * @param string $new_status
94
     * @param bool   $use_default
95
     * @return void
96
     * @throws \EE_Error
97
     */
98
    public function set_status($new_status = null, $use_default = false)
99
    {
100
        // if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
101
        if (empty($new_status) && ! $use_default) {
102
            return;
103
        }
104
        // get current Event status
105
        $old_status = $this->status();
106
        // if status has changed
107
        if ($old_status !== $new_status) {
108
            // TO sold_out
109
            if ($new_status === EEM_Event::sold_out) {
110
                // save the previous event status so that we can revert if the event is no longer sold out
111
                $this->add_post_meta('_previous_event_status', $old_status);
112
                do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $new_status);
113
                // OR FROM  sold_out
114
            } else if ($old_status === EEM_Event::sold_out) {
115
                $this->delete_post_meta('_previous_event_status');
116
                do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $new_status);
117
            }
118
            // update status
119
            parent::set('status', $new_status, $use_default);
120
            do_action('AHEE__EE_Event__set_status__after_update', $this);
121
            return;
122
        } else {
123
            // even though the old value matches the new value, it's still good to
124
            // allow the parent set method to have a say
125
            parent::set('status', $new_status, $use_default);
126
            return;
127
        }
128
    }
129
130
131
132
    /**
133
     * Gets all the datetimes for this event
134
     *
135
     * @param array $query_params like EEM_Base::get_all
136
     * @return EE_Datetime[]
137
     * @throws \EE_Error
138
     */
139
    public function datetimes($query_params = array())
140
    {
141
        return $this->get_many_related('Datetime', $query_params);
142
    }
143
144
145
146
    /**
147
     * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
148
     *
149
     * @return EE_Datetime[]
150
     * @throws \EE_Error
151
     */
152
    public function datetimes_in_chronological_order()
153
    {
154
        return $this->get_many_related('Datetime', array('order_by' => array('DTT_EVT_start' => 'ASC')));
155
    }
156
157
158
159
    /**
160
     * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
161
     * @darren, we should probably UNSET timezone on the EEM_Datetime model
162
     * after running our query, so that this timezone isn't set for EVERY query
163
     * on EEM_Datetime for the rest of the request, no?
164
     *
165
     * @param boolean $show_expired whether or not to include expired events
166
     * @param boolean $show_deleted whether or not to include deleted events
167
     * @param null    $limit
168
     * @return \EE_Datetime[]
169
     * @throws \EE_Error
170
     */
171
    public function datetimes_ordered($show_expired = true, $show_deleted = false, $limit = null)
172
    {
173
        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
174
            $this->ID(),
175
            $show_expired,
176
            $show_deleted,
177
            $limit
178
        );
179
    }
180
181
182
183
    /**
184
     * Returns one related datetime. Mostly only used by some legacy code.
185
     *
186
     * @return EE_Datetime
187
     * @throws \EE_Error
188
     */
189
    public function first_datetime()
190
    {
191
        return $this->get_first_related('Datetime');
192
    }
193
194
195
196
    /**
197
     * Returns the 'primary' datetime for the event
198
     *
199
     * @param bool $try_to_exclude_expired
200
     * @param bool $try_to_exclude_deleted
201
     * @return EE_Datetime
202
     * @throws \EE_Error
203
     */
204
    public function primary_datetime($try_to_exclude_expired = true, $try_to_exclude_deleted = true)
205
    {
206
        if ( ! empty ($this->_Primary_Datetime)) {
207
            return $this->_Primary_Datetime;
208
        }
209
        $this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
210
            $this->ID(),
211
            $try_to_exclude_expired,
212
            $try_to_exclude_deleted
213
        );
214
        return $this->_Primary_Datetime;
215
    }
216
217
218
219
    /**
220
     * Gets all the tickets available for purchase of this event
221
     *
222
     * @param array $query_params like EEM_Base::get_all
223
     * @return EE_Ticket[]
224
     * @throws \EE_Error
225
     */
226
    public function tickets($query_params = array())
227
    {
228
        //first get all datetimes
229
        $datetimes = $this->datetimes_ordered();
230
        if ( ! $datetimes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $datetimes of type EE_Datetime[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
231
            return array();
232
        }
233
        $datetime_ids = array();
234
        foreach ($datetimes as $datetime) {
235
            $datetime_ids[] = $datetime->ID();
236
        }
237
        $where_params = array('Datetime.DTT_ID' => array('IN', $datetime_ids));
238
        //if incoming $query_params has where conditions let's merge but not override existing.
239
        if (is_array($query_params) && isset($query_params[0])) {
240
            $where_params = array_merge($query_params[0], $where_params);
241
            unset($query_params[0]);
242
        }
243
        //now add $where_params to $query_params
244
        $query_params[0] = $where_params;
245
        return EEM_Ticket::instance()->get_all($query_params);
246
    }
247
248
249
250
    /**
251
     * get all unexpired untrashed tickets
252
     *
253
     * @return bool|EE_Ticket[]
254
     * @throws EE_Error
255
     */
256
    public function active_tickets()
257
    {
258
        return $this->tickets(array(
259
            array(
260
                'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
261
                'TKT_deleted'  => false,
262
            ),
263
        ));
264
    }
265
266
267
268
    /**
269
     * @return bool
270
     * @throws \EE_Error
271
     */
272
    public function additional_limit()
273
    {
274
        return $this->get('EVT_additional_limit');
275
    }
276
277
278
279
    /**
280
     * @return bool
281
     * @throws \EE_Error
282
     */
283
    public function allow_overflow()
284
    {
285
        return $this->get('EVT_allow_overflow');
286
    }
287
288
289
290
    /**
291
     * @return bool
292
     * @throws \EE_Error
293
     */
294
    public function created()
295
    {
296
        return $this->get('EVT_created');
297
    }
298
299
300
301
    /**
302
     * @return bool
303
     * @throws \EE_Error
304
     */
305
    public function description()
306
    {
307
        return $this->get('EVT_desc');
308
    }
309
310
311
312
    /**
313
     * Runs do_shortcode and wpautop on the description
314
     *
315
     * @return string of html
316
     * @throws \EE_Error
317
     */
318
    public function description_filtered()
319
    {
320
        return $this->get_pretty('EVT_desc');
321
    }
322
323
324
325
    /**
326
     * @return bool
327
     * @throws \EE_Error
328
     */
329
    public function display_description()
330
    {
331
        return $this->get('EVT_display_desc');
332
    }
333
334
335
336
    /**
337
     * @return bool
338
     * @throws \EE_Error
339
     */
340
    public function display_ticket_selector()
341
    {
342
        return (bool)$this->get('EVT_display_ticket_selector');
343
    }
344
345
346
347
    /**
348
     * @return bool
349
     * @throws \EE_Error
350
     */
351
    public function external_url()
352
    {
353
        return $this->get('EVT_external_URL');
354
    }
355
356
357
358
    /**
359
     * @return bool
360
     * @throws \EE_Error
361
     */
362
    public function member_only()
363
    {
364
        return $this->get('EVT_member_only');
365
    }
366
367
368
369
    /**
370
     * @return bool
371
     * @throws \EE_Error
372
     */
373
    public function phone()
374
    {
375
        return $this->get('EVT_phone');
376
    }
377
378
379
380
    /**
381
     * @return bool
382
     * @throws \EE_Error
383
     */
384
    public function modified()
385
    {
386
        return $this->get('EVT_modified');
387
    }
388
389
390
391
    /**
392
     * @return bool
393
     * @throws \EE_Error
394
     */
395
    public function name()
396
    {
397
        return $this->get('EVT_name');
398
    }
399
400
401
402
    /**
403
     * @return bool
404
     * @throws \EE_Error
405
     */
406
    public function order()
407
    {
408
        return $this->get('EVT_order');
409
    }
410
411
412
413
    /**
414
     * @return bool|string
415
     * @throws \EE_Error
416
     */
417
    public function default_registration_status()
418
    {
419
        $event_default_registration_status = $this->get('EVT_default_registration_status');
420
        return ! empty($event_default_registration_status)
421
            ? $event_default_registration_status
422
            : EE_Registry::instance()->CFG->registration->default_STS_ID;
423
    }
424
425
426
427
    /**
428
     * @param int  $num_words
429
     * @param null $more
430
     * @param bool $not_full_desc
431
     * @return bool|string
432
     * @throws \EE_Error
433
     */
434
    public function short_description($num_words = 55, $more = null, $not_full_desc = false)
435
    {
436
        $short_desc = $this->get('EVT_short_desc');
437
        if ( ! empty($short_desc) || $not_full_desc) {
438
            return $short_desc;
439
        } else {
440
            $full_desc = $this->get('EVT_desc');
441
            return wp_trim_words($full_desc, $num_words, $more);
442
        }
443
    }
444
445
446
447
    /**
448
     * @return bool
449
     * @throws \EE_Error
450
     */
451
    public function slug()
452
    {
453
        return $this->get('EVT_slug');
454
    }
455
456
457
458
    /**
459
     * @return bool
460
     * @throws \EE_Error
461
     */
462
    public function timezone_string()
463
    {
464
        return $this->get('EVT_timezone_string');
465
    }
466
467
468
469
    /**
470
     * @return bool
471
     * @throws \EE_Error
472
     */
473
    public function visible_on()
474
    {
475
        return $this->get('EVT_visible_on');
476
    }
477
478
479
480
    /**
481
     * @return int
482
     * @throws \EE_Error
483
     */
484
    public function wp_user()
485
    {
486
        return $this->get('EVT_wp_user');
487
    }
488
489
490
491
    /**
492
     * @return bool
493
     * @throws \EE_Error
494
     */
495
    public function donations()
496
    {
497
        return $this->get('EVT_donations');
498
    }
499
500
501
502
    /**
503
     * @param $limit
504
     * @throws \EE_Error
505
     */
506
    public function set_additional_limit($limit)
507
    {
508
        $this->set('EVT_additional_limit', $limit);
509
    }
510
511
512
513
    /**
514
     * @param $created
515
     * @throws \EE_Error
516
     */
517
    public function set_created($created)
518
    {
519
        $this->set('EVT_created', $created);
520
    }
521
522
523
524
    /**
525
     * @param $desc
526
     * @throws \EE_Error
527
     */
528
    public function set_description($desc)
529
    {
530
        $this->set('EVT_desc', $desc);
531
    }
532
533
534
535
    /**
536
     * @param $display_desc
537
     * @throws \EE_Error
538
     */
539
    public function set_display_description($display_desc)
540
    {
541
        $this->set('EVT_display_desc', $display_desc);
542
    }
543
544
545
546
    /**
547
     * @param $display_ticket_selector
548
     * @throws \EE_Error
549
     */
550
    public function set_display_ticket_selector($display_ticket_selector)
551
    {
552
        $this->set('EVT_display_ticket_selector', $display_ticket_selector);
553
    }
554
555
556
557
    /**
558
     * @param $external_url
559
     * @throws \EE_Error
560
     */
561
    public function set_external_url($external_url)
562
    {
563
        $this->set('EVT_external_URL', $external_url);
564
    }
565
566
567
568
    /**
569
     * @param $member_only
570
     * @throws \EE_Error
571
     */
572
    public function set_member_only($member_only)
573
    {
574
        $this->set('EVT_member_only', $member_only);
575
    }
576
577
578
579
    /**
580
     * @param $event_phone
581
     * @throws \EE_Error
582
     */
583
    public function set_event_phone($event_phone)
584
    {
585
        $this->set('EVT_phone', $event_phone);
586
    }
587
588
589
590
    /**
591
     * @param $modified
592
     * @throws \EE_Error
593
     */
594
    public function set_modified($modified)
595
    {
596
        $this->set('EVT_modified', $modified);
597
    }
598
599
600
601
    /**
602
     * @param $name
603
     * @throws \EE_Error
604
     */
605
    public function set_name($name)
606
    {
607
        $this->set('EVT_name', $name);
608
    }
609
610
611
612
    /**
613
     * @param $order
614
     * @throws \EE_Error
615
     */
616
    public function set_order($order)
617
    {
618
        $this->set('EVT_order', $order);
619
    }
620
621
622
623
    /**
624
     * @param $short_desc
625
     * @throws \EE_Error
626
     */
627
    public function set_short_description($short_desc)
628
    {
629
        $this->set('EVT_short_desc', $short_desc);
630
    }
631
632
633
634
    /**
635
     * @param $slug
636
     * @throws \EE_Error
637
     */
638
    public function set_slug($slug)
639
    {
640
        $this->set('EVT_slug', $slug);
641
    }
642
643
644
645
    /**
646
     * @param $timezone_string
647
     * @throws \EE_Error
648
     */
649
    public function set_timezone_string($timezone_string)
650
    {
651
        $this->set('EVT_timezone_string', $timezone_string);
652
    }
653
654
655
656
    /**
657
     * @param $visible_on
658
     * @throws \EE_Error
659
     */
660
    public function set_visible_on($visible_on)
661
    {
662
        $this->set('EVT_visible_on', $visible_on);
663
    }
664
665
666
667
    /**
668
     * @param $wp_user
669
     * @throws \EE_Error
670
     */
671
    public function set_wp_user($wp_user)
672
    {
673
        $this->set('EVT_wp_user', $wp_user);
674
    }
675
676
677
678
    /**
679
     * @param $default_registration_status
680
     * @throws \EE_Error
681
     */
682
    public function set_default_registration_status($default_registration_status)
683
    {
684
        $this->set('EVT_default_registration_status', $default_registration_status);
685
    }
686
687
688
689
    /**
690
     * @param $donations
691
     * @throws \EE_Error
692
     */
693
    public function set_donations($donations)
694
    {
695
        $this->set('EVT_donations', $donations);
696
    }
697
698
699
700
    /**
701
     * Adds a venue to this event
702
     *
703
     * @param EE_Venue /int $venue_id_or_obj
704
     * @return EE_Venue
705
     * @throws \EE_Error
706
     */
707
    public function add_venue($venue_id_or_obj)
708
    {
709
        return $this->_add_relation_to($venue_id_or_obj, 'Venue');
710
    }
711
712
713
714
    /**
715
     * Removes a venue from the event
716
     *
717
     * @param EE_Venue /int $venue_id_or_obj
718
     * @return EE_Venue
719
     * @throws \EE_Error
720
     */
721
    public function remove_venue($venue_id_or_obj)
722
    {
723
        return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
724
    }
725
726
727
728
    /**
729
     * Gets all the venues related ot the event. May provide additional $query_params if desired
730
     *
731
     * @param array $query_params like EEM_Base::get_all's $query_params
732
     * @return EE_Venue[]
733
     * @throws \EE_Error
734
     */
735
    public function venues($query_params = array())
736
    {
737
        return $this->get_many_related('Venue', $query_params);
738
    }
739
740
741
742
    /**
743
     * check if event id is present and if event is published
744
     *
745
     * @access public
746
     * @return boolean true yes, false no
747
     * @throws \EE_Error
748
     */
749
    private function _has_ID_and_is_published()
750
    {
751
        // first check if event id is present and not NULL,
752
        // then check if this event is published (or any of the equivalent "published" statuses)
753
        return
754
            $this->ID() && $this->ID() !== null
755
            && (
756
                $this->status() === 'publish'
757
                || $this->status() === EEM_Event::sold_out
758
                || $this->status() === EEM_Event::postponed
759
                || $this->status() === EEM_Event::cancelled
760
            )
761
            ? true
762
            : false;
763
    }
764
765
766
767
    /**
768
     * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
769
     *
770
     * @access public
771
     * @return boolean true yes, false no
772
     * @throws \EE_Error
773
     */
774 View Code Duplication
    public function is_upcoming()
775
    {
776
        // check if event id is present and if this event is published
777
        if ($this->is_inactive()) {
778
            return false;
779
        }
780
        // set initial value
781
        $upcoming = false;
782
        //next let's get all datetimes and loop through them
783
        $datetimes = $this->datetimes_in_chronological_order();
784
        foreach ($datetimes as $datetime) {
785
            if ($datetime instanceof EE_Datetime) {
786
                //if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
787
                if ($datetime->is_expired()) {
788
                    continue;
789
                }
790
                //if this dtt is active then we return false.
791
                if ($datetime->is_active()) {
792
                    return false;
793
                }
794
                //otherwise let's check upcoming status
795
                $upcoming = $datetime->is_upcoming();
796
            }
797
        }
798
        return $upcoming;
799
    }
800
801
802
803
    /**
804
     * @return bool
805
     * @throws \EE_Error
806
     */
807 View Code Duplication
    public function is_active()
808
    {
809
        // check if event id is present and if this event is published
810
        if ($this->is_inactive()) {
811
            return false;
812
        }
813
        // set initial value
814
        $active = false;
815
        //next let's get all datetimes and loop through them
816
        $datetimes = $this->datetimes_in_chronological_order();
817
        foreach ($datetimes as $datetime) {
818
            if ($datetime instanceof EE_Datetime) {
819
                //if this dtt is expired then we continue cause one of the other datetimes might be active.
820
                if ($datetime->is_expired()) {
821
                    continue;
822
                }
823
                //if this dtt is upcoming then we return false.
824
                if ($datetime->is_upcoming()) {
825
                    return false;
826
                }
827
                //otherwise let's check active status
828
                $active = $datetime->is_active();
829
            }
830
        }
831
        return $active;
832
    }
833
834
835
836
    /**
837
     * @return bool
838
     * @throws \EE_Error
839
     */
840 View Code Duplication
    public function is_expired()
841
    {
842
        // check if event id is present and if this event is published
843
        if ($this->is_inactive()) {
844
            return false;
845
        }
846
        // set initial value
847
        $expired = false;
848
        //first let's get all datetimes and loop through them
849
        $datetimes = $this->datetimes_in_chronological_order();
850
        foreach ($datetimes as $datetime) {
851
            if ($datetime instanceof EE_Datetime) {
852
                //if this dtt is upcoming or active then we return false.
853
                if ($datetime->is_upcoming() || $datetime->is_active()) {
854
                    return false;
855
                }
856
                //otherwise let's check active status
857
                $expired = $datetime->is_expired();
858
            }
859
        }
860
        return $expired;
861
    }
862
863
864
865
    /**
866
     * @return bool
867
     * @throws \EE_Error
868
     */
869
    public function is_inactive()
870
    {
871
        // check if event id is present and if this event is published
872
        if ($this->_has_ID_and_is_published()) {
873
            return false;
874
        }
875
        return true;
876
    }
877
878
879
880
    /**
881
     * calculate spaces remaining based on "saleable" tickets
882
     *
883
     * @param array $tickets
884
     * @return int
885
     * @throws EE_Error
886
     */
887
    public function spaces_remaining($tickets = array(), $filtered = true)
888
    {
889
        // get all unexpired untrashed tickets if nothing was passed
890
        $tickets = ! empty($tickets) ? $tickets : $this->active_tickets();
891
        // set initial value
892
        $spaces_remaining = 0;
893
        foreach ($tickets as $ticket) {
0 ignored issues
show
Bug introduced by
The expression $tickets of type boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
894
            if ($ticket instanceof EE_Ticket) {
895
                $spaces_remaining += $ticket->qty('saleable');
896
            }
897
        }
898
        return $filtered
899
            ? (int) apply_filters(
900
                'FHEE_EE_Event__spaces_remaining',
901
                $spaces_remaining,
902
                $this,
903
                $tickets
904
            )
905
            : $spaces_remaining;
906
    }
907
908
909
    /**
910
     *    perform_sold_out_status_check
911
     *    checks all of this events's datetime  reg_limit - sold values to determine if ANY datetimes have spaces available...
912
     *    if NOT, then the event status will get toggled to 'sold_out'
913
     *
914
     * @access public
915
     * @return bool    return the ACTUAL sold out state.
916
     * @throws \EE_Error
917
     */
918
    public function perform_sold_out_status_check()
919
    {
920
        // get all unexpired untrashed tickets
921
        $tickets = $this->active_tickets();
922
        // if all the tickets are just expired, then don't update the event status to sold out
923
        if (empty($tickets)) {
924
            return true;
925
        }
926
        $spaces_remaining = $this->spaces_remaining($tickets);
927
        if ($spaces_remaining < 1) {
928
            $this->set_status(EEM_Event::sold_out);
929
            $this->save();
930
            $sold_out = true;
931
        } else {
932
            $sold_out = false;
933
            // was event previously marked as sold out ?
934
            if ($this->status() === EEM_Event::sold_out) {
935
                // revert status to previous value, if it was set
936
                $previous_event_status = $this->get_post_meta('_previous_event_status', true);
937
                if ($previous_event_status) {
938
                    $this->set_status($previous_event_status);
939
                    $this->save();
940
                }
941
            }
942
        }
943
        do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
944
        return $sold_out;
945
    }
946
947
948
949
    /**
950
     * This returns the total remaining spaces for sale on this event.
951
     * ############################
952
     * VERY IMPORTANT FOR DEVELOPERS:
953
     * While included here, this method is still being tested internally, so its signature and behaviour COULD change.
954
     * While this comment block is in place, usage is at your own risk and know that it may change in future builds.
955
     * ############################
956
     *
957
     * @uses EE_Event::total_available_spaces()
958
     * @return float|int  (EE_INF is returned as float)
959
     * @throws \EE_Error
960
     */
961
    public function spaces_remaining_for_sale()
962
    {
963
        //first get total available spaces including consideration for tickets that have already sold.
964
        $spaces_available = $this->total_available_spaces(true);
965
        //if total available = 0, then exit right away because that means everything is expired.
966
        if ($spaces_available === 0) {
967
            return 0;
968
        }
969
        //subtract total approved registrations from spaces available to get how many are remaining.
970
        $spots_taken = EEM_Registration::instance()->event_reg_count_for_status($this);
971
        $spaces_remaining = $spaces_available - $spots_taken;
972
        return $spaces_remaining > 0 ? $spaces_remaining : 0;
973
    }
974
975
976
977
    /**
978
     * This returns the total spaces available for an event while considering all the qtys on the tickets and the reg limits
979
     * on the datetimes attached to this event.
980
     * ############################
981
     * VERY IMPORTANT FOR DEVELOPERS:
982
     * While included here, this method is still being tested internally, so its signature and behaviour COULD change. While
983
     * this comment block is in place, usage is at your own risk and know that it may change in future builds.
984
     * ############################
985
     * Note: by "spaces available" we are not returning how many spaces remain.  That is a calculation involving using the value
986
     * from this method and subtracting the approved registrations for the event.
987
     *
988
     * @param   bool $current_total_available       Whether to consider any tickets that have already sold in our calculation.
989
     *                                              If this is false, then we return the most tickets that could ever be sold
990
     *                                              for this event with the datetime and tickets setup on the event under optimal
991
     *                                              selling conditions.  Otherwise we return a live calculation of spaces available
992
     *                                              based on tickets sold.  Depending on setup and stage of sales, this
993
     *                                              may appear to equal remaining tickets.  However, the more tickets are
994
     *                                              sold out, the more accurate the "live" total is.
995
     * @return  int|float  (Note: if EE_INF is returned its considered a float by PHP)
996
     * @throws \EE_Error
997
     */
998
    public function total_available_spaces($current_total_available = false)
999
    {
1000
        $spaces_available = 0;
1001
        //first get all tickets on the event and include expired tickets
1002
        $tickets = $this->tickets(array('default_where_conditions' => 'none'));
1003
        $ticket_sums = array();
1004
        $datetimes = array();
1005
        $datetime_limits = array();
1006
        //loop through tickets and normalize them
1007
        foreach ($tickets as $ticket) {
1008
            $datetimes = $ticket->datetimes(array('order_by' => array('DTT_reg_limit' => 'ASC')));
1009
            if (empty($datetimes)) {
1010
                continue;
1011
            }
1012
            //first datetime should be the lowest datetime
1013
            $least_datetime = reset($datetimes);
1014
            //lets reset the ticket quantity to be the lower of either the lowest datetime reg limit or the ticket quantity
1015
            //IF datetimes sold (and we're not doing current live total available, then use spaces remaining for datetime, not reg_limit.
1016
            if ($current_total_available) {
1017
                if ($ticket->is_remaining()) {
1018
                    $remaining = $ticket->remaining();
1019
                } else {
1020
                    $spaces_available += $ticket->sold();
1021
                    //and we don't cache this ticket to our list because its sold out.
1022
                    continue;
1023
                }
1024
            } else {
1025
                $remaining = min($ticket->qty(), $least_datetime->reg_limit());
1026
            }
1027
            //if $ticket_limit == infinity then let's drop out right away and just return that because any infinity amount trumps all other "available" amounts.
1028
            if ($remaining === EE_INF) {
1029
                return EE_INF;
1030
            }
1031
            //multiply normalized $tkt quantity by the number of datetimes on the ticket as the "sum"
1032
            //also include the sum of all the datetime reg limits on the ticket for breaking ties.
1033
            $ticket_sums[$ticket->ID()]['sum'] = $remaining * count($datetimes);
1034
            $ticket_sums[$ticket->ID()]['datetime_sums'] = 0;
1035
            foreach ($datetimes as $datetime) {
1036
                if ($datetime->reg_limit() === EE_INF) {
1037
                    $ticket_sums[$ticket->ID()]['datetime_sums'] = EE_INF;
1038
                } else {
1039
                    $ticket_sums[$ticket->ID()]['datetime_sums'] += $current_total_available
1040
                        ? $datetime->spaces_remaining()
1041
                        : $datetime->reg_limit();
1042
                }
1043
                $datetime_limits[$datetime->ID()] = $current_total_available
1044
                    ? $datetime->spaces_remaining()
1045
                    : $datetime->reg_limit();
1046
            }
1047
            $ticket_sums[$ticket->ID()]['ticket'] = $ticket;
1048
        }
1049
        //The order is sorted by lowest available first (which is calculated for each ticket by multiplying the normalized
1050
        //ticket quantity by the number of datetimes on the ticket).  For tie-breakers, then the next sort is based on the
1051
        //ticket with the greatest sum of all remaining datetime->spaces_remaining() ( or $datetime->reg_limit() if not
1052
        //$current_total_available ) for the datetimes on the ticket.
1053
        usort($ticket_sums, function ($a, $b) {
1054
            if ($a['sum'] === $b['sum']) {
1055
                if ($a['datetime_sums'] === $b['datetime_sums']) {
1056
                    return 0;
1057
                }
1058
                return $a['datetime_sums'] < $b['datetime_sums'] ? 1 : -1;
1059
            }
1060
            return ($a['sum'] < $b['sum']) ? -1 : 1;
1061
        });
1062
        //now let's loop through the sorted tickets and simulate sellouts
1063
        foreach ($ticket_sums as $ticket_info) {
1064
            if ($ticket_info['ticket'] instanceof EE_Ticket) {
1065
                $datetimes = $ticket_info['ticket']->datetimes(array('order_by' => array('DTT_reg_limit' => 'ASC')));
1066
                //need to sort these $datetimes by remaining (only if $current_total_available)
1067
                //setup datetimes for simulation
1068
                $ticket_datetimes_remaining = array();
1069
                foreach ($datetimes as $datetime) {
1070
                    $ticket_datetimes_remaining[$datetime->ID()]['rem'] = $datetime_limits[$datetime->ID()];
1071
                    $ticket_datetimes_remaining[$datetime->ID()]['datetime'] = $datetime;
1072
                }
1073 View Code Duplication
                usort($ticket_datetimes_remaining, function ($a, $b) {
1074
                    if ($a['rem'] === $b['rem']) {
1075
                        return 0;
1076
                    }
1077
                    return ($a['rem'] < $b['rem']) ? -1 : 1;
1078
                });
1079
                //get the remaining on the first datetime (which should be the one with the least remaining) and that is
1080
                //what we add to the spaces_available running total.  Then we need to decrease the remaining on our datetime tracker.
1081
                $lowest_datetime = reset($ticket_datetimes_remaining);
1082
                //need to get the lower of; what the remaining is on the lowest datetime, and the remaining on the ticket.
1083
                // If this ends up being 0 (because of previous tickets in our simulation selling out), then it has already
1084
                // been tracked on $spaces available and this ticket is now sold out for the simulation, so we can continue
1085
                // to the next ticket.
1086
                if ($current_total_available) {
1087
                    $remaining = min($lowest_datetime['rem'], $ticket_info['ticket']->remaining());
1088
                } else {
1089
                    $remaining = min($lowest_datetime['rem'], $ticket_info['ticket']->qty());
1090
                }
1091
                //if $remaining is infinite that means that all datetimes on this ticket are infinite but we've made it here because all
1092
                //tickets have a quantity.  So we don't have to track datetimes, we can just use ticket quantities for total
1093
                //available.
1094
                if ($remaining === EE_INF) {
1095
                    $spaces_available += $ticket_info['ticket']->qty();
1096
                    continue;
1097
                }
1098
                //if ticket has sold amounts then we also need to add that (but only if doing live counts)
1099
                if ($current_total_available) {
1100
                    $spaces_available += $ticket_info['ticket']->sold();
1101
                }
1102
                if ($remaining <= 0) {
1103
                    continue;
1104
                } else {
1105
                    $spaces_available += $remaining;
1106
                }
1107
                //loop through the datetimes and sell them out!
1108
                foreach ($ticket_datetimes_remaining as $datetime_info) {
1109
                    if ($datetime_info['datetime'] instanceof EE_Datetime) {
1110
                        $datetime_limits[$datetime_info['datetime']->ID()] += -$remaining;
1111
                    }
1112
                }
1113
            }
1114
        }
1115
        return apply_filters(
1116
            'FHEE_EE_Event__total_available_spaces__spaces_available',
1117
            $spaces_available,
1118
            $this,
1119
            $datetimes,
1120
            $tickets
1121
        );
1122
    }
1123
1124
1125
1126
    /**
1127
     * Checks if the event is set to sold out
1128
     *
1129
     * @param  bool $actual whether or not to perform calculations to not only figure the
1130
     *                      actual status but also to flip the status if necessary to sold
1131
     *                      out If false, we just check the existing status of the event
1132
     * @return boolean
1133
     * @throws \EE_Error
1134
     */
1135
    public function is_sold_out($actual = false)
1136
    {
1137
        if ( ! $actual) {
1138
            return $this->status() === EEM_Event::sold_out;
1139
        } else {
1140
            return $this->perform_sold_out_status_check();
1141
        }
1142
    }
1143
1144
1145
1146
    /**
1147
     * Checks if the event is marked as postponed
1148
     *
1149
     * @return boolean
1150
     */
1151
    public function is_postponed()
1152
    {
1153
        return $this->status() === EEM_Event::postponed;
1154
    }
1155
1156
1157
1158
    /**
1159
     * Checks if the event is marked as cancelled
1160
     *
1161
     * @return boolean
1162
     */
1163
    public function is_cancelled()
1164
    {
1165
        return $this->status() === EEM_Event::cancelled;
1166
    }
1167
1168
1169
1170
    /**
1171
     * Get the logical active status in a hierarchical order for all the datetimes.  Note
1172
     * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1173
     * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1174
     * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1175
     * the event is considered expired.
1176
     * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a status
1177
     * set on the EVENT when it is not published and thus is done
1178
     *
1179
     * @param bool $reset
1180
     * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1181
     * @throws \EE_Error
1182
     */
1183
    public function get_active_status($reset = false)
1184
    {
1185
        // if the active status has already been set, then just use that value (unless we are resetting it)
1186
        if ( ! empty($this->_active_status) && ! $reset) {
1187
            return $this->_active_status;
1188
        }
1189
        //first check if event id is present on this object
1190
        if ( ! $this->ID()) {
1191
            return false;
1192
        }
1193
        $where_params_for_event = array(array('EVT_ID' => $this->ID()));
1194
        //if event is published:
1195
        if ($this->status() === 'publish') {
1196
            //active?
1197
            if (EEM_Datetime::instance()->get_datetime_count_for_status(EE_Datetime::active, $where_params_for_event) > 0) {
1198
                $this->_active_status = EE_Datetime::active;
1199
            } else {
1200
                //upcoming?
1201
                if (EEM_Datetime::instance()->get_datetime_count_for_status(EE_Datetime::upcoming, $where_params_for_event) > 0) {
1202
                    $this->_active_status = EE_Datetime::upcoming;
1203
                } else {
1204
                    //expired?
1205
                    if (EEM_Datetime::instance()->get_datetime_count_for_status(EE_Datetime::expired, $where_params_for_event) > 0) {
1206
                        $this->_active_status = EE_Datetime::expired;
1207
                    } else {
1208
                        //it would be odd if things make it this far because it basically means there are no datetime's
1209
                        //attached to the event.  So in this case it will just be considered inactive.
1210
                        $this->_active_status = EE_Datetime::inactive;
1211
                    }
1212
                }
1213
            }
1214
        } else {
1215
            //the event is not published, so let's just set it's active status according to its' post status
1216
            switch ($this->status()) {
1217
                case EEM_Event::sold_out :
1218
                    $this->_active_status = EE_Datetime::sold_out;
1219
                    break;
1220
                case EEM_Event::cancelled :
1221
                    $this->_active_status = EE_Datetime::cancelled;
1222
                    break;
1223
                case EEM_Event::postponed :
1224
                    $this->_active_status = EE_Datetime::postponed;
1225
                    break;
1226
                default :
1227
                    $this->_active_status = EE_Datetime::inactive;
1228
            }
1229
        }
1230
        return $this->_active_status;
1231
    }
1232
1233
1234
1235
    /**
1236
     *    pretty_active_status
1237
     *
1238
     * @access public
1239
     * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1240
     * @return mixed void|string
1241
     * @throws \EE_Error
1242
     */
1243
    public function pretty_active_status($echo = true)
1244
    {
1245
        $active_status = $this->get_active_status();
1246
        $status = '<span class="ee-status event-active-status-'
1247
                  . $active_status
1248
                  . '">'
1249
                  . EEH_Template::pretty_status($active_status, false, 'sentence')
1250
                  . '</span>';
1251
        if ($echo) {
1252
            echo $status;
1253
            return '';
1254
        }
1255
        return $status;
1256
    }
1257
1258
1259
1260
    /**
1261
     * @return bool|int
1262
     * @throws \EE_Error
1263
     */
1264
    public function get_number_of_tickets_sold()
1265
    {
1266
        $tkt_sold = 0;
1267
        if ( ! $this->ID()) {
1268
            return 0;
1269
        }
1270
        $datetimes = $this->datetimes();
1271
        foreach ($datetimes as $datetime) {
1272
            if ($datetime instanceof EE_Datetime) {
1273
                $tkt_sold += $datetime->sold();
1274
            }
1275
        }
1276
        return $tkt_sold;
1277
    }
1278
1279
1280
1281
    /**
1282
     * This just returns a count of all the registrations for this event
1283
     *
1284
     * @access  public
1285
     * @return int
1286
     * @throws \EE_Error
1287
     */
1288
    public function get_count_of_all_registrations()
1289
    {
1290
        return EEM_Event::instance()->count_related($this, 'Registration');
1291
    }
1292
1293
1294
1295
    /**
1296
     * This returns the ticket with the earliest start time that is
1297
     * available for this event (across all datetimes attached to the event)
1298
     *
1299
     * @return EE_Ticket
1300
     * @throws \EE_Error
1301
     */
1302
    public function get_ticket_with_earliest_start_time()
1303
    {
1304
        $where['Datetime.EVT_ID'] = $this->ID();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$where was never initialized. Although not strictly required by PHP, it is generally a good practice to add $where = 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...
1305
        $query_params = array($where, 'order_by' => array('TKT_start_date' => 'ASC'));
1306
        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1307
    }
1308
1309
1310
1311
    /**
1312
     * This returns the ticket with the latest end time that is available
1313
     * for this event (across all datetimes attached to the event)
1314
     *
1315
     * @return EE_Ticket
1316
     * @throws \EE_Error
1317
     */
1318
    public function get_ticket_with_latest_end_time()
1319
    {
1320
        $where['Datetime.EVT_ID'] = $this->ID();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$where was never initialized. Although not strictly required by PHP, it is generally a good practice to add $where = 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...
1321
        $query_params = array($where, 'order_by' => array('TKT_end_date' => 'DESC'));
1322
        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1323
    }
1324
1325
1326
1327
    /**
1328
     * This returns whether there are any tickets on sale for this event.
1329
     *
1330
     * @return bool true = YES tickets on sale.
1331
     * @throws \EE_Error
1332
     */
1333
    public function tickets_on_sale()
1334
    {
1335
        $earliest_ticket = $this->get_ticket_with_earliest_start_time();
1336
        $latest_ticket = $this->get_ticket_with_latest_end_time();
1337
        if ( ! $latest_ticket instanceof EE_Ticket && ! $earliest_ticket instanceof EE_Ticket) {
1338
            return false;
1339
        }
1340
        //check on sale for these two tickets.
1341
        if ($latest_ticket->is_on_sale() || $earliest_ticket->is_on_sale()) {
1342
            return true;
1343
        }
1344
        return false;
1345
    }
1346
1347
1348
1349
    /**
1350
     * Gets the URL for viewing this event on the front-end. Overrides parent
1351
     * to check for an external URL first
1352
     *
1353
     * @return string
1354
     * @throws \EE_Error
1355
     */
1356
    public function get_permalink()
1357
    {
1358
        if ($this->external_url()) {
1359
            return $this->external_url();
1360
        } else {
1361
            return parent::get_permalink();
1362
        }
1363
    }
1364
1365
1366
1367
    /**
1368
     * Gets the first term for 'espresso_event_categories' we can find
1369
     *
1370
     * @param array $query_params like EEM_Base::get_all
1371
     * @return EE_Term
1372
     * @throws \EE_Error
1373
     */
1374
    public function first_event_category($query_params = array())
1375
    {
1376
        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1377
        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1378
        return EEM_Term::instance()->get_one($query_params);
1379
    }
1380
1381
1382
1383
    /**
1384
     * Gets all terms for 'espresso_event_categories' we can find
1385
     *
1386
     * @param array $query_params
1387
     * @return EE_Term[]
1388
     * @throws \EE_Error
1389
     */
1390
    public function get_all_event_categories($query_params = array())
1391
    {
1392
        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1393
        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1394
        return EEM_Term::instance()->get_all($query_params);
1395
    }
1396
1397
1398
1399
    /**
1400
     * Gets all the question groups, ordering them by QSG_order ascending
1401
     *
1402
     * @param array $query_params @see EEM_Base::get_all
1403
     * @return EE_Question_Group[]
1404
     * @throws \EE_Error
1405
     */
1406
    public function question_groups($query_params = array())
1407
    {
1408
        $query_params = ! empty($query_params) ? $query_params : array('order_by' => array('QSG_order' => 'ASC'));
1409
        return $this->get_many_related('Question_Group', $query_params);
1410
    }
1411
1412
1413
1414
    /**
1415
     * Implementation for EEI_Has_Icon interface method.
1416
     *
1417
     * @see EEI_Visual_Representation for comments
1418
     * @return string
1419
     */
1420
    public function get_icon()
1421
    {
1422
        return '<span class="dashicons dashicons-flag"></span>';
1423
    }
1424
1425
1426
1427
    /**
1428
     * Implementation for EEI_Admin_Links interface method.
1429
     *
1430
     * @see EEI_Admin_Links for comments
1431
     * @return string
1432
     * @throws \EE_Error
1433
     */
1434
    public function get_admin_details_link()
1435
    {
1436
        return $this->get_admin_edit_link();
1437
    }
1438
1439
1440
1441
    /**
1442
     * Implementation for EEI_Admin_Links interface method.
1443
     *
1444
     * @see EEI_Admin_Links for comments
1445
     * @return string
1446
     * @throws \EE_Error
1447
     */
1448
    public function get_admin_edit_link()
1449
    {
1450
        return EEH_URL::add_query_args_and_nonce(array(
1451
            'page'   => 'espresso_events',
1452
            'action' => 'edit',
1453
            'post'   => $this->ID(),
1454
        ),
1455
            admin_url('admin.php')
1456
        );
1457
    }
1458
1459
1460
1461
    /**
1462
     * Implementation for EEI_Admin_Links interface method.
1463
     *
1464
     * @see EEI_Admin_Links for comments
1465
     * @return string
1466
     */
1467
    public function get_admin_settings_link()
1468
    {
1469
        return EEH_URL::add_query_args_and_nonce(array(
1470
            'page'   => 'espresso_events',
1471
            'action' => 'default_event_settings',
1472
        ),
1473
            admin_url('admin.php')
1474
        );
1475
    }
1476
1477
1478
1479
    /**
1480
     * Implementation for EEI_Admin_Links interface method.
1481
     *
1482
     * @see EEI_Admin_Links for comments
1483
     * @return string
1484
     */
1485
    public function get_admin_overview_link()
1486
    {
1487
        return EEH_URL::add_query_args_and_nonce(array(
1488
            'page'   => 'espresso_events',
1489
            'action' => 'default',
1490
        ),
1491
            admin_url('admin.php')
1492
        );
1493
    }
1494
1495
}
1496