Completed
Branch BUG-10636-remove-unnecessary-b... (dfa227)
by
unknown
35:02 queued 23:26
created

EE_Event::remove_venue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php use EventEspresso\core\exceptions\InvalidStatusException;
2
3
if (!defined('EVENT_ESPRESSO_VERSION')) {
4
    exit('No direct script access allowed');
5
}
6
7
8
/**
9
 * EE_Event
10
 *
11
 * @package               Event Espresso
12
 * @subpackage            includes/models/
13
 * @author                Mike Nelson
14
 */
15
class EE_Event extends EE_CPT_Base implements EEI_Line_Item_Object, EEI_Admin_Links, EEI_Has_Icon, EEI_Event
16
{
17
18
    /**
19
     * cached value for the the logical active status for the event
20
     *
21
     * @see get_active_status()
22
     * @var string
23
     */
24
    protected $_active_status = '';
25
26
    /**
27
     * This is just used for caching the Primary Datetime for the Event on initial retrieval
28
     *
29
     * @var EE_Datetime
30
     */
31
    protected $_Primary_Datetime;
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
     * @param array $props_n_values incoming values from the database
52
     * @param string $timezone incoming timezone as set by the model.  If not set the timezone for
53
     *                                the website will be used.
54
     * @return EE_Event
55
     * @throws EE_Error
56
     */
57
    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
58
    {
59
        return new self($props_n_values, true, $timezone);
60
    }
61
62
63
    /**
64
     * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
65
     *
66
     * @param string $field_name
67
     * @param mixed $field_value
68
     * @param bool $use_default
69
     * @throws EE_Error
70
     */
71
    public function set($field_name, $field_value, $use_default = false)
72
    {
73
        switch ($field_name) {
74
            case 'status' :
75
                $this->set_status($field_value, $use_default);
76
                break;
77
            default :
78
                parent::set($field_name, $field_value, $use_default);
79
        }
80
    }
81
82
83
    /**
84
     *    set_status
85
     * Checks if event status is being changed to SOLD OUT
86
     * and updates event meta data with previous event status
87
     * so that we can revert things if/when the event is no longer sold out
88
     *
89
     * @access public
90
     * @param string $new_status
91
     * @param bool $use_default
92
     * @return void
93
     * @throws EE_Error
94
     */
95
    public function set_status($new_status = null, $use_default = false)
96
    {
97
        // if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
98
        if (empty($new_status) && !$use_default) {
99
            return;
100
        }
101
        // get current Event status
102
        $old_status = $this->status();
103
        // if status has changed
104
        if ($old_status !== $new_status) {
105
            // TO sold_out
106
            if ($new_status === EEM_Event::sold_out) {
107
                // save the previous event status so that we can revert if the event is no longer sold out
108
                $this->add_post_meta('_previous_event_status', $old_status);
109
                do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $new_status);
110
                // OR FROM  sold_out
111
            } else if ($old_status === EEM_Event::sold_out) {
112
                $this->delete_post_meta('_previous_event_status');
113
                do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $new_status);
114
            }
115
            // update status
116
            parent::set('status', $new_status, $use_default);
117
            do_action('AHEE__EE_Event__set_status__after_update', $this);
118
            return;
119
        }
120
        // even though the old value matches the new value, it's still good to
121
        // allow the parent set method to have a say
122
        parent::set('status', $new_status, $use_default);
123
    }
124
125
126
    /**
127
     * Gets all the datetimes for this event
128
     *
129
     * @param array $query_params like EEM_Base::get_all
130
     * @return EE_Base_Class[]|EE_Datetime[]
131
     * @throws EE_Error
132
     */
133
    public function datetimes($query_params = array())
134
    {
135
        return $this->get_many_related('Datetime', $query_params);
136
    }
137
138
139
    /**
140
     * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
141
     *
142
     * @return EE_Base_Class[]|EE_Datetime[]
143
     * @throws EE_Error
144
     */
145
    public function datetimes_in_chronological_order()
146
    {
147
        return $this->get_many_related('Datetime', array('order_by' => array('DTT_EVT_start' => 'ASC')));
148
    }
149
150
151
    /**
152
     * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
153
     * @darren, we should probably UNSET timezone on the EEM_Datetime model
154
     * after running our query, so that this timezone isn't set for EVERY query
155
     * on EEM_Datetime for the rest of the request, no?
156
     *
157
     * @param boolean $show_expired whether or not to include expired events
158
     * @param boolean $show_deleted whether or not to include deleted events
159
     * @param null $limit
160
     * @return EE_Datetime[]
161
     * @throws EE_Error
162
     */
163
    public function datetimes_ordered($show_expired = true, $show_deleted = false, $limit = null)
164
    {
165
        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
166
            $this->ID(),
167
            $show_expired,
168
            $show_deleted,
169
            $limit
170
        );
171
    }
172
173
174
    /**
175
     * Returns one related datetime. Mostly only used by some legacy code.
176
     *
177
     * @return EE_Base_Class|EE_Datetime
178
     * @throws EE_Error
179
     */
180
    public function first_datetime()
181
    {
182
        return $this->get_first_related('Datetime');
183
    }
184
185
186
    /**
187
     * Returns the 'primary' datetime for the event
188
     *
189
     * @param bool $try_to_exclude_expired
190
     * @param bool $try_to_exclude_deleted
191
     * @return EE_Datetime
192
     * @throws EE_Error
193
     */
194
    public function primary_datetime($try_to_exclude_expired = true, $try_to_exclude_deleted = true)
195
    {
196
        if (!empty ($this->_Primary_Datetime)) {
197
            return $this->_Primary_Datetime;
198
        }
199
        $this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
200
            $this->ID(),
201
            $try_to_exclude_expired,
202
            $try_to_exclude_deleted
203
        );
204
        return $this->_Primary_Datetime;
205
    }
206
207
208
    /**
209
     * Gets all the tickets available for purchase of this event
210
     *
211
     * @param array $query_params like EEM_Base::get_all
212
     * @return EE_Base_Class[]|EE_Ticket[]
213
     * @throws EE_Error
214
     */
215
    public function tickets($query_params = array())
216
    {
217
        //first get all datetimes
218
        $datetimes = $this->datetimes_ordered();
219
        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...
220
            return array();
221
        }
222
        $datetime_ids = array();
223
        foreach ($datetimes as $datetime) {
224
            $datetime_ids[] = $datetime->ID();
225
        }
226
        $where_params = array('Datetime.DTT_ID' => array('IN', $datetime_ids));
227
        //if incoming $query_params has where conditions let's merge but not override existing.
228
        if (is_array($query_params) && isset($query_params[0])) {
229
            $where_params = array_merge($query_params[0], $where_params);
230
            unset($query_params[0]);
231
        }
232
        //now add $where_params to $query_params
233
        $query_params[0] = $where_params;
234
        return EEM_Ticket::instance()->get_all($query_params);
235
    }
236
237
238
    /**
239
     * get all unexpired untrashed tickets
240
     *
241
     * @return EE_Ticket[]
242
     * @throws EE_Error
243
     */
244
    public function active_tickets()
245
    {
246
        return $this->tickets(array(
247
            array(
248
                'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
249
                'TKT_deleted' => false,
250
            ),
251
        ));
252
    }
253
254
255
    /**
256
     * @return bool
257
     * @throws EE_Error
258
     */
259
    public function additional_limit()
260
    {
261
        return $this->get('EVT_additional_limit');
262
    }
263
264
265
    /**
266
     * @return bool
267
     * @throws EE_Error
268
     */
269
    public function allow_overflow()
270
    {
271
        return $this->get('EVT_allow_overflow');
272
    }
273
274
275
    /**
276
     * @return bool
277
     * @throws EE_Error
278
     */
279
    public function created()
280
    {
281
        return $this->get('EVT_created');
282
    }
283
284
285
    /**
286
     * @return bool
287
     * @throws EE_Error
288
     */
289
    public function description()
290
    {
291
        return $this->get('EVT_desc');
292
    }
293
294
295
    /**
296
     * Runs do_shortcode and wpautop on the description
297
     *
298
     * @return string of html
299
     * @throws EE_Error
300
     */
301
    public function description_filtered()
302
    {
303
        return $this->get_pretty('EVT_desc');
304
    }
305
306
307
    /**
308
     * @return bool
309
     * @throws EE_Error
310
     */
311
    public function display_description()
312
    {
313
        return $this->get('EVT_display_desc');
314
    }
315
316
317
    /**
318
     * @return bool
319
     * @throws EE_Error
320
     */
321
    public function display_ticket_selector()
322
    {
323
        return (bool)$this->get('EVT_display_ticket_selector');
324
    }
325
326
327
    /**
328
     * @return bool
329
     * @throws EE_Error
330
     */
331
    public function external_url()
332
    {
333
        return $this->get('EVT_external_URL');
334
    }
335
336
337
    /**
338
     * @return bool
339
     * @throws EE_Error
340
     */
341
    public function member_only()
342
    {
343
        return $this->get('EVT_member_only');
344
    }
345
346
347
    /**
348
     * @return bool
349
     * @throws EE_Error
350
     */
351
    public function phone()
352
    {
353
        return $this->get('EVT_phone');
354
    }
355
356
357
    /**
358
     * @return bool
359
     * @throws EE_Error
360
     */
361
    public function modified()
362
    {
363
        return $this->get('EVT_modified');
364
    }
365
366
367
    /**
368
     * @return bool
369
     * @throws EE_Error
370
     */
371
    public function name()
372
    {
373
        return $this->get('EVT_name');
374
    }
375
376
377
    /**
378
     * @return bool
379
     * @throws EE_Error
380
     */
381
    public function order()
382
    {
383
        return $this->get('EVT_order');
384
    }
385
386
387
    /**
388
     * @return bool|string
389
     * @throws EE_Error
390
     */
391
    public function default_registration_status()
392
    {
393
        $event_default_registration_status = $this->get('EVT_default_registration_status');
394
        return !empty($event_default_registration_status)
395
            ? $event_default_registration_status
396
            : EE_Registry::instance()->CFG->registration->default_STS_ID;
397
    }
398
399
400
    /**
401
     * @param int $num_words
402
     * @param null $more
403
     * @param bool $not_full_desc
404
     * @return bool|string
405
     * @throws EE_Error
406
     */
407
    public function short_description($num_words = 55, $more = null, $not_full_desc = false)
408
    {
409
        $short_desc = $this->get('EVT_short_desc');
410
        if (!empty($short_desc) || $not_full_desc) {
411
            return $short_desc;
412
        }
413
        $full_desc = $this->get('EVT_desc');
414
        return wp_trim_words($full_desc, $num_words, $more);
415
    }
416
417
418
    /**
419
     * @return bool
420
     * @throws EE_Error
421
     */
422
    public function slug()
423
    {
424
        return $this->get('EVT_slug');
425
    }
426
427
428
    /**
429
     * @return bool
430
     * @throws EE_Error
431
     */
432
    public function timezone_string()
433
    {
434
        return $this->get('EVT_timezone_string');
435
    }
436
437
438
    /**
439
     * @return bool
440
     * @throws EE_Error
441
     */
442
    public function visible_on()
443
    {
444
        return $this->get('EVT_visible_on');
445
    }
446
447
448
    /**
449
     * @return int
450
     * @throws EE_Error
451
     */
452
    public function wp_user()
453
    {
454
        return $this->get('EVT_wp_user');
455
    }
456
457
458
    /**
459
     * @return bool
460
     * @throws EE_Error
461
     */
462
    public function donations()
463
    {
464
        return $this->get('EVT_donations');
465
    }
466
467
468
    /**
469
     * @param $limit
470
     * @throws EE_Error
471
     */
472
    public function set_additional_limit($limit)
473
    {
474
        $this->set('EVT_additional_limit', $limit);
475
    }
476
477
478
    /**
479
     * @param $created
480
     * @throws EE_Error
481
     */
482
    public function set_created($created)
483
    {
484
        $this->set('EVT_created', $created);
485
    }
486
487
488
    /**
489
     * @param $desc
490
     * @throws EE_Error
491
     */
492
    public function set_description($desc)
493
    {
494
        $this->set('EVT_desc', $desc);
495
    }
496
497
498
    /**
499
     * @param $display_desc
500
     * @throws EE_Error
501
     */
502
    public function set_display_description($display_desc)
503
    {
504
        $this->set('EVT_display_desc', $display_desc);
505
    }
506
507
508
    /**
509
     * @param $display_ticket_selector
510
     * @throws EE_Error
511
     */
512
    public function set_display_ticket_selector($display_ticket_selector)
513
    {
514
        $this->set('EVT_display_ticket_selector', $display_ticket_selector);
515
    }
516
517
518
    /**
519
     * @param $external_url
520
     * @throws EE_Error
521
     */
522
    public function set_external_url($external_url)
523
    {
524
        $this->set('EVT_external_URL', $external_url);
525
    }
526
527
528
    /**
529
     * @param $member_only
530
     * @throws EE_Error
531
     */
532
    public function set_member_only($member_only)
533
    {
534
        $this->set('EVT_member_only', $member_only);
535
    }
536
537
538
    /**
539
     * @param $event_phone
540
     * @throws EE_Error
541
     */
542
    public function set_event_phone($event_phone)
543
    {
544
        $this->set('EVT_phone', $event_phone);
545
    }
546
547
548
    /**
549
     * @param $modified
550
     * @throws EE_Error
551
     */
552
    public function set_modified($modified)
553
    {
554
        $this->set('EVT_modified', $modified);
555
    }
556
557
558
    /**
559
     * @param $name
560
     * @throws EE_Error
561
     */
562
    public function set_name($name)
563
    {
564
        $this->set('EVT_name', $name);
565
    }
566
567
568
    /**
569
     * @param $order
570
     * @throws EE_Error
571
     */
572
    public function set_order($order)
573
    {
574
        $this->set('EVT_order', $order);
575
    }
576
577
578
    /**
579
     * @param $short_desc
580
     * @throws EE_Error
581
     */
582
    public function set_short_description($short_desc)
583
    {
584
        $this->set('EVT_short_desc', $short_desc);
585
    }
586
587
588
    /**
589
     * @param $slug
590
     * @throws EE_Error
591
     */
592
    public function set_slug($slug)
593
    {
594
        $this->set('EVT_slug', $slug);
595
    }
596
597
598
    /**
599
     * @param $timezone_string
600
     * @throws EE_Error
601
     */
602
    public function set_timezone_string($timezone_string)
603
    {
604
        $this->set('EVT_timezone_string', $timezone_string);
605
    }
606
607
608
    /**
609
     * @param $visible_on
610
     * @throws EE_Error
611
     */
612
    public function set_visible_on($visible_on)
613
    {
614
        $this->set('EVT_visible_on', $visible_on);
615
    }
616
617
618
    /**
619
     * @param $wp_user
620
     * @throws EE_Error
621
     */
622
    public function set_wp_user($wp_user)
623
    {
624
        $this->set('EVT_wp_user', $wp_user);
625
    }
626
627
628
    /**
629
     * @param $default_registration_status
630
     * @throws EE_Error
631
     */
632
    public function set_default_registration_status($default_registration_status)
633
    {
634
        $this->set('EVT_default_registration_status', $default_registration_status);
635
    }
636
637
638
    /**
639
     * @param $donations
640
     * @throws EE_Error
641
     */
642
    public function set_donations($donations)
643
    {
644
        $this->set('EVT_donations', $donations);
645
    }
646
647
648
    /**
649
     * Adds a venue to this event
650
     *
651
     * @param EE_Venue /int $venue_id_or_obj
652
     * @return EE_Base_Class|EE_Venue
653
     * @throws EE_Error
654
     */
655
    public function add_venue($venue_id_or_obj)
656
    {
657
        return $this->_add_relation_to($venue_id_or_obj, 'Venue');
658
    }
659
660
661
    /**
662
     * Removes a venue from the event
663
     *
664
     * @param EE_Venue /int $venue_id_or_obj
665
     * @return EE_Base_Class|EE_Venue
666
     * @throws EE_Error
667
     */
668
    public function remove_venue($venue_id_or_obj)
669
    {
670
        return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
671
    }
672
673
674
    /**
675
     * Gets all the venues related ot the event. May provide additional $query_params if desired
676
     *
677
     * @param array $query_params like EEM_Base::get_all's $query_params
678
     * @return EE_Base_Class[]|EE_Venue[]
679
     * @throws EE_Error
680
     */
681
    public function venues($query_params = array())
682
    {
683
        return $this->get_many_related('Venue', $query_params);
684
    }
685
686
687
    /**
688
     * check if event id is present and if event is published
689
     *
690
     * @access public
691
     * @return boolean true yes, false no
692
     * @throws EE_Error
693
     */
694
    private function _has_ID_and_is_published()
695
    {
696
        // first check if event id is present and not NULL,
697
        // then check if this event is published (or any of the equivalent "published" statuses)
698
        return
699
            $this->ID() && $this->ID() !== null
700
            && (
701
                $this->status() === 'publish'
702
                || $this->status() === EEM_Event::sold_out
703
                || $this->status() === EEM_Event::postponed
704
                || $this->status() === EEM_Event::cancelled
705
            );
706
    }
707
708
709
    /**
710
     * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
711
     *
712
     * @access public
713
     * @return boolean true yes, false no
714
     * @throws EE_Error
715
     */
716 View Code Duplication
    public function is_upcoming()
717
    {
718
        // check if event id is present and if this event is published
719
        if ($this->is_inactive()) {
720
            return false;
721
        }
722
        // set initial value
723
        $upcoming = false;
724
        //next let's get all datetimes and loop through them
725
        $datetimes = $this->datetimes_in_chronological_order();
726
        foreach ($datetimes as $datetime) {
727
            if ($datetime instanceof EE_Datetime) {
728
                //if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
729
                if ($datetime->is_expired()) {
730
                    continue;
731
                }
732
                //if this dtt is active then we return false.
733
                if ($datetime->is_active()) {
734
                    return false;
735
                }
736
                //otherwise let's check upcoming status
737
                $upcoming = $datetime->is_upcoming();
738
            }
739
        }
740
        return $upcoming;
741
    }
742
743
744
    /**
745
     * @return bool
746
     * @throws EE_Error
747
     */
748 View Code Duplication
    public function is_active()
749
    {
750
        // check if event id is present and if this event is published
751
        if ($this->is_inactive()) {
752
            return false;
753
        }
754
        // set initial value
755
        $active = false;
756
        //next let's get all datetimes and loop through them
757
        $datetimes = $this->datetimes_in_chronological_order();
758
        foreach ($datetimes as $datetime) {
759
            if ($datetime instanceof EE_Datetime) {
760
                //if this dtt is expired then we continue cause one of the other datetimes might be active.
761
                if ($datetime->is_expired()) {
762
                    continue;
763
                }
764
                //if this dtt is upcoming then we return false.
765
                if ($datetime->is_upcoming()) {
766
                    return false;
767
                }
768
                //otherwise let's check active status
769
                $active = $datetime->is_active();
770
            }
771
        }
772
        return $active;
773
    }
774
775
776
    /**
777
     * @return bool
778
     * @throws EE_Error
779
     */
780 View Code Duplication
    public function is_expired()
781
    {
782
        // check if event id is present and if this event is published
783
        if ($this->is_inactive()) {
784
            return false;
785
        }
786
        // set initial value
787
        $expired = false;
788
        //first let's get all datetimes and loop through them
789
        $datetimes = $this->datetimes_in_chronological_order();
790
        foreach ($datetimes as $datetime) {
791
            if ($datetime instanceof EE_Datetime) {
792
                //if this dtt is upcoming or active then we return false.
793
                if ($datetime->is_upcoming() || $datetime->is_active()) {
794
                    return false;
795
                }
796
                //otherwise let's check active status
797
                $expired = $datetime->is_expired();
798
            }
799
        }
800
        return $expired;
801
    }
802
803
804
    /**
805
     * @return bool
806
     * @throws EE_Error
807
     */
808
    public function is_inactive()
809
    {
810
        // check if event id is present and if this event is published
811
        if ($this->_has_ID_and_is_published()) {
812
            return false;
813
        }
814
        return true;
815
    }
816
817
818
    /**
819
     * calculate spaces remaining based on "saleable" tickets
820
     *
821
     * @param array $tickets
822
     * @param bool $filtered
823
     * @return int|float
824
     * @throws EE_Error
825
     */
826
    public function spaces_remaining($tickets = array(), $filtered = true)
827
    {
828
        // get all unexpired untrashed tickets if nothing was passed
829
        $tickets = !empty($tickets) ? $tickets : $this->active_tickets();
830
        // set initial value
831
        $spaces_remaining = 0;
832
        if (!empty($tickets)) {
833
            foreach ($tickets as $ticket) {
834
                if ($ticket instanceof EE_Ticket) {
835
                    $spaces_remaining += $ticket->qty('saleable');
836
                }
837
            }
838
        }
839
        return $filtered
840
            ? apply_filters(
841
                'FHEE_EE_Event__spaces_remaining',
842
                $spaces_remaining,
843
                $this,
844
                $tickets
845
            )
846
            : $spaces_remaining;
847
    }
848
849
850
    /**
851
     *    perform_sold_out_status_check
852
     *    checks all of this events's datetime  reg_limit - sold values to determine if ANY datetimes have spaces available...
853
     *    if NOT, then the event status will get toggled to 'sold_out'
854
     *
855
     * @access public
856
     * @return bool    return the ACTUAL sold out state.
857
     * @throws EE_Error
858
     */
859
    public function perform_sold_out_status_check()
860
    {
861
        // get all unexpired untrashed tickets
862
        $tickets = $this->active_tickets();
863
        // if all the tickets are just expired, then don't update the event status to sold out
864
        if (empty($tickets)) {
865
            return true;
866
        }
867
        $spaces_remaining = $this->spaces_remaining($tickets);
868
        if ($spaces_remaining < 1) {
869
            $this->set_status(EEM_Event::sold_out);
870
            $this->save();
871
            $sold_out = true;
872
        } else {
873
            $sold_out = false;
874
            // was event previously marked as sold out ?
875
            if ($this->status() === EEM_Event::sold_out) {
876
                // revert status to previous value, if it was set
877
                $previous_event_status = $this->get_post_meta('_previous_event_status', true);
878
                if ($previous_event_status) {
879
                    $this->set_status($previous_event_status);
880
                    $this->save();
881
                }
882
            }
883
        }
884
        do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
885
        return $sold_out;
886
    }
887
888
889
    /**
890
     * This returns the total remaining spaces for sale on this event.
891
     * ############################
892
     * VERY IMPORTANT FOR DEVELOPERS:
893
     * While included here, this method is still being tested internally, so its signature and behaviour COULD change.
894
     * While this comment block is in place, usage is at your own risk and know that it may change in future builds.
895
     * ############################
896
     *
897
     * @uses EE_Event::total_available_spaces()
898
     * @return float|int  (EE_INF is returned as float)
899
     * @throws InvalidArgumentException
900
     * @throws InvalidStatusException
901
     * @throws EE_Error
902
     */
903
    public function spaces_remaining_for_sale()
904
    {
905
        //first get total available spaces including consideration for tickets that have already sold.
906
        $spaces_available = $this->total_available_spaces(true);
907
        //if total available = 0, then exit right away because that means everything is expired.
908
        if ($spaces_available === 0) {
909
            return 0;
910
        }
911
        //subtract total approved registrations from spaces available to get how many are remaining.
912
        $spots_taken = EEM_Registration::instance()->event_reg_count_for_statuses($this->ID());
913
        $spaces_remaining = $spaces_available - $spots_taken;
914
        return $spaces_remaining > 0 ? $spaces_remaining : 0;
915
    }
916
917
918
    /**
919
     * This returns the total spaces available for an event while considering all the qtys on the tickets and the reg limits
920
     * on the datetimes attached to this event.
921
     * ############################
922
     * VERY IMPORTANT FOR DEVELOPERS:
923
     * While included here, this method is still being tested internally, so its signature and behaviour COULD change. While
924
     * this comment block is in place, usage is at your own risk and know that it may change in future builds.
925
     * ############################
926
     * Note: by "spaces available" we are not returning how many spaces remain.  That is a calculation involving using the value
927
     * from this method and subtracting the approved registrations for the event.
928
     *
929
     * @param   bool $current_total_available Whether to consider any tickets that have already sold in our calculation.
930
     *                                              If this is false, then we return the most tickets that could ever be sold
931
     *                                              for this event with the datetime and tickets setup on the event under optimal
932
     *                                              selling conditions.  Otherwise we return a live calculation of spaces available
933
     *                                              based on tickets sold.  Depending on setup and stage of sales, this
934
     *                                              may appear to equal remaining tickets.  However, the more tickets are
935
     *                                              sold out, the more accurate the "live" total is.
936
     * @return  int|float  (Note: if EE_INF is returned its considered a float by PHP)
937
     * @throws EE_Error
938
     */
939
    public function total_available_spaces($current_total_available = false)
940
    {
941
        $spaces_available = 0;
942
        //first get all tickets on the event and include expired tickets
943
        $tickets = $this->tickets(array('default_where_conditions' => 'none'));
944
        $ticket_sums = array();
945
        $datetimes = array();
946
        $datetime_limits = array();
947
        //loop through tickets and normalize them
948
        foreach ($tickets as $ticket) {
949
            $datetimes = $ticket->datetimes(array('order_by' => array('DTT_reg_limit' => 'ASC')));
950
            if (empty($datetimes)) {
951
                continue;
952
            }
953
            //first datetime should be the lowest datetime
954
            $least_datetime = reset($datetimes);
955
            //lets reset the ticket quantity to be the lower of either the lowest datetime reg limit or the ticket quantity
956
            //IF datetimes sold (and we're not doing current live total available, then use spaces remaining for datetime, not reg_limit.
957
            if ($current_total_available) {
958
                if ($ticket->is_remaining()) {
959
                    $remaining = $ticket->remaining();
960
                } else {
961
                    $spaces_available += $ticket->sold();
962
                    //and we don't cache this ticket to our list because its sold out.
963
                    continue;
964
                }
965
            } else {
966
                $remaining = min($ticket->qty(), $least_datetime->reg_limit());
967
            }
968
            //if $ticket_limit == infinity then let's drop out right away and just return that because any infinity amount trumps all other "available" amounts.
969
            if ($remaining === EE_INF) {
970
                return EE_INF;
971
            }
972
            //multiply normalized $tkt quantity by the number of datetimes on the ticket as the "sum"
973
            //also include the sum of all the datetime reg limits on the ticket for breaking ties.
974
            $ticket_sums[$ticket->ID()]['sum'] = $remaining * count($datetimes);
975
            $ticket_sums[$ticket->ID()]['datetime_sums'] = 0;
976
            foreach ($datetimes as $datetime) {
977
                if ($datetime->reg_limit() === EE_INF) {
978
                    $ticket_sums[$ticket->ID()]['datetime_sums'] = EE_INF;
979
                } else {
980
                    $ticket_sums[$ticket->ID()]['datetime_sums'] += $current_total_available
981
                        ? $datetime->spaces_remaining()
982
                        : $datetime->reg_limit();
983
                }
984
                $datetime_limits[$datetime->ID()] = $current_total_available
985
                    ? $datetime->spaces_remaining()
986
                    : $datetime->reg_limit();
987
            }
988
            $ticket_sums[$ticket->ID()]['ticket'] = $ticket;
989
        }
990
        //The order is sorted by lowest available first (which is calculated for each ticket by multiplying the normalized
991
        //ticket quantity by the number of datetimes on the ticket).  For tie-breakers, then the next sort is based on the
992
        //ticket with the greatest sum of all remaining datetime->spaces_remaining() ( or $datetime->reg_limit() if not
993
        //$current_total_available ) for the datetimes on the ticket.
994
        usort($ticket_sums, function ($a, $b) {
995
            if ($a['sum'] === $b['sum']) {
996
                if ($a['datetime_sums'] === $b['datetime_sums']) {
997
                    return 0;
998
                }
999
                return $a['datetime_sums'] < $b['datetime_sums'] ? 1 : -1;
1000
            }
1001
            return ($a['sum'] < $b['sum']) ? -1 : 1;
1002
        });
1003
        //now let's loop through the sorted tickets and simulate sellouts
1004
        foreach ($ticket_sums as $ticket_info) {
1005
            if ($ticket_info['ticket'] instanceof EE_Ticket) {
1006
                $datetimes = $ticket_info['ticket']->datetimes(array('order_by' => array('DTT_reg_limit' => 'ASC')));
1007
                //need to sort these $datetimes by remaining (only if $current_total_available)
1008
                //setup datetimes for simulation
1009
                $ticket_datetimes_remaining = array();
1010
                foreach ($datetimes as $datetime) {
1011
                    $DTT_ID = $datetime->ID();
1012
                    $ticket_datetimes_remaining[$DTT_ID]['rem'] = $datetime_limits[$DTT_ID];
1013
                    $ticket_datetimes_remaining[$DTT_ID]['datetime'] = $datetime;
1014
                }
1015 View Code Duplication
                usort($ticket_datetimes_remaining, function ($a, $b) {
1016
                    if ($a['rem'] === $b['rem']) {
1017
                        return 0;
1018
                    }
1019
                    return ($a['rem'] < $b['rem']) ? -1 : 1;
1020
                });
1021
                //get the remaining on the first datetime (which should be the one with the least remaining) and that is
1022
                //what we add to the spaces_available running total.  Then we need to decrease the remaining on our datetime tracker.
1023
                $lowest_datetime = reset($ticket_datetimes_remaining);
1024
                //need to get the lower of; what the remaining is on the lowest datetime, and the remaining on the ticket.
1025
                // If this ends up being 0 (because of previous tickets in our simulation selling out), then it has already
1026
                // been tracked on $spaces available and this ticket is now sold out for the simulation, so we can continue
1027
                // to the next ticket.
1028
                if ($current_total_available) {
1029
                    $remaining = min($lowest_datetime['rem'], $ticket_info['ticket']->remaining());
1030
                } else {
1031
                    $remaining = min($lowest_datetime['rem'], $ticket_info['ticket']->qty());
1032
                }
1033
                //if $remaining is infinite that means that all datetimes on this ticket are infinite but we've made it here because all
1034
                //tickets have a quantity.  So we don't have to track datetimes, we can just use ticket quantities for total
1035
                //available.
1036
                if ($remaining === EE_INF) {
1037
                    $spaces_available += $ticket_info['ticket']->qty();
1038
                    continue;
1039
                }
1040
                //if ticket has sold amounts then we also need to add that (but only if doing live counts)
1041
                if ($current_total_available) {
1042
                    $spaces_available += $ticket_info['ticket']->sold();
1043
                }
1044
                if ($remaining <= 0) {
1045
                    continue;
1046
                }
1047
                $spaces_available += $remaining;
1048
                //loop through the datetimes and sell them out!
1049
                foreach ($ticket_datetimes_remaining as $datetime_info) {
1050
                    if ($datetime_info['datetime'] instanceof EE_Datetime) {
1051
                        $datetime_limits[$datetime_info['datetime']->ID()] += -$remaining;
1052
                    }
1053
                }
1054
            }
1055
        }
1056
        return apply_filters(
1057
            'FHEE_EE_Event__total_available_spaces__spaces_available',
1058
            $spaces_available,
1059
            $this,
1060
            $datetimes,
1061
            $tickets
1062
        );
1063
    }
1064
1065
1066
    /**
1067
     * Checks if the event is set to sold out
1068
     *
1069
     * @param  bool $actual whether or not to perform calculations to not only figure the
1070
     *                      actual status but also to flip the status if necessary to sold
1071
     *                      out If false, we just check the existing status of the event
1072
     * @return boolean
1073
     * @throws EE_Error
1074
     */
1075
    public function is_sold_out($actual = false)
1076
    {
1077
        if (!$actual) {
1078
            return $this->status() === EEM_Event::sold_out;
1079
        }
1080
        return $this->perform_sold_out_status_check();
1081
    }
1082
1083
1084
    /**
1085
     * Checks if the event is marked as postponed
1086
     *
1087
     * @return boolean
1088
     */
1089
    public function is_postponed()
1090
    {
1091
        return $this->status() === EEM_Event::postponed;
1092
    }
1093
1094
1095
    /**
1096
     * Checks if the event is marked as cancelled
1097
     *
1098
     * @return boolean
1099
     */
1100
    public function is_cancelled()
1101
    {
1102
        return $this->status() === EEM_Event::cancelled;
1103
    }
1104
1105
1106
    /**
1107
     * Get the logical active status in a hierarchical order for all the datetimes.  Note
1108
     * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1109
     * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1110
     * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1111
     * the event is considered expired.
1112
     * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a status
1113
     * set on the EVENT when it is not published and thus is done
1114
     *
1115
     * @param bool $reset
1116
     * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1117
     * @throws EE_Error
1118
     */
1119
    public function get_active_status($reset = false)
1120
    {
1121
        // if the active status has already been set, then just use that value (unless we are resetting it)
1122
        if (!empty($this->_active_status) && !$reset) {
1123
            return $this->_active_status;
1124
        }
1125
        //first check if event id is present on this object
1126
        if (!$this->ID()) {
1127
            return false;
1128
        }
1129
        $where_params_for_event = array(array('EVT_ID' => $this->ID()));
1130
        //if event is published:
1131
        if ($this->status() === 'publish') {
1132
            //active?
1133
            if (EEM_Datetime::instance()->get_datetime_count_for_status(EE_Datetime::active, $where_params_for_event) > 0) {
1134
                $this->_active_status = EE_Datetime::active;
1135
            } else {
1136
                //upcoming?
1137
                if (EEM_Datetime::instance()->get_datetime_count_for_status(EE_Datetime::upcoming, $where_params_for_event) > 0) {
1138
                    $this->_active_status = EE_Datetime::upcoming;
1139
                } else {
1140
                    //expired?
1141
                    if (
1142
                        EEM_Datetime::instance()->get_datetime_count_for_status(EE_Datetime::expired, $where_params_for_event) > 0
1143
                    ) {
1144
                        $this->_active_status = EE_Datetime::expired;
1145
                    } else {
1146
                        //it would be odd if things make it this far because it basically means there are no datetime's
1147
                        //attached to the event.  So in this case it will just be considered inactive.
1148
                        $this->_active_status = EE_Datetime::inactive;
1149
                    }
1150
                }
1151
            }
1152
        } else {
1153
            //the event is not published, so let's just set it's active status according to its' post status
1154
            switch ($this->status()) {
1155
                case EEM_Event::sold_out :
1156
                    $this->_active_status = EE_Datetime::sold_out;
1157
                    break;
1158
                case EEM_Event::cancelled :
1159
                    $this->_active_status = EE_Datetime::cancelled;
1160
                    break;
1161
                case EEM_Event::postponed :
1162
                    $this->_active_status = EE_Datetime::postponed;
1163
                    break;
1164
                default :
1165
                    $this->_active_status = EE_Datetime::inactive;
1166
            }
1167
        }
1168
        return $this->_active_status;
1169
    }
1170
1171
1172
    /**
1173
     *    pretty_active_status
1174
     *
1175
     * @access public
1176
     * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1177
     * @return mixed void|string
1178
     * @throws EE_Error
1179
     */
1180
    public function pretty_active_status($echo = true)
1181
    {
1182
        $active_status = $this->get_active_status();
1183
        $status = '<span class="ee-status event-active-status-'
1184
            . $active_status
1185
            . '">'
1186
            . EEH_Template::pretty_status($active_status, false, 'sentence')
1187
            . '</span>';
1188
        if ($echo) {
1189
            echo $status;
1190
            return '';
1191
        }
1192
        return $status;
1193
    }
1194
1195
1196
    /**
1197
     * @return bool|int
1198
     * @throws EE_Error
1199
     */
1200
    public function get_number_of_tickets_sold()
1201
    {
1202
        $tkt_sold = 0;
1203
        if (!$this->ID()) {
1204
            return 0;
1205
        }
1206
        $datetimes = $this->datetimes();
1207
        foreach ($datetimes as $datetime) {
1208
            if ($datetime instanceof EE_Datetime) {
1209
                $tkt_sold += $datetime->sold();
1210
            }
1211
        }
1212
        return $tkt_sold;
1213
    }
1214
1215
1216
    /**
1217
     * This just returns a count of all the registrations for this event
1218
     *
1219
     * @access  public
1220
     * @return int
1221
     * @throws EE_Error
1222
     */
1223
    public function get_count_of_all_registrations()
1224
    {
1225
        return EEM_Event::instance()->count_related($this, 'Registration');
1226
    }
1227
1228
1229
    /**
1230
     * This returns the ticket with the earliest start time that is
1231
     * available for this event (across all datetimes attached to the event)
1232
     *
1233
     * @return EE_Base_Class|EE_Ticket|null
1234
     * @throws EE_Error
1235
     */
1236
    public function get_ticket_with_earliest_start_time()
1237
    {
1238
        $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...
1239
        $query_params = array($where, 'order_by' => array('TKT_start_date' => 'ASC'));
1240
        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1241
    }
1242
1243
1244
    /**
1245
     * This returns the ticket with the latest end time that is available
1246
     * for this event (across all datetimes attached to the event)
1247
     *
1248
     * @return EE_Base_Class|EE_Ticket|null
1249
     * @throws EE_Error
1250
     */
1251
    public function get_ticket_with_latest_end_time()
1252
    {
1253
        $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...
1254
        $query_params = array($where, 'order_by' => array('TKT_end_date' => 'DESC'));
1255
        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1256
    }
1257
1258
1259
    /**
1260
     * This returns whether there are any tickets on sale for this event.
1261
     *
1262
     * @return bool true = YES tickets on sale.
1263
     * @throws EE_Error
1264
     */
1265
    public function tickets_on_sale()
1266
    {
1267
        $earliest_ticket = $this->get_ticket_with_earliest_start_time();
1268
        $latest_ticket = $this->get_ticket_with_latest_end_time();
1269
        if (!$latest_ticket instanceof EE_Ticket && !$earliest_ticket instanceof EE_Ticket) {
1270
            return false;
1271
        }
1272
        //check on sale for these two tickets.
1273
        if ($latest_ticket->is_on_sale() || $earliest_ticket->is_on_sale()) {
1274
            return true;
1275
        }
1276
        return false;
1277
    }
1278
1279
1280
    /**
1281
     * Gets the URL for viewing this event on the front-end. Overrides parent
1282
     * to check for an external URL first
1283
     *
1284
     * @return string
1285
     * @throws EE_Error
1286
     */
1287
    public function get_permalink()
1288
    {
1289
        if ($this->external_url()) {
1290
            return $this->external_url();
1291
        }
1292
        return parent::get_permalink();
1293
    }
1294
1295
1296
    /**
1297
     * Gets the first term for 'espresso_event_categories' we can find
1298
     *
1299
     * @param array $query_params like EEM_Base::get_all
1300
     * @return EE_Base_Class|EE_Term|null
1301
     * @throws EE_Error
1302
     */
1303
    public function first_event_category($query_params = array())
1304
    {
1305
        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1306
        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1307
        return EEM_Term::instance()->get_one($query_params);
1308
    }
1309
1310
1311
    /**
1312
     * Gets all terms for 'espresso_event_categories' we can find
1313
     *
1314
     * @param array $query_params
1315
     * @return EE_Base_Class[]|EE_Term[]
1316
     * @throws EE_Error
1317
     */
1318
    public function get_all_event_categories($query_params = array())
1319
    {
1320
        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1321
        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1322
        return EEM_Term::instance()->get_all($query_params);
1323
    }
1324
1325
1326
    /**
1327
     * Gets all the question groups, ordering them by QSG_order ascending
1328
     *
1329
     * @param array $query_params @see EEM_Base::get_all
1330
     * @return EE_Base_Class[]|EE_Question_Group[]
1331
     * @throws EE_Error
1332
     */
1333
    public function question_groups($query_params = array())
1334
    {
1335
        $query_params = !empty($query_params) ? $query_params : array('order_by' => array('QSG_order' => 'ASC'));
1336
        return $this->get_many_related('Question_Group', $query_params);
1337
    }
1338
1339
1340
    /**
1341
     * Implementation for EEI_Has_Icon interface method.
1342
     *
1343
     * @see EEI_Visual_Representation for comments
1344
     * @return string
1345
     */
1346
    public function get_icon()
1347
    {
1348
        return '<span class="dashicons dashicons-flag"></span>';
1349
    }
1350
1351
1352
    /**
1353
     * Implementation for EEI_Admin_Links interface method.
1354
     *
1355
     * @see EEI_Admin_Links for comments
1356
     * @return string
1357
     * @throws EE_Error
1358
     */
1359
    public function get_admin_details_link()
1360
    {
1361
        return $this->get_admin_edit_link();
1362
    }
1363
1364
1365
    /**
1366
     * Implementation for EEI_Admin_Links interface method.
1367
     *
1368
     * @see EEI_Admin_Links for comments
1369
     * @return string
1370
     * @throws EE_Error
1371
     */
1372
    public function get_admin_edit_link()
1373
    {
1374
        return EEH_URL::add_query_args_and_nonce(array(
1375
            'page' => 'espresso_events',
1376
            'action' => 'edit',
1377
            'post' => $this->ID(),
1378
        ),
1379
            admin_url('admin.php')
1380
        );
1381
    }
1382
1383
1384
    /**
1385
     * Implementation for EEI_Admin_Links interface method.
1386
     *
1387
     * @see EEI_Admin_Links for comments
1388
     * @return string
1389
     */
1390
    public function get_admin_settings_link()
1391
    {
1392
        return EEH_URL::add_query_args_and_nonce(array(
1393
            'page' => 'espresso_events',
1394
            'action' => 'default_event_settings',
1395
        ),
1396
            admin_url('admin.php')
1397
        );
1398
    }
1399
1400
1401
    /**
1402
     * Implementation for EEI_Admin_Links interface method.
1403
     *
1404
     * @see EEI_Admin_Links for comments
1405
     * @return string
1406
     */
1407
    public function get_admin_overview_link()
1408
    {
1409
        return EEH_URL::add_query_args_and_nonce(array(
1410
            'page' => 'espresso_events',
1411
            'action' => 'default',
1412
        ),
1413
            admin_url('admin.php')
1414
        );
1415
    }
1416
1417
}
1418