Completed
Branch FET/extract-doc-block-from-eem... (b14d68)
by
unknown
08:28 queued 21s
created
core/db_models/EEM_Registration.model.php 1 patch
Indentation   +866 added lines, -866 removed lines patch added patch discarded remove patch
@@ -13,815 +13,815 @@  discard block
 block discarded – undo
13 13
 class EEM_Registration extends EEM_Soft_Delete_Base
14 14
 {
15 15
 
16
-    /**
17
-     * @var EEM_Registration $_instance
18
-     */
19
-    protected static $_instance;
20
-
21
-    /**
22
-     * Keys are the status IDs for registrations (eg, RAP, RCN, etc), and the values
23
-     * are status codes (eg, approved, cancelled, etc)
24
-     *
25
-     * @var array
26
-     */
27
-    private static $_reg_status;
28
-
29
-    /**
30
-     * The value of REG_count for a primary registrant
31
-     */
32
-    const PRIMARY_REGISTRANT_COUNT = 1;
33
-
34
-    /**
35
-     * Status ID (STS_ID on esp_status table) to indicate an INCOMPLETE registration.
36
-     * Initial status for registrations when they are first created
37
-     * Payments are NOT allowed.
38
-     * Automatically toggled to whatever the default Event registration status is upon completion of the attendee
39
-     * information reg step NO space reserved. Registration is NOT active
40
-     */
41
-    const status_id_incomplete = 'RIC';
42
-
43
-    /**
44
-     * Status ID (STS_ID on esp_status table) to indicate an UNAPPROVED registration.
45
-     * Payments are NOT allowed.
46
-     * Event Admin must manually toggle STS_ID for it to change
47
-     * No space reserved.
48
-     * Registration is active
49
-     */
50
-    const status_id_not_approved = 'RNA';
51
-
52
-    /**
53
-     * Status ID (STS_ID on esp_status table) to indicate registration is PENDING_PAYMENT .
54
-     * Payments are allowed.
55
-     * STS_ID will automatically be toggled to RAP if payment is made in full by the attendee
56
-     * No space reserved.
57
-     * Registration is active
58
-     */
59
-    const status_id_pending_payment = 'RPP';
60
-
61
-    /**
62
-     * Status ID (STS_ID on esp_status table) to indicate registration is on the WAIT_LIST .
63
-     * Payments are allowed.
64
-     * STS_ID will automatically be toggled to RAP if payment is made in full by the attendee
65
-     * No space reserved.
66
-     * Registration is active
67
-     */
68
-    const status_id_wait_list = 'RWL';
69
-
70
-    /**
71
-     * Status ID (STS_ID on esp_status table) to indicate an APPROVED registration.
72
-     * the TXN may or may not be completed ( paid in full )
73
-     * Payments are allowed.
74
-     * A space IS reserved.
75
-     * Registration is active
76
-     */
77
-    const status_id_approved = 'RAP';
78
-
79
-    /**
80
-     * Status ID (STS_ID on esp_status table) to indicate a registration was CANCELLED by the attendee.
81
-     * Payments are NOT allowed.
82
-     * NO space reserved.
83
-     * Registration is NOT active
84
-     */
85
-    const status_id_cancelled = 'RCN';
86
-
87
-    /**
88
-     * Status ID (STS_ID on esp_status table) to indicate a registration was DECLINED by the Event Admin
89
-     * Payments are NOT allowed.
90
-     * No space reserved.
91
-     * Registration is NOT active
92
-     */
93
-    const status_id_declined = 'RDC';
94
-
95
-    /**
96
-     * @var TableAnalysis $table_analysis
97
-     */
98
-    protected $_table_analysis;
99
-
100
-
101
-    /**
102
-     *    private constructor to prevent direct creation
103
-     *
104
-     * @Constructor
105
-     * @access protected
106
-     * @param string $timezone string representing the timezone we want to set for returned Date Time Strings (and any
107
-     *                         incoming timezone data that gets saved). Note this just sends the timezone info to the
108
-     *                         date time model field objects.  Default is NULL (and will be assumed using the set
109
-     *                         timezone in the 'timezone_string' wp option)
110
-     * @throws EE_Error
111
-     */
112
-    protected function __construct($timezone = null)
113
-    {
114
-        $this->_table_analysis = EE_Registry::instance()->create('TableAnalysis', array(), true);
115
-        $this->singular_item = esc_html__('Registration', 'event_espresso');
116
-        $this->plural_item = esc_html__('Registrations', 'event_espresso');
117
-        $this->_tables = array(
118
-            'Registration' => new EE_Primary_Table('esp_registration', 'REG_ID'),
119
-        );
120
-        $this->_fields = array(
121
-            'Registration' => array(
122
-                'REG_ID' => new EE_Primary_Key_Int_Field(
123
-                    'REG_ID',
124
-                    esc_html__('Registration ID', 'event_espresso')
125
-                ),
126
-                'EVT_ID' => new EE_Foreign_Key_Int_Field(
127
-                    'EVT_ID',
128
-                    esc_html__('Event ID', 'event_espresso'),
129
-                    false,
130
-                    0,
131
-                    'Event'
132
-                ),
133
-                'ATT_ID' => new EE_Foreign_Key_Int_Field(
134
-                    'ATT_ID',
135
-                    esc_html__('Attendee ID', 'event_espresso'),
136
-                    false,
137
-                    0,
138
-                    'Attendee'
139
-                ),
140
-                'TXN_ID' => new EE_Foreign_Key_Int_Field(
141
-                    'TXN_ID',
142
-                    esc_html__('Transaction ID', 'event_espresso'),
143
-                    false,
144
-                    0,
145
-                    'Transaction'
146
-                ),
147
-                'TKT_ID' => new EE_Foreign_Key_Int_Field(
148
-                    'TKT_ID',
149
-                    esc_html__('Ticket ID', 'event_espresso'),
150
-                    false,
151
-                    0,
152
-                    'Ticket'
153
-                ),
154
-                'STS_ID' => new EE_Foreign_Key_String_Field(
155
-                    'STS_ID',
156
-                    esc_html__('Status ID', 'event_espresso'),
157
-                    false,
158
-                    EEM_Registration::status_id_incomplete,
159
-                    'Status'
160
-                ),
161
-                'REG_date' => new EE_Datetime_Field(
162
-                    'REG_date',
163
-                    esc_html__('Time registration occurred', 'event_espresso'),
164
-                    false,
165
-                    EE_Datetime_Field::now,
166
-                    $timezone
167
-                ),
168
-                'REG_final_price' => new EE_Money_Field(
169
-                    'REG_final_price',
170
-                    esc_html__('Registration\'s share of the transaction total', 'event_espresso'),
171
-                    false,
172
-                    0
173
-                ),
174
-                'REG_paid' => new EE_Money_Field(
175
-                    'REG_paid',
176
-                    esc_html__('Amount paid to date towards registration', 'event_espresso'),
177
-                    false,
178
-                    0
179
-                ),
180
-                'REG_session' => new EE_Plain_Text_Field(
181
-                    'REG_session',
182
-                    esc_html__('Session ID of registration', 'event_espresso'),
183
-                    false,
184
-                    ''
185
-                ),
186
-                'REG_code' => new EE_Plain_Text_Field(
187
-                    'REG_code',
188
-                    esc_html__('Unique Code for this registration', 'event_espresso'),
189
-                    false,
190
-                    ''
191
-                ),
192
-                'REG_url_link' => new EE_Plain_Text_Field(
193
-                    'REG_url_link',
194
-                    esc_html__('String to be used in URL for identifying registration', 'event_espresso'),
195
-                    false,
196
-                    ''
197
-                ),
198
-                'REG_count' => new EE_Integer_Field(
199
-                    'REG_count',
200
-                    esc_html__('Count of this registration in the group registration ', 'event_espresso'),
201
-                    true,
202
-                    1
203
-                ),
204
-                'REG_group_size' => new EE_Integer_Field(
205
-                    'REG_group_size',
206
-                    esc_html__('Number of registrations on this group', 'event_espresso'),
207
-                    false,
208
-                    1
209
-                ),
210
-                'REG_att_is_going' => new EE_Boolean_Field(
211
-                    'REG_att_is_going',
212
-                    esc_html__('Flag indicating the registrant plans on attending', 'event_espresso'),
213
-                    false,
214
-                    false
215
-                ),
216
-                'REG_deleted' => new EE_Trashed_Flag_Field(
217
-                    'REG_deleted',
218
-                    esc_html__('Flag indicating if registration has been archived or not.', 'event_espresso'),
219
-                    false,
220
-                    false
221
-                ),
222
-            ),
223
-        );
224
-        $this->_model_relations = array(
225
-            'Event' => new EE_Belongs_To_Relation(),
226
-            'Attendee' => new EE_Belongs_To_Relation(),
227
-            'Transaction' => new EE_Belongs_To_Relation(),
228
-            'Ticket' => new EE_Belongs_To_Relation(),
229
-            'Status' => new EE_Belongs_To_Relation(),
230
-            'Answer' => new EE_Has_Many_Relation(),
231
-            'Checkin' => new EE_Has_Many_Relation(),
232
-            'Registration_Payment' => new EE_Has_Many_Relation(),
233
-            'Payment' => new EE_HABTM_Relation('Registration_Payment'),
234
-            'Message' => new EE_Has_Many_Any_Relation(false)
235
-            // allow deletes even if there are messages in the queue related
236
-        );
237
-        $this->_model_chain_to_wp_user = 'Event';
238
-        parent::__construct($timezone);
239
-    }
240
-
241
-
242
-    /**
243
-     * a list of ALL valid registration statuses currently in use within the system
244
-     * generated by combining the filterable active and inactive reg status arrays
245
-     *
246
-     * @return array
247
-     */
248
-    public static function reg_statuses()
249
-    {
250
-        return array_unique(
251
-            array_merge(
252
-                EEM_Registration::active_reg_statuses(),
253
-                EEM_Registration::inactive_reg_statuses()
254
-            )
255
-        );
256
-    }
257
-
258
-
259
-    /**
260
-     * reg_statuses_that_allow_payment
261
-     * a filterable list of registration statuses that allow a registrant to make a payment
262
-     *
263
-     * @access public
264
-     * @return array
265
-     */
266
-    public static function reg_statuses_that_allow_payment()
267
-    {
268
-        return apply_filters(
269
-            'FHEE__EEM_Registration__reg_statuses_that_allow_payment',
270
-            array(
271
-                EEM_Registration::status_id_approved,
272
-                EEM_Registration::status_id_pending_payment,
273
-            )
274
-        );
275
-    }
276
-
277
-
278
-    /**
279
-     * active_reg_statuses
280
-     * a filterable list of registration statuses that are considered active
281
-     *
282
-     * @access public
283
-     * @return array
284
-     */
285
-    public static function active_reg_statuses()
286
-    {
287
-        return apply_filters(
288
-            'FHEE__EEM_Registration__active_reg_statuses',
289
-            array(
290
-                EEM_Registration::status_id_approved,
291
-                EEM_Registration::status_id_pending_payment,
292
-                EEM_Registration::status_id_wait_list,
293
-                EEM_Registration::status_id_not_approved,
294
-            )
295
-        );
296
-    }
297
-
298
-
299
-    /**
300
-     * inactive_reg_statuses
301
-     * a filterable list of registration statuses that are not considered active
302
-     *
303
-     * @access public
304
-     * @return array
305
-     */
306
-    public static function inactive_reg_statuses()
307
-    {
308
-        return apply_filters(
309
-            'FHEE__EEM_Registration__inactive_reg_statuses',
310
-            array(
311
-                EEM_Registration::status_id_incomplete,
312
-                EEM_Registration::status_id_cancelled,
313
-                EEM_Registration::status_id_declined,
314
-            )
315
-        );
316
-    }
317
-
318
-
319
-    /**
320
-     *    closed_reg_statuses
321
-     *    a filterable list of registration statuses that are considered "closed"
322
-     * meaning they should not be considered in any calculations involving monies owing
323
-     *
324
-     * @access public
325
-     * @return array
326
-     */
327
-    public static function closed_reg_statuses()
328
-    {
329
-        return apply_filters(
330
-            'FHEE__EEM_Registration__closed_reg_statuses',
331
-            array(
332
-                EEM_Registration::status_id_cancelled,
333
-                EEM_Registration::status_id_declined,
334
-                EEM_Registration::status_id_wait_list,
335
-            )
336
-        );
337
-    }
338
-
339
-
340
-    /**
341
-     *        get list of registration statuses
342
-     *
343
-     * @access public
344
-     * @param array $exclude The status ids to exclude from the returned results
345
-     * @param bool $translated If true will return the values as singular localized strings
346
-     * @return array
347
-     * @throws EE_Error
348
-     */
349
-    public static function reg_status_array($exclude = array(), $translated = false)
350
-    {
351
-        EEM_Registration::instance()->_get_registration_status_array($exclude);
352
-        return $translated
353
-            ? EEM_Status::instance()->localized_status(self::$_reg_status, false, 'sentence')
354
-            : self::$_reg_status;
355
-    }
356
-
357
-
358
-    /**
359
-     *    get list of registration statuses
360
-     *
361
-     * @access private
362
-     * @param array $exclude
363
-     * @return void
364
-     * @throws EE_Error
365
-     */
366
-    private function _get_registration_status_array($exclude = array())
367
-    {
368
-        // in the very rare circumstance that we are deleting a model's table's data
369
-        // and the table hasn't actually been created, this could have an error
370
-        /** @type WPDB $wpdb */
371
-        global $wpdb;
372
-        if ($this->_get_table_analysis()->tableExists($wpdb->prefix . 'esp_status')) {
373
-            $results = $wpdb->get_results(
374
-                "SELECT STS_ID, STS_code FROM {$wpdb->prefix}esp_status WHERE STS_type = 'registration'"
375
-            );
376
-            self::$_reg_status = array();
377
-            foreach ($results as $status) {
378
-                if (!in_array($status->STS_ID, $exclude, true)) {
379
-                    self::$_reg_status[ $status->STS_ID ] = $status->STS_code;
380
-                }
381
-            }
382
-        }
383
-    }
384
-
385
-
386
-    /**
387
-     * Gets the injected table analyzer, or throws an exception
388
-     *
389
-     * @return TableAnalysis
390
-     * @throws EE_Error
391
-     */
392
-    protected function _get_table_analysis()
393
-    {
394
-        if ($this->_table_analysis instanceof TableAnalysis) {
395
-            return $this->_table_analysis;
396
-        }
397
-        throw new EE_Error(
398
-            sprintf(
399
-                esc_html__('Table analysis class on class %1$s is not set properly.', 'event_espresso'),
400
-                get_class($this)
401
-            )
402
-        );
403
-    }
404
-
405
-
406
-    /**
407
-     * This returns a wpdb->results array of all registration date month and years matching the incoming query params
408
-     * and grouped by month and year.
409
-     *
410
-     * @param  array $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
411
-     * @return array
412
-     * @throws EE_Error
413
-     */
414
-    public function get_reg_months_and_years($where_params)
415
-    {
416
-        $query_params[0] = $where_params;
417
-        $query_params['group_by'] = array('reg_year', 'reg_month');
418
-        $query_params['order_by'] = array('REG_date' => 'DESC');
419
-        $columns_to_select = array(
420
-            'reg_year' => array('YEAR(REG_date)', '%s'),
421
-            'reg_month' => array('MONTHNAME(REG_date)', '%s'),
422
-        );
423
-        return $this->_get_all_wpdb_results($query_params, OBJECT, $columns_to_select);
424
-    }
425
-
426
-
427
-    /**
428
-     * retrieve ALL registrations for a particular Attendee from db
429
-     *
430
-     * @param int $ATT_ID
431
-     * @return EE_Base_Class[]|EE_Registration[]|null
432
-     * @throws EE_Error
433
-     */
434
-    public function get_all_registrations_for_attendee($ATT_ID = 0)
435
-    {
436
-        if (!$ATT_ID) {
437
-            return null;
438
-        }
439
-        return $this->get_all(array(array('ATT_ID' => $ATT_ID)));
440
-    }
441
-
442
-
443
-    /**
444
-     * Gets a registration given their REG_url_link. Yes, this should usually
445
-     * be passed via a GET parameter.
446
-     *
447
-     * @param string $REG_url_link
448
-     * @return EE_Base_Class|EE_Registration|null
449
-     * @throws EE_Error
450
-     */
451
-    public function get_registration_for_reg_url_link($REG_url_link)
452
-    {
453
-        if (!$REG_url_link) {
454
-            return null;
455
-        }
456
-        return $this->get_one(array(array('REG_url_link' => $REG_url_link)));
457
-    }
458
-
459
-
460
-    /**
461
-     *        retrieve registration for a specific transaction attendee from db
462
-     *
463
-     * @access        public
464
-     * @param    int $TXN_ID
465
-     * @param    int $ATT_ID
466
-     * @param    int $att_nmbr in case the ATT_ID is the same for multiple registrations (same details used) then the
467
-     *                         attendee number is required
468
-     * @return        mixed        array on success, FALSE on fail
469
-     * @throws EE_Error
470
-     */
471
-    public function get_registration_for_transaction_attendee($TXN_ID = 0, $ATT_ID = 0, $att_nmbr = 0)
472
-    {
473
-        return $this->get_one(array(
474
-            array(
475
-                'TXN_ID' => $TXN_ID,
476
-                'ATT_ID' => $ATT_ID,
477
-            ),
478
-            'limit' => array(min($att_nmbr - 1, 0), 1),
479
-        ));
480
-    }
481
-
482
-
483
-    /**
484
-     *        get the number of registrations per day  for the Registration Admin page Reports Tab.
485
-     *        (doesn't utilize models because it's a fairly specialized query)
486
-     *
487
-     * @access        public
488
-     * @param $period string which can be passed to php's strtotime function (eg "-1 month")
489
-     * @return stdClass[] with properties regDate and total
490
-     * @throws EE_Error
491
-     */
492
-    public function get_registrations_per_day_report($period = '-1 month')
493
-    {
494
-        $sql_date = $this->convert_datetime_for_query(
495
-            'REG_date',
496
-            date('Y-m-d H:i:s', strtotime($period)),
497
-            'Y-m-d H:i:s',
498
-            'UTC'
499
-        );
500
-        $where = array(
501
-            'REG_date' => array('>=', $sql_date),
502
-            'STS_ID' => array('!=', EEM_Registration::status_id_incomplete),
503
-        );
504
-        if (!EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_day_report')) {
505
-            $where['Event.EVT_wp_user'] = get_current_user_id();
506
-        }
507
-        $query_interval = EEH_DTT_Helper::get_sql_query_interval_for_offset($this->get_timezone(), 'REG_date');
508
-        $results = $this->_get_all_wpdb_results(
509
-            array(
510
-                $where,
511
-                'group_by' => 'regDate',
512
-                'order_by' => array('REG_date' => 'ASC'),
513
-            ),
514
-            OBJECT,
515
-            array(
516
-                'regDate' => array('DATE(' . $query_interval . ')', '%s'),
517
-                'total' => array('count(REG_ID)', '%d'),
518
-            )
519
-        );
520
-        return $results;
521
-    }
522
-
523
-
524
-    /**
525
-     * Get the number of registrations per day including the count of registrations for each Registration Status.
526
-     * Note: EEM_Registration::status_id_incomplete registrations are excluded from the results.
527
-     *
528
-     * @param string $period
529
-     * @return stdClass[] with properties Registration_REG_date and a column for each registration status as the STS_ID
530
-     * @throws EE_Error
531
-     *                    (i.e. RAP)
532
-     */
533
-    public function get_registrations_per_day_and_per_status_report($period = '-1 month')
534
-    {
535
-        global $wpdb;
536
-        $registration_table = $wpdb->prefix . 'esp_registration';
537
-        $event_table = $wpdb->posts;
538
-        $sql_date = date('Y-m-d H:i:s', strtotime($period));
539
-        // prepare the query interval for displaying offset
540
-        $query_interval = EEH_DTT_Helper::get_sql_query_interval_for_offset($this->get_timezone(), 'dates.REG_date');
541
-        // inner date query
542
-        $inner_date_query = "SELECT DISTINCT REG_date from {$registration_table} ";
543
-        $inner_where = ' WHERE';
544
-        // exclude events not authored by user if permissions in effect
545
-        if (!EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_event_report')) {
546
-            $inner_date_query .= "LEFT JOIN {$event_table} ON ID = EVT_ID";
547
-            $inner_where .= ' post_author = ' . get_current_user_id() . ' AND';
548
-        }
549
-        $inner_where .= " REG_date >= '{$sql_date}'";
550
-        $inner_date_query .= $inner_where;
551
-        // start main query
552
-        $select = "SELECT DATE({$query_interval}) as Registration_REG_date, ";
553
-        $join = '';
554
-        $join_parts = array();
555
-        $select_parts = array();
556
-        // loop through registration stati to do parts for each status.
557
-        foreach (EEM_Registration::reg_status_array() as $STS_ID => $STS_code) {
558
-            if ($STS_ID === EEM_Registration::status_id_incomplete) {
559
-                continue;
560
-            }
561
-            $select_parts[] = "COUNT({$STS_code}.REG_ID) as {$STS_ID}";
562
-            $join_parts[] = "{$registration_table} AS {$STS_code} ON {$STS_code}.REG_date = dates.REG_date AND {$STS_code}.STS_ID = '{$STS_ID}'";
563
-        }
564
-        // setup the selects
565
-        $select .= implode(', ', $select_parts);
566
-        $select .= " FROM ($inner_date_query) AS dates LEFT JOIN ";
567
-        // setup the joins
568
-        $join .= implode(' LEFT JOIN ', $join_parts);
569
-        // now let's put it all together
570
-        $query = $select . $join . ' GROUP BY Registration_REG_date';
571
-        // and execute it
572
-        return $wpdb->get_results($query, ARRAY_A);
573
-    }
574
-
575
-
576
-    /**
577
-     *        get the number of registrations per event  for the Registration Admin page Reports Tab
578
-     *
579
-     * @access        public
580
-     * @param $period string which can be passed to php's strtotime function (eg "-1 month")
581
-     * @return stdClass[] each with properties event_name, reg_limit, and total
582
-     * @throws EE_Error
583
-     */
584
-    public function get_registrations_per_event_report($period = '-1 month')
585
-    {
586
-        $date_sql = $this->convert_datetime_for_query(
587
-            'REG_date',
588
-            date('Y-m-d H:i:s', strtotime($period)),
589
-            'Y-m-d H:i:s',
590
-            'UTC'
591
-        );
592
-        $where = array(
593
-            'REG_date' => array('>=', $date_sql),
594
-            'STS_ID' => array('!=', EEM_Registration::status_id_incomplete),
595
-        );
596
-        if (!EE_Registry::instance()->CAP->current_user_can(
597
-            'ee_read_others_registrations',
598
-            'reg_per_event_report'
599
-        )
600
-        ) {
601
-            $where['Event.EVT_wp_user'] = get_current_user_id();
602
-        }
603
-        $results = $this->_get_all_wpdb_results(
604
-            array(
605
-            $where,
606
-            'group_by' => 'Event.EVT_name',
607
-            'order_by' => 'Event.EVT_name',
608
-            'limit' => array(0, 24),
609
-            ),
610
-            OBJECT,
611
-            array(
612
-                'event_name' => array('Event_CPT.post_title', '%s'),
613
-                'total' => array('COUNT(REG_ID)', '%s'),
614
-            )
615
-        );
616
-        return $results;
617
-    }
618
-
619
-
620
-    /**
621
-     * Get the number of registrations per event grouped by registration status.
622
-     * Note: EEM_Registration::status_id_incomplete registrations are excluded from the results.
623
-     *
624
-     * @param string $period
625
-     * @return stdClass[] with properties `Registration_Event` and a column for each registration status as the STS_ID
626
-     * @throws EE_Error
627
-     *                    (i.e. RAP)
628
-     */
629
-    public function get_registrations_per_event_and_per_status_report($period = '-1 month')
630
-    {
631
-        global $wpdb;
632
-        $registration_table = $wpdb->prefix . 'esp_registration';
633
-        $event_table = $wpdb->posts;
634
-        $sql_date = date('Y-m-d H:i:s', strtotime($period));
635
-        // inner date query
636
-        $inner_date_query = "SELECT DISTINCT EVT_ID, REG_date from $registration_table ";
637
-        $inner_where = ' WHERE';
638
-        // exclude events not authored by user if permissions in effect
639
-        if (!EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_event_report')) {
640
-            $inner_date_query .= "LEFT JOIN {$event_table} ON ID = EVT_ID";
641
-            $inner_where .= ' post_author = ' . get_current_user_id() . ' AND';
642
-        }
643
-        $inner_where .= " REG_date >= '{$sql_date}'";
644
-        $inner_date_query .= $inner_where;
645
-        // build main query
646
-        $select = 'SELECT Event.post_title as Registration_Event, ';
647
-        $join = '';
648
-        $join_parts = array();
649
-        $select_parts = array();
650
-        // loop through registration stati to do parts for each status.
651
-        foreach (EEM_Registration::reg_status_array() as $STS_ID => $STS_code) {
652
-            if ($STS_ID === EEM_Registration::status_id_incomplete) {
653
-                continue;
654
-            }
655
-            $select_parts[] = "COUNT({$STS_code}.REG_ID) as {$STS_ID}";
656
-            $join_parts[] = "{$registration_table} AS {$STS_code} ON {$STS_code}.EVT_ID = dates.EVT_ID AND {$STS_code}.STS_ID = '{$STS_ID}' AND {$STS_code}.REG_date = dates.REG_date";
657
-        }
658
-        // setup the selects
659
-        $select .= implode(', ', $select_parts);
660
-        $select .= " FROM ($inner_date_query) AS dates LEFT JOIN $event_table as Event ON Event.ID = dates.EVT_ID LEFT JOIN ";
661
-        // setup remaining joins
662
-        $join .= implode(' LEFT JOIN ', $join_parts);
663
-        // now put it all together
664
-        $query = $select . $join . ' GROUP BY Registration_Event';
665
-        // and execute
666
-        return $wpdb->get_results($query, ARRAY_A);
667
-    }
668
-
669
-
670
-    /**
671
-     * Returns the EE_Registration of the primary attendee on the transaction id provided
672
-     *
673
-     * @param int $TXN_ID
674
-     * @return EE_Base_Class|EE_Registration|null
675
-     * @throws EE_Error
676
-     */
677
-    public function get_primary_registration_for_transaction_ID($TXN_ID = 0)
678
-    {
679
-        if (!$TXN_ID) {
680
-            return null;
681
-        }
682
-        return $this->get_one(array(
683
-            array(
684
-                'TXN_ID' => $TXN_ID,
685
-                'REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT,
686
-            ),
687
-        ));
688
-    }
689
-
690
-
691
-    /**
692
-     *        get_event_registration_count
693
-     *
694
-     * @access public
695
-     * @param int $EVT_ID
696
-     * @param boolean $for_incomplete_payments
697
-     * @return int
698
-     * @throws EE_Error
699
-     */
700
-    public function get_event_registration_count($EVT_ID, $for_incomplete_payments = false)
701
-    {
702
-        // we only count approved registrations towards registration limits
703
-        $query_params = array(array('EVT_ID' => $EVT_ID, 'STS_ID' => self::status_id_approved));
704
-        if ($for_incomplete_payments) {
705
-            $query_params[0]['Transaction.STS_ID'] = array('!=', EEM_Transaction::complete_status_code);
706
-        }
707
-        return $this->count($query_params);
708
-    }
709
-
710
-
711
-    /**
712
-     * Deletes all registrations with no transactions. Note that this needs to be very efficient
713
-     * and so it uses wpdb directly
714
-     *
715
-     * @global WPDB $wpdb
716
-     * @return int number deleted
717
-     * @throws EE_Error
718
-     */
719
-    public function delete_registrations_with_no_transaction()
720
-    {
721
-        /** @type WPDB $wpdb */
722
-        global $wpdb;
723
-        return $wpdb->query(
724
-            'DELETE r FROM '
725
-            . $this->table()
726
-            . ' r LEFT JOIN '
727
-            . EEM_Transaction::instance()->table()
728
-            . ' t ON r.TXN_ID = t.TXN_ID WHERE t.TXN_ID IS NULL'
729
-        );
730
-    }
731
-
732
-
733
-    /**
734
-     *  Count registrations checked into (or out of) a datetime
735
-     *
736
-     * @param int $DTT_ID datetime ID
737
-     * @param boolean $checked_in whether to count registrations checked IN or OUT
738
-     * @return int
739
-     * @throws EE_Error
740
-     */
741
-    public function count_registrations_checked_into_datetime($DTT_ID, $checked_in = true)
742
-    {
743
-        global $wpdb;
744
-        // subquery to get latest checkin
745
-        $query = $wpdb->prepare(
746
-            'SELECT '
747
-            . 'COUNT( DISTINCT checkins.REG_ID ) '
748
-            . 'FROM ' . EEM_Checkin::instance()->table() . ' AS checkins INNER JOIN'
749
-            . '( SELECT '
750
-            . 'max( CHK_timestamp ) AS latest_checkin, '
751
-            . 'REG_ID AS REG_ID '
752
-            . 'FROM ' . EEM_Checkin::instance()->table() . ' '
753
-            . 'WHERE DTT_ID=%d '
754
-            . 'GROUP BY REG_ID'
755
-            . ') AS most_recent_checkin_per_reg '
756
-            . 'ON checkins.REG_ID=most_recent_checkin_per_reg.REG_ID '
757
-            . 'AND checkins.CHK_timestamp = most_recent_checkin_per_reg.latest_checkin '
758
-            . 'WHERE '
759
-            . 'checkins.CHK_in=%d',
760
-            $DTT_ID,
761
-            $checked_in
762
-        );
763
-        return (int) $wpdb->get_var($query);
764
-    }
765
-
766
-
767
-    /**
768
-     *  Count registrations checked into (or out of) an event.
769
-     *
770
-     * @param int $EVT_ID event ID
771
-     * @param boolean $checked_in whether to count registrations checked IN or OUT
772
-     * @return int
773
-     * @throws EE_Error
774
-     */
775
-    public function count_registrations_checked_into_event($EVT_ID, $checked_in = true)
776
-    {
777
-        global $wpdb;
778
-        // subquery to get latest checkin
779
-        $query = $wpdb->prepare(
780
-            'SELECT '
781
-            . 'COUNT( DISTINCT checkins.REG_ID ) '
782
-            . 'FROM ' . EEM_Checkin::instance()->table() . ' AS checkins INNER JOIN'
783
-            . '( SELECT '
784
-            . 'max( CHK_timestamp ) AS latest_checkin, '
785
-            . 'REG_ID AS REG_ID '
786
-            . 'FROM ' . EEM_Checkin::instance()->table() . ' AS c '
787
-            . 'INNER JOIN ' . EEM_Datetime::instance()->table() . ' AS d '
788
-            . 'ON c.DTT_ID=d.DTT_ID '
789
-            . 'WHERE d.EVT_ID=%d '
790
-            . 'GROUP BY REG_ID'
791
-            . ') AS most_recent_checkin_per_reg '
792
-            . 'ON checkins.REG_ID=most_recent_checkin_per_reg.REG_ID '
793
-            . 'AND checkins.CHK_timestamp = most_recent_checkin_per_reg.latest_checkin '
794
-            . 'WHERE '
795
-            . 'checkins.CHK_in=%d',
796
-            $EVT_ID,
797
-            $checked_in
798
-        );
799
-        return (int) $wpdb->get_var($query);
800
-    }
801
-
802
-
803
-    /**
804
-     * The purpose of this method is to retrieve an array of
805
-     * EE_Registration objects that represent the latest registration
806
-     * for each ATT_ID given in the function argument.
807
-     *
808
-     * @param array $attendee_ids
809
-     * @return EE_Base_Class[]|EE_Registration[]
810
-     * @throws EE_Error
811
-     */
812
-    public function get_latest_registration_for_each_of_given_contacts($attendee_ids = array())
813
-    {
814
-        // first do a native wp_query to get the latest REG_ID's matching these attendees.
815
-        global $wpdb;
816
-        $registration_table = $wpdb->prefix . 'esp_registration';
817
-        $attendee_table = $wpdb->posts;
818
-        $attendee_ids = is_array($attendee_ids)
819
-            ? array_map('absint', $attendee_ids)
820
-            : array((int) $attendee_ids);
821
-        $ATT_IDs = implode(',', $attendee_ids);
822
-        // first we do a query to get the registration ids
823
-        // (because a group by before order by causes the order by to be ignored.)
824
-        $registration_id_query = "
16
+	/**
17
+	 * @var EEM_Registration $_instance
18
+	 */
19
+	protected static $_instance;
20
+
21
+	/**
22
+	 * Keys are the status IDs for registrations (eg, RAP, RCN, etc), and the values
23
+	 * are status codes (eg, approved, cancelled, etc)
24
+	 *
25
+	 * @var array
26
+	 */
27
+	private static $_reg_status;
28
+
29
+	/**
30
+	 * The value of REG_count for a primary registrant
31
+	 */
32
+	const PRIMARY_REGISTRANT_COUNT = 1;
33
+
34
+	/**
35
+	 * Status ID (STS_ID on esp_status table) to indicate an INCOMPLETE registration.
36
+	 * Initial status for registrations when they are first created
37
+	 * Payments are NOT allowed.
38
+	 * Automatically toggled to whatever the default Event registration status is upon completion of the attendee
39
+	 * information reg step NO space reserved. Registration is NOT active
40
+	 */
41
+	const status_id_incomplete = 'RIC';
42
+
43
+	/**
44
+	 * Status ID (STS_ID on esp_status table) to indicate an UNAPPROVED registration.
45
+	 * Payments are NOT allowed.
46
+	 * Event Admin must manually toggle STS_ID for it to change
47
+	 * No space reserved.
48
+	 * Registration is active
49
+	 */
50
+	const status_id_not_approved = 'RNA';
51
+
52
+	/**
53
+	 * Status ID (STS_ID on esp_status table) to indicate registration is PENDING_PAYMENT .
54
+	 * Payments are allowed.
55
+	 * STS_ID will automatically be toggled to RAP if payment is made in full by the attendee
56
+	 * No space reserved.
57
+	 * Registration is active
58
+	 */
59
+	const status_id_pending_payment = 'RPP';
60
+
61
+	/**
62
+	 * Status ID (STS_ID on esp_status table) to indicate registration is on the WAIT_LIST .
63
+	 * Payments are allowed.
64
+	 * STS_ID will automatically be toggled to RAP if payment is made in full by the attendee
65
+	 * No space reserved.
66
+	 * Registration is active
67
+	 */
68
+	const status_id_wait_list = 'RWL';
69
+
70
+	/**
71
+	 * Status ID (STS_ID on esp_status table) to indicate an APPROVED registration.
72
+	 * the TXN may or may not be completed ( paid in full )
73
+	 * Payments are allowed.
74
+	 * A space IS reserved.
75
+	 * Registration is active
76
+	 */
77
+	const status_id_approved = 'RAP';
78
+
79
+	/**
80
+	 * Status ID (STS_ID on esp_status table) to indicate a registration was CANCELLED by the attendee.
81
+	 * Payments are NOT allowed.
82
+	 * NO space reserved.
83
+	 * Registration is NOT active
84
+	 */
85
+	const status_id_cancelled = 'RCN';
86
+
87
+	/**
88
+	 * Status ID (STS_ID on esp_status table) to indicate a registration was DECLINED by the Event Admin
89
+	 * Payments are NOT allowed.
90
+	 * No space reserved.
91
+	 * Registration is NOT active
92
+	 */
93
+	const status_id_declined = 'RDC';
94
+
95
+	/**
96
+	 * @var TableAnalysis $table_analysis
97
+	 */
98
+	protected $_table_analysis;
99
+
100
+
101
+	/**
102
+	 *    private constructor to prevent direct creation
103
+	 *
104
+	 * @Constructor
105
+	 * @access protected
106
+	 * @param string $timezone string representing the timezone we want to set for returned Date Time Strings (and any
107
+	 *                         incoming timezone data that gets saved). Note this just sends the timezone info to the
108
+	 *                         date time model field objects.  Default is NULL (and will be assumed using the set
109
+	 *                         timezone in the 'timezone_string' wp option)
110
+	 * @throws EE_Error
111
+	 */
112
+	protected function __construct($timezone = null)
113
+	{
114
+		$this->_table_analysis = EE_Registry::instance()->create('TableAnalysis', array(), true);
115
+		$this->singular_item = esc_html__('Registration', 'event_espresso');
116
+		$this->plural_item = esc_html__('Registrations', 'event_espresso');
117
+		$this->_tables = array(
118
+			'Registration' => new EE_Primary_Table('esp_registration', 'REG_ID'),
119
+		);
120
+		$this->_fields = array(
121
+			'Registration' => array(
122
+				'REG_ID' => new EE_Primary_Key_Int_Field(
123
+					'REG_ID',
124
+					esc_html__('Registration ID', 'event_espresso')
125
+				),
126
+				'EVT_ID' => new EE_Foreign_Key_Int_Field(
127
+					'EVT_ID',
128
+					esc_html__('Event ID', 'event_espresso'),
129
+					false,
130
+					0,
131
+					'Event'
132
+				),
133
+				'ATT_ID' => new EE_Foreign_Key_Int_Field(
134
+					'ATT_ID',
135
+					esc_html__('Attendee ID', 'event_espresso'),
136
+					false,
137
+					0,
138
+					'Attendee'
139
+				),
140
+				'TXN_ID' => new EE_Foreign_Key_Int_Field(
141
+					'TXN_ID',
142
+					esc_html__('Transaction ID', 'event_espresso'),
143
+					false,
144
+					0,
145
+					'Transaction'
146
+				),
147
+				'TKT_ID' => new EE_Foreign_Key_Int_Field(
148
+					'TKT_ID',
149
+					esc_html__('Ticket ID', 'event_espresso'),
150
+					false,
151
+					0,
152
+					'Ticket'
153
+				),
154
+				'STS_ID' => new EE_Foreign_Key_String_Field(
155
+					'STS_ID',
156
+					esc_html__('Status ID', 'event_espresso'),
157
+					false,
158
+					EEM_Registration::status_id_incomplete,
159
+					'Status'
160
+				),
161
+				'REG_date' => new EE_Datetime_Field(
162
+					'REG_date',
163
+					esc_html__('Time registration occurred', 'event_espresso'),
164
+					false,
165
+					EE_Datetime_Field::now,
166
+					$timezone
167
+				),
168
+				'REG_final_price' => new EE_Money_Field(
169
+					'REG_final_price',
170
+					esc_html__('Registration\'s share of the transaction total', 'event_espresso'),
171
+					false,
172
+					0
173
+				),
174
+				'REG_paid' => new EE_Money_Field(
175
+					'REG_paid',
176
+					esc_html__('Amount paid to date towards registration', 'event_espresso'),
177
+					false,
178
+					0
179
+				),
180
+				'REG_session' => new EE_Plain_Text_Field(
181
+					'REG_session',
182
+					esc_html__('Session ID of registration', 'event_espresso'),
183
+					false,
184
+					''
185
+				),
186
+				'REG_code' => new EE_Plain_Text_Field(
187
+					'REG_code',
188
+					esc_html__('Unique Code for this registration', 'event_espresso'),
189
+					false,
190
+					''
191
+				),
192
+				'REG_url_link' => new EE_Plain_Text_Field(
193
+					'REG_url_link',
194
+					esc_html__('String to be used in URL for identifying registration', 'event_espresso'),
195
+					false,
196
+					''
197
+				),
198
+				'REG_count' => new EE_Integer_Field(
199
+					'REG_count',
200
+					esc_html__('Count of this registration in the group registration ', 'event_espresso'),
201
+					true,
202
+					1
203
+				),
204
+				'REG_group_size' => new EE_Integer_Field(
205
+					'REG_group_size',
206
+					esc_html__('Number of registrations on this group', 'event_espresso'),
207
+					false,
208
+					1
209
+				),
210
+				'REG_att_is_going' => new EE_Boolean_Field(
211
+					'REG_att_is_going',
212
+					esc_html__('Flag indicating the registrant plans on attending', 'event_espresso'),
213
+					false,
214
+					false
215
+				),
216
+				'REG_deleted' => new EE_Trashed_Flag_Field(
217
+					'REG_deleted',
218
+					esc_html__('Flag indicating if registration has been archived or not.', 'event_espresso'),
219
+					false,
220
+					false
221
+				),
222
+			),
223
+		);
224
+		$this->_model_relations = array(
225
+			'Event' => new EE_Belongs_To_Relation(),
226
+			'Attendee' => new EE_Belongs_To_Relation(),
227
+			'Transaction' => new EE_Belongs_To_Relation(),
228
+			'Ticket' => new EE_Belongs_To_Relation(),
229
+			'Status' => new EE_Belongs_To_Relation(),
230
+			'Answer' => new EE_Has_Many_Relation(),
231
+			'Checkin' => new EE_Has_Many_Relation(),
232
+			'Registration_Payment' => new EE_Has_Many_Relation(),
233
+			'Payment' => new EE_HABTM_Relation('Registration_Payment'),
234
+			'Message' => new EE_Has_Many_Any_Relation(false)
235
+			// allow deletes even if there are messages in the queue related
236
+		);
237
+		$this->_model_chain_to_wp_user = 'Event';
238
+		parent::__construct($timezone);
239
+	}
240
+
241
+
242
+	/**
243
+	 * a list of ALL valid registration statuses currently in use within the system
244
+	 * generated by combining the filterable active and inactive reg status arrays
245
+	 *
246
+	 * @return array
247
+	 */
248
+	public static function reg_statuses()
249
+	{
250
+		return array_unique(
251
+			array_merge(
252
+				EEM_Registration::active_reg_statuses(),
253
+				EEM_Registration::inactive_reg_statuses()
254
+			)
255
+		);
256
+	}
257
+
258
+
259
+	/**
260
+	 * reg_statuses_that_allow_payment
261
+	 * a filterable list of registration statuses that allow a registrant to make a payment
262
+	 *
263
+	 * @access public
264
+	 * @return array
265
+	 */
266
+	public static function reg_statuses_that_allow_payment()
267
+	{
268
+		return apply_filters(
269
+			'FHEE__EEM_Registration__reg_statuses_that_allow_payment',
270
+			array(
271
+				EEM_Registration::status_id_approved,
272
+				EEM_Registration::status_id_pending_payment,
273
+			)
274
+		);
275
+	}
276
+
277
+
278
+	/**
279
+	 * active_reg_statuses
280
+	 * a filterable list of registration statuses that are considered active
281
+	 *
282
+	 * @access public
283
+	 * @return array
284
+	 */
285
+	public static function active_reg_statuses()
286
+	{
287
+		return apply_filters(
288
+			'FHEE__EEM_Registration__active_reg_statuses',
289
+			array(
290
+				EEM_Registration::status_id_approved,
291
+				EEM_Registration::status_id_pending_payment,
292
+				EEM_Registration::status_id_wait_list,
293
+				EEM_Registration::status_id_not_approved,
294
+			)
295
+		);
296
+	}
297
+
298
+
299
+	/**
300
+	 * inactive_reg_statuses
301
+	 * a filterable list of registration statuses that are not considered active
302
+	 *
303
+	 * @access public
304
+	 * @return array
305
+	 */
306
+	public static function inactive_reg_statuses()
307
+	{
308
+		return apply_filters(
309
+			'FHEE__EEM_Registration__inactive_reg_statuses',
310
+			array(
311
+				EEM_Registration::status_id_incomplete,
312
+				EEM_Registration::status_id_cancelled,
313
+				EEM_Registration::status_id_declined,
314
+			)
315
+		);
316
+	}
317
+
318
+
319
+	/**
320
+	 *    closed_reg_statuses
321
+	 *    a filterable list of registration statuses that are considered "closed"
322
+	 * meaning they should not be considered in any calculations involving monies owing
323
+	 *
324
+	 * @access public
325
+	 * @return array
326
+	 */
327
+	public static function closed_reg_statuses()
328
+	{
329
+		return apply_filters(
330
+			'FHEE__EEM_Registration__closed_reg_statuses',
331
+			array(
332
+				EEM_Registration::status_id_cancelled,
333
+				EEM_Registration::status_id_declined,
334
+				EEM_Registration::status_id_wait_list,
335
+			)
336
+		);
337
+	}
338
+
339
+
340
+	/**
341
+	 *        get list of registration statuses
342
+	 *
343
+	 * @access public
344
+	 * @param array $exclude The status ids to exclude from the returned results
345
+	 * @param bool $translated If true will return the values as singular localized strings
346
+	 * @return array
347
+	 * @throws EE_Error
348
+	 */
349
+	public static function reg_status_array($exclude = array(), $translated = false)
350
+	{
351
+		EEM_Registration::instance()->_get_registration_status_array($exclude);
352
+		return $translated
353
+			? EEM_Status::instance()->localized_status(self::$_reg_status, false, 'sentence')
354
+			: self::$_reg_status;
355
+	}
356
+
357
+
358
+	/**
359
+	 *    get list of registration statuses
360
+	 *
361
+	 * @access private
362
+	 * @param array $exclude
363
+	 * @return void
364
+	 * @throws EE_Error
365
+	 */
366
+	private function _get_registration_status_array($exclude = array())
367
+	{
368
+		// in the very rare circumstance that we are deleting a model's table's data
369
+		// and the table hasn't actually been created, this could have an error
370
+		/** @type WPDB $wpdb */
371
+		global $wpdb;
372
+		if ($this->_get_table_analysis()->tableExists($wpdb->prefix . 'esp_status')) {
373
+			$results = $wpdb->get_results(
374
+				"SELECT STS_ID, STS_code FROM {$wpdb->prefix}esp_status WHERE STS_type = 'registration'"
375
+			);
376
+			self::$_reg_status = array();
377
+			foreach ($results as $status) {
378
+				if (!in_array($status->STS_ID, $exclude, true)) {
379
+					self::$_reg_status[ $status->STS_ID ] = $status->STS_code;
380
+				}
381
+			}
382
+		}
383
+	}
384
+
385
+
386
+	/**
387
+	 * Gets the injected table analyzer, or throws an exception
388
+	 *
389
+	 * @return TableAnalysis
390
+	 * @throws EE_Error
391
+	 */
392
+	protected function _get_table_analysis()
393
+	{
394
+		if ($this->_table_analysis instanceof TableAnalysis) {
395
+			return $this->_table_analysis;
396
+		}
397
+		throw new EE_Error(
398
+			sprintf(
399
+				esc_html__('Table analysis class on class %1$s is not set properly.', 'event_espresso'),
400
+				get_class($this)
401
+			)
402
+		);
403
+	}
404
+
405
+
406
+	/**
407
+	 * This returns a wpdb->results array of all registration date month and years matching the incoming query params
408
+	 * and grouped by month and year.
409
+	 *
410
+	 * @param  array $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
411
+	 * @return array
412
+	 * @throws EE_Error
413
+	 */
414
+	public function get_reg_months_and_years($where_params)
415
+	{
416
+		$query_params[0] = $where_params;
417
+		$query_params['group_by'] = array('reg_year', 'reg_month');
418
+		$query_params['order_by'] = array('REG_date' => 'DESC');
419
+		$columns_to_select = array(
420
+			'reg_year' => array('YEAR(REG_date)', '%s'),
421
+			'reg_month' => array('MONTHNAME(REG_date)', '%s'),
422
+		);
423
+		return $this->_get_all_wpdb_results($query_params, OBJECT, $columns_to_select);
424
+	}
425
+
426
+
427
+	/**
428
+	 * retrieve ALL registrations for a particular Attendee from db
429
+	 *
430
+	 * @param int $ATT_ID
431
+	 * @return EE_Base_Class[]|EE_Registration[]|null
432
+	 * @throws EE_Error
433
+	 */
434
+	public function get_all_registrations_for_attendee($ATT_ID = 0)
435
+	{
436
+		if (!$ATT_ID) {
437
+			return null;
438
+		}
439
+		return $this->get_all(array(array('ATT_ID' => $ATT_ID)));
440
+	}
441
+
442
+
443
+	/**
444
+	 * Gets a registration given their REG_url_link. Yes, this should usually
445
+	 * be passed via a GET parameter.
446
+	 *
447
+	 * @param string $REG_url_link
448
+	 * @return EE_Base_Class|EE_Registration|null
449
+	 * @throws EE_Error
450
+	 */
451
+	public function get_registration_for_reg_url_link($REG_url_link)
452
+	{
453
+		if (!$REG_url_link) {
454
+			return null;
455
+		}
456
+		return $this->get_one(array(array('REG_url_link' => $REG_url_link)));
457
+	}
458
+
459
+
460
+	/**
461
+	 *        retrieve registration for a specific transaction attendee from db
462
+	 *
463
+	 * @access        public
464
+	 * @param    int $TXN_ID
465
+	 * @param    int $ATT_ID
466
+	 * @param    int $att_nmbr in case the ATT_ID is the same for multiple registrations (same details used) then the
467
+	 *                         attendee number is required
468
+	 * @return        mixed        array on success, FALSE on fail
469
+	 * @throws EE_Error
470
+	 */
471
+	public function get_registration_for_transaction_attendee($TXN_ID = 0, $ATT_ID = 0, $att_nmbr = 0)
472
+	{
473
+		return $this->get_one(array(
474
+			array(
475
+				'TXN_ID' => $TXN_ID,
476
+				'ATT_ID' => $ATT_ID,
477
+			),
478
+			'limit' => array(min($att_nmbr - 1, 0), 1),
479
+		));
480
+	}
481
+
482
+
483
+	/**
484
+	 *        get the number of registrations per day  for the Registration Admin page Reports Tab.
485
+	 *        (doesn't utilize models because it's a fairly specialized query)
486
+	 *
487
+	 * @access        public
488
+	 * @param $period string which can be passed to php's strtotime function (eg "-1 month")
489
+	 * @return stdClass[] with properties regDate and total
490
+	 * @throws EE_Error
491
+	 */
492
+	public function get_registrations_per_day_report($period = '-1 month')
493
+	{
494
+		$sql_date = $this->convert_datetime_for_query(
495
+			'REG_date',
496
+			date('Y-m-d H:i:s', strtotime($period)),
497
+			'Y-m-d H:i:s',
498
+			'UTC'
499
+		);
500
+		$where = array(
501
+			'REG_date' => array('>=', $sql_date),
502
+			'STS_ID' => array('!=', EEM_Registration::status_id_incomplete),
503
+		);
504
+		if (!EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_day_report')) {
505
+			$where['Event.EVT_wp_user'] = get_current_user_id();
506
+		}
507
+		$query_interval = EEH_DTT_Helper::get_sql_query_interval_for_offset($this->get_timezone(), 'REG_date');
508
+		$results = $this->_get_all_wpdb_results(
509
+			array(
510
+				$where,
511
+				'group_by' => 'regDate',
512
+				'order_by' => array('REG_date' => 'ASC'),
513
+			),
514
+			OBJECT,
515
+			array(
516
+				'regDate' => array('DATE(' . $query_interval . ')', '%s'),
517
+				'total' => array('count(REG_ID)', '%d'),
518
+			)
519
+		);
520
+		return $results;
521
+	}
522
+
523
+
524
+	/**
525
+	 * Get the number of registrations per day including the count of registrations for each Registration Status.
526
+	 * Note: EEM_Registration::status_id_incomplete registrations are excluded from the results.
527
+	 *
528
+	 * @param string $period
529
+	 * @return stdClass[] with properties Registration_REG_date and a column for each registration status as the STS_ID
530
+	 * @throws EE_Error
531
+	 *                    (i.e. RAP)
532
+	 */
533
+	public function get_registrations_per_day_and_per_status_report($period = '-1 month')
534
+	{
535
+		global $wpdb;
536
+		$registration_table = $wpdb->prefix . 'esp_registration';
537
+		$event_table = $wpdb->posts;
538
+		$sql_date = date('Y-m-d H:i:s', strtotime($period));
539
+		// prepare the query interval for displaying offset
540
+		$query_interval = EEH_DTT_Helper::get_sql_query_interval_for_offset($this->get_timezone(), 'dates.REG_date');
541
+		// inner date query
542
+		$inner_date_query = "SELECT DISTINCT REG_date from {$registration_table} ";
543
+		$inner_where = ' WHERE';
544
+		// exclude events not authored by user if permissions in effect
545
+		if (!EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_event_report')) {
546
+			$inner_date_query .= "LEFT JOIN {$event_table} ON ID = EVT_ID";
547
+			$inner_where .= ' post_author = ' . get_current_user_id() . ' AND';
548
+		}
549
+		$inner_where .= " REG_date >= '{$sql_date}'";
550
+		$inner_date_query .= $inner_where;
551
+		// start main query
552
+		$select = "SELECT DATE({$query_interval}) as Registration_REG_date, ";
553
+		$join = '';
554
+		$join_parts = array();
555
+		$select_parts = array();
556
+		// loop through registration stati to do parts for each status.
557
+		foreach (EEM_Registration::reg_status_array() as $STS_ID => $STS_code) {
558
+			if ($STS_ID === EEM_Registration::status_id_incomplete) {
559
+				continue;
560
+			}
561
+			$select_parts[] = "COUNT({$STS_code}.REG_ID) as {$STS_ID}";
562
+			$join_parts[] = "{$registration_table} AS {$STS_code} ON {$STS_code}.REG_date = dates.REG_date AND {$STS_code}.STS_ID = '{$STS_ID}'";
563
+		}
564
+		// setup the selects
565
+		$select .= implode(', ', $select_parts);
566
+		$select .= " FROM ($inner_date_query) AS dates LEFT JOIN ";
567
+		// setup the joins
568
+		$join .= implode(' LEFT JOIN ', $join_parts);
569
+		// now let's put it all together
570
+		$query = $select . $join . ' GROUP BY Registration_REG_date';
571
+		// and execute it
572
+		return $wpdb->get_results($query, ARRAY_A);
573
+	}
574
+
575
+
576
+	/**
577
+	 *        get the number of registrations per event  for the Registration Admin page Reports Tab
578
+	 *
579
+	 * @access        public
580
+	 * @param $period string which can be passed to php's strtotime function (eg "-1 month")
581
+	 * @return stdClass[] each with properties event_name, reg_limit, and total
582
+	 * @throws EE_Error
583
+	 */
584
+	public function get_registrations_per_event_report($period = '-1 month')
585
+	{
586
+		$date_sql = $this->convert_datetime_for_query(
587
+			'REG_date',
588
+			date('Y-m-d H:i:s', strtotime($period)),
589
+			'Y-m-d H:i:s',
590
+			'UTC'
591
+		);
592
+		$where = array(
593
+			'REG_date' => array('>=', $date_sql),
594
+			'STS_ID' => array('!=', EEM_Registration::status_id_incomplete),
595
+		);
596
+		if (!EE_Registry::instance()->CAP->current_user_can(
597
+			'ee_read_others_registrations',
598
+			'reg_per_event_report'
599
+		)
600
+		) {
601
+			$where['Event.EVT_wp_user'] = get_current_user_id();
602
+		}
603
+		$results = $this->_get_all_wpdb_results(
604
+			array(
605
+			$where,
606
+			'group_by' => 'Event.EVT_name',
607
+			'order_by' => 'Event.EVT_name',
608
+			'limit' => array(0, 24),
609
+			),
610
+			OBJECT,
611
+			array(
612
+				'event_name' => array('Event_CPT.post_title', '%s'),
613
+				'total' => array('COUNT(REG_ID)', '%s'),
614
+			)
615
+		);
616
+		return $results;
617
+	}
618
+
619
+
620
+	/**
621
+	 * Get the number of registrations per event grouped by registration status.
622
+	 * Note: EEM_Registration::status_id_incomplete registrations are excluded from the results.
623
+	 *
624
+	 * @param string $period
625
+	 * @return stdClass[] with properties `Registration_Event` and a column for each registration status as the STS_ID
626
+	 * @throws EE_Error
627
+	 *                    (i.e. RAP)
628
+	 */
629
+	public function get_registrations_per_event_and_per_status_report($period = '-1 month')
630
+	{
631
+		global $wpdb;
632
+		$registration_table = $wpdb->prefix . 'esp_registration';
633
+		$event_table = $wpdb->posts;
634
+		$sql_date = date('Y-m-d H:i:s', strtotime($period));
635
+		// inner date query
636
+		$inner_date_query = "SELECT DISTINCT EVT_ID, REG_date from $registration_table ";
637
+		$inner_where = ' WHERE';
638
+		// exclude events not authored by user if permissions in effect
639
+		if (!EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_event_report')) {
640
+			$inner_date_query .= "LEFT JOIN {$event_table} ON ID = EVT_ID";
641
+			$inner_where .= ' post_author = ' . get_current_user_id() . ' AND';
642
+		}
643
+		$inner_where .= " REG_date >= '{$sql_date}'";
644
+		$inner_date_query .= $inner_where;
645
+		// build main query
646
+		$select = 'SELECT Event.post_title as Registration_Event, ';
647
+		$join = '';
648
+		$join_parts = array();
649
+		$select_parts = array();
650
+		// loop through registration stati to do parts for each status.
651
+		foreach (EEM_Registration::reg_status_array() as $STS_ID => $STS_code) {
652
+			if ($STS_ID === EEM_Registration::status_id_incomplete) {
653
+				continue;
654
+			}
655
+			$select_parts[] = "COUNT({$STS_code}.REG_ID) as {$STS_ID}";
656
+			$join_parts[] = "{$registration_table} AS {$STS_code} ON {$STS_code}.EVT_ID = dates.EVT_ID AND {$STS_code}.STS_ID = '{$STS_ID}' AND {$STS_code}.REG_date = dates.REG_date";
657
+		}
658
+		// setup the selects
659
+		$select .= implode(', ', $select_parts);
660
+		$select .= " FROM ($inner_date_query) AS dates LEFT JOIN $event_table as Event ON Event.ID = dates.EVT_ID LEFT JOIN ";
661
+		// setup remaining joins
662
+		$join .= implode(' LEFT JOIN ', $join_parts);
663
+		// now put it all together
664
+		$query = $select . $join . ' GROUP BY Registration_Event';
665
+		// and execute
666
+		return $wpdb->get_results($query, ARRAY_A);
667
+	}
668
+
669
+
670
+	/**
671
+	 * Returns the EE_Registration of the primary attendee on the transaction id provided
672
+	 *
673
+	 * @param int $TXN_ID
674
+	 * @return EE_Base_Class|EE_Registration|null
675
+	 * @throws EE_Error
676
+	 */
677
+	public function get_primary_registration_for_transaction_ID($TXN_ID = 0)
678
+	{
679
+		if (!$TXN_ID) {
680
+			return null;
681
+		}
682
+		return $this->get_one(array(
683
+			array(
684
+				'TXN_ID' => $TXN_ID,
685
+				'REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT,
686
+			),
687
+		));
688
+	}
689
+
690
+
691
+	/**
692
+	 *        get_event_registration_count
693
+	 *
694
+	 * @access public
695
+	 * @param int $EVT_ID
696
+	 * @param boolean $for_incomplete_payments
697
+	 * @return int
698
+	 * @throws EE_Error
699
+	 */
700
+	public function get_event_registration_count($EVT_ID, $for_incomplete_payments = false)
701
+	{
702
+		// we only count approved registrations towards registration limits
703
+		$query_params = array(array('EVT_ID' => $EVT_ID, 'STS_ID' => self::status_id_approved));
704
+		if ($for_incomplete_payments) {
705
+			$query_params[0]['Transaction.STS_ID'] = array('!=', EEM_Transaction::complete_status_code);
706
+		}
707
+		return $this->count($query_params);
708
+	}
709
+
710
+
711
+	/**
712
+	 * Deletes all registrations with no transactions. Note that this needs to be very efficient
713
+	 * and so it uses wpdb directly
714
+	 *
715
+	 * @global WPDB $wpdb
716
+	 * @return int number deleted
717
+	 * @throws EE_Error
718
+	 */
719
+	public function delete_registrations_with_no_transaction()
720
+	{
721
+		/** @type WPDB $wpdb */
722
+		global $wpdb;
723
+		return $wpdb->query(
724
+			'DELETE r FROM '
725
+			. $this->table()
726
+			. ' r LEFT JOIN '
727
+			. EEM_Transaction::instance()->table()
728
+			. ' t ON r.TXN_ID = t.TXN_ID WHERE t.TXN_ID IS NULL'
729
+		);
730
+	}
731
+
732
+
733
+	/**
734
+	 *  Count registrations checked into (or out of) a datetime
735
+	 *
736
+	 * @param int $DTT_ID datetime ID
737
+	 * @param boolean $checked_in whether to count registrations checked IN or OUT
738
+	 * @return int
739
+	 * @throws EE_Error
740
+	 */
741
+	public function count_registrations_checked_into_datetime($DTT_ID, $checked_in = true)
742
+	{
743
+		global $wpdb;
744
+		// subquery to get latest checkin
745
+		$query = $wpdb->prepare(
746
+			'SELECT '
747
+			. 'COUNT( DISTINCT checkins.REG_ID ) '
748
+			. 'FROM ' . EEM_Checkin::instance()->table() . ' AS checkins INNER JOIN'
749
+			. '( SELECT '
750
+			. 'max( CHK_timestamp ) AS latest_checkin, '
751
+			. 'REG_ID AS REG_ID '
752
+			. 'FROM ' . EEM_Checkin::instance()->table() . ' '
753
+			. 'WHERE DTT_ID=%d '
754
+			. 'GROUP BY REG_ID'
755
+			. ') AS most_recent_checkin_per_reg '
756
+			. 'ON checkins.REG_ID=most_recent_checkin_per_reg.REG_ID '
757
+			. 'AND checkins.CHK_timestamp = most_recent_checkin_per_reg.latest_checkin '
758
+			. 'WHERE '
759
+			. 'checkins.CHK_in=%d',
760
+			$DTT_ID,
761
+			$checked_in
762
+		);
763
+		return (int) $wpdb->get_var($query);
764
+	}
765
+
766
+
767
+	/**
768
+	 *  Count registrations checked into (or out of) an event.
769
+	 *
770
+	 * @param int $EVT_ID event ID
771
+	 * @param boolean $checked_in whether to count registrations checked IN or OUT
772
+	 * @return int
773
+	 * @throws EE_Error
774
+	 */
775
+	public function count_registrations_checked_into_event($EVT_ID, $checked_in = true)
776
+	{
777
+		global $wpdb;
778
+		// subquery to get latest checkin
779
+		$query = $wpdb->prepare(
780
+			'SELECT '
781
+			. 'COUNT( DISTINCT checkins.REG_ID ) '
782
+			. 'FROM ' . EEM_Checkin::instance()->table() . ' AS checkins INNER JOIN'
783
+			. '( SELECT '
784
+			. 'max( CHK_timestamp ) AS latest_checkin, '
785
+			. 'REG_ID AS REG_ID '
786
+			. 'FROM ' . EEM_Checkin::instance()->table() . ' AS c '
787
+			. 'INNER JOIN ' . EEM_Datetime::instance()->table() . ' AS d '
788
+			. 'ON c.DTT_ID=d.DTT_ID '
789
+			. 'WHERE d.EVT_ID=%d '
790
+			. 'GROUP BY REG_ID'
791
+			. ') AS most_recent_checkin_per_reg '
792
+			. 'ON checkins.REG_ID=most_recent_checkin_per_reg.REG_ID '
793
+			. 'AND checkins.CHK_timestamp = most_recent_checkin_per_reg.latest_checkin '
794
+			. 'WHERE '
795
+			. 'checkins.CHK_in=%d',
796
+			$EVT_ID,
797
+			$checked_in
798
+		);
799
+		return (int) $wpdb->get_var($query);
800
+	}
801
+
802
+
803
+	/**
804
+	 * The purpose of this method is to retrieve an array of
805
+	 * EE_Registration objects that represent the latest registration
806
+	 * for each ATT_ID given in the function argument.
807
+	 *
808
+	 * @param array $attendee_ids
809
+	 * @return EE_Base_Class[]|EE_Registration[]
810
+	 * @throws EE_Error
811
+	 */
812
+	public function get_latest_registration_for_each_of_given_contacts($attendee_ids = array())
813
+	{
814
+		// first do a native wp_query to get the latest REG_ID's matching these attendees.
815
+		global $wpdb;
816
+		$registration_table = $wpdb->prefix . 'esp_registration';
817
+		$attendee_table = $wpdb->posts;
818
+		$attendee_ids = is_array($attendee_ids)
819
+			? array_map('absint', $attendee_ids)
820
+			: array((int) $attendee_ids);
821
+		$ATT_IDs = implode(',', $attendee_ids);
822
+		// first we do a query to get the registration ids
823
+		// (because a group by before order by causes the order by to be ignored.)
824
+		$registration_id_query = "
825 825
 			SELECT registrations.registration_ids as registration_id
826 826
 			FROM (
827 827
 				SELECT
@@ -835,61 +835,61 @@  discard block
 block discarded – undo
835 835
 			  ) AS registrations
836 836
 			  GROUP BY registrations.attendee_ids
837 837
 		";
838
-        $registration_ids = $wpdb->get_results($registration_id_query, ARRAY_A);
839
-        if (empty($registration_ids)) {
840
-            return array();
841
-        }
842
-        $ids_for_model_query = array();
843
-        // let's flatten the ids so they can be used in the model query.
844
-        foreach ($registration_ids as $registration_id) {
845
-            if (isset($registration_id['registration_id'])) {
846
-                $ids_for_model_query[] = $registration_id['registration_id'];
847
-            }
848
-        }
849
-        // construct query
850
-        $_where = array(
851
-            'REG_ID' => array('IN', $ids_for_model_query),
852
-        );
853
-        return $this->get_all(array($_where));
854
-    }
855
-
856
-
857
-
858
-    /**
859
-     * returns a count of registrations for the supplied event having the status as specified
860
-     *
861
-     * @param int $EVT_ID
862
-     * @param array $statuses
863
-     * @return int
864
-     * @throws InvalidArgumentException
865
-     * @throws InvalidStatusException
866
-     * @throws EE_Error
867
-     */
868
-    public function event_reg_count_for_statuses($EVT_ID, $statuses = array())
869
-    {
870
-        $EVT_ID = absint($EVT_ID);
871
-        if (! $EVT_ID) {
872
-            throw new InvalidArgumentException(
873
-                esc_html__('An invalid Event ID was supplied.', 'event_espresso')
874
-            );
875
-        }
876
-        $statuses = is_array($statuses) ? $statuses : array($statuses);
877
-        $statuses = ! empty($statuses) ? $statuses : array(EEM_Registration::status_id_approved);
878
-        $valid_reg_statuses = EEM_Registration::reg_statuses();
879
-        foreach ($statuses as $status) {
880
-            if (! in_array($status, $valid_reg_statuses, true)) {
881
-                throw new InvalidStatusException($status, esc_html__('Registration', 'event_espresso'));
882
-            }
883
-        }
884
-        return $this->count(
885
-            array(
886
-                array(
887
-                    'EVT_ID' => $EVT_ID,
888
-                    'STS_ID' => array('IN', $statuses),
889
-                ),
890
-            ),
891
-            'REG_ID',
892
-            true
893
-        );
894
-    }
838
+		$registration_ids = $wpdb->get_results($registration_id_query, ARRAY_A);
839
+		if (empty($registration_ids)) {
840
+			return array();
841
+		}
842
+		$ids_for_model_query = array();
843
+		// let's flatten the ids so they can be used in the model query.
844
+		foreach ($registration_ids as $registration_id) {
845
+			if (isset($registration_id['registration_id'])) {
846
+				$ids_for_model_query[] = $registration_id['registration_id'];
847
+			}
848
+		}
849
+		// construct query
850
+		$_where = array(
851
+			'REG_ID' => array('IN', $ids_for_model_query),
852
+		);
853
+		return $this->get_all(array($_where));
854
+	}
855
+
856
+
857
+
858
+	/**
859
+	 * returns a count of registrations for the supplied event having the status as specified
860
+	 *
861
+	 * @param int $EVT_ID
862
+	 * @param array $statuses
863
+	 * @return int
864
+	 * @throws InvalidArgumentException
865
+	 * @throws InvalidStatusException
866
+	 * @throws EE_Error
867
+	 */
868
+	public function event_reg_count_for_statuses($EVT_ID, $statuses = array())
869
+	{
870
+		$EVT_ID = absint($EVT_ID);
871
+		if (! $EVT_ID) {
872
+			throw new InvalidArgumentException(
873
+				esc_html__('An invalid Event ID was supplied.', 'event_espresso')
874
+			);
875
+		}
876
+		$statuses = is_array($statuses) ? $statuses : array($statuses);
877
+		$statuses = ! empty($statuses) ? $statuses : array(EEM_Registration::status_id_approved);
878
+		$valid_reg_statuses = EEM_Registration::reg_statuses();
879
+		foreach ($statuses as $status) {
880
+			if (! in_array($status, $valid_reg_statuses, true)) {
881
+				throw new InvalidStatusException($status, esc_html__('Registration', 'event_espresso'));
882
+			}
883
+		}
884
+		return $this->count(
885
+			array(
886
+				array(
887
+					'EVT_ID' => $EVT_ID,
888
+					'STS_ID' => array('IN', $statuses),
889
+				),
890
+			),
891
+			'REG_ID',
892
+			true
893
+		);
894
+	}
895 895
 }
Please login to merge, or discard this patch.
core/db_models/strategies/EE_Default_Where_Conditions.strategy.php 1 patch
Indentation   +140 added lines, -140 removed lines patch added patch discarded remove patch
@@ -3,7 +3,7 @@  discard block
 block discarded – undo
3 3
 /**
4 4
  *
5 5
  * Class EE_Default_Where_Conditions
6
-  *
6
+ *
7 7
  * Strategy to be used for getting default where conditions for EEM_Base children.
8 8
  * Should be initialized and set on construction of model
9 9
  *
@@ -16,143 +16,143 @@  discard block
 block discarded – undo
16 16
 class EE_Default_Where_Conditions
17 17
 {
18 18
 
19
-    /**
20
-     * This const can be used in EE_Default_Where_Conditions values, and at the time of querying it will be
21
-     * replaced with the current user's ID (because we don't want to use the current user's ID at time of
22
-     * initializing the models because it's too early)
23
-     */
24
-    const current_user_placeholder = '%$current_user_placeholder_should_be_replaced_automatically$%';
25
-
26
-    /**
27
-     * This const can be used in EE_Default_Where_Conditions where parameters as the name
28
-     * of the user field. When we are actually generating the where conditions it will be
29
-     * replaced with the model's wp user fieldname
30
-     */
31
-    const user_field_name_placeholder = '%$user_field_name_placeholder$%';
32
-
33
-    /**
34
-     * Model for which this strategy find default where conditions
35
-     * @var EEM_Base
36
-     */
37
-    protected $_model;
38
-
39
-    /**
40
-     * Where conditions specified on construction
41
-     * @var array
42
-     */
43
-    protected $_where_conditions_provided = array();
44
-
45
-    /**
46
-     * Custom where conditions. Model relation chains will be automatically added
47
-     * onto any field names
48
-     * @param array $custom_where_conditions
49
-     */
50
-    public function __construct($custom_where_conditions = array())
51
-    {
52
-        $this->_where_conditions_provided = $custom_where_conditions;
53
-    }
54
-
55
-
56
-
57
-    /**
58
-     * finalizes construction of the strategy for use in getting default where conditions
59
-     * for querying of the model.
60
-     * @param EEM_Base $model
61
-     */
62
-    public function _finalize_construct(EEM_Base $model)
63
-    {
64
-        $this->_model = $model;
65
-    }
66
-
67
-
68
-
69
-    /**
70
-     * Returns the where conditions explicitly passed in the constructor
71
-     * @return array
72
-     */
73
-    public function get_where_conditions_provided()
74
-    {
75
-        return $this->_where_conditions_provided;
76
-    }
77
-
78
-
79
-
80
-    /**
81
-     * Gets the where conditions to be added onto the query
82
-     * @param string $model_relation_chain
83
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
84
-     */
85
-    public function get_default_where_conditions($model_relation_chain = '')
86
-    {
87
-        return $this->prepare_where_conditions_for_querying(array_merge($this->_get_default_where_conditions(), $this->get_where_conditions_provided()), $model_relation_chain);
88
-    }
89
-
90
-
91
-
92
-    /**
93
-     * Gets the default where conditions that are specific to this child of EE_Default_Where_Conditions.
94
-     * Adding model relation chains is handled by the public method get_default_where_conditions
95
-     * @return array
96
-     */
97
-    protected function _get_default_where_conditions()
98
-    {
99
-        return array();
100
-    }
101
-
102
-
103
-
104
-    /**
105
-     * Takes the default query parameters, and traverses them, adding the model relation chain
106
-     * onto them (intelligently doesn't do that to logic query params like NOT, OR, and AND)
107
-     * @param array $where_conditions
108
-     * @param string $model_relation_chain
109
-     * @return array
110
-     * @throws \EE_Error
111
-     */
112
-    public function prepare_where_conditions_for_querying($where_conditions, $model_relation_chain)
113
-    {
114
-        $where_conditions_with_model_relation_chain_prefixes = array();
115
-        if (! is_array($where_conditions)) {
116
-            $where_conditions = array();
117
-        }
118
-        foreach ($where_conditions as $key => $value) {
119
-            if (in_array($key, array( 'OR', 'AND', 'NOT' ))
120
-                || strpos($key, 'OR*') !== false
121
-                || strpos($key, 'AND*') !== false
122
-                || strpos($key, 'NOT*') !== false
123
-            ) {
124
-                $where_conditions_with_model_relation_chain_prefixes[ $key ] = $this->prepare_where_conditions_for_querying(
125
-                    $value,
126
-                    $model_relation_chain
127
-                );
128
-            } else {
129
-                if ($model_relation_chain != ''
130
-                    && $model_relation_chain[ strlen($model_relation_chain) - 1 ] != '.'
131
-                ) {
132
-                    $model_relation_chain = $model_relation_chain . ".";
133
-                }
134
-                // check for the current user id place holder, and if present change it
135
-                if ($value === self::current_user_placeholder) {
136
-                    $value = get_current_user_id();
137
-                }
138
-                // check for user field placeholder
139
-                if ($key == self::user_field_name_placeholder) {
140
-                    if (! $this->_model->wp_user_field_name()) {
141
-                        throw new EE_Error(
142
-                            sprintf(
143
-                                __(
144
-                                    'There is no foreign key to the WP_User model on model %s. Please either modify your default where conditions, add a _model_chain_to_wp_user onto the model, or a proper EE_WP_User_Field onto the model',
145
-                                    'event_espresso'
146
-                                ),
147
-                                $this->_model->get_this_model_name()
148
-                            )
149
-                        );
150
-                    }
151
-                    $key = $this->_model->wp_user_field_name();
152
-                }
153
-                $where_conditions_with_model_relation_chain_prefixes[ $model_relation_chain . $key ] = $value;
154
-            }
155
-        }
156
-        return $where_conditions_with_model_relation_chain_prefixes;
157
-    }
19
+	/**
20
+	 * This const can be used in EE_Default_Where_Conditions values, and at the time of querying it will be
21
+	 * replaced with the current user's ID (because we don't want to use the current user's ID at time of
22
+	 * initializing the models because it's too early)
23
+	 */
24
+	const current_user_placeholder = '%$current_user_placeholder_should_be_replaced_automatically$%';
25
+
26
+	/**
27
+	 * This const can be used in EE_Default_Where_Conditions where parameters as the name
28
+	 * of the user field. When we are actually generating the where conditions it will be
29
+	 * replaced with the model's wp user fieldname
30
+	 */
31
+	const user_field_name_placeholder = '%$user_field_name_placeholder$%';
32
+
33
+	/**
34
+	 * Model for which this strategy find default where conditions
35
+	 * @var EEM_Base
36
+	 */
37
+	protected $_model;
38
+
39
+	/**
40
+	 * Where conditions specified on construction
41
+	 * @var array
42
+	 */
43
+	protected $_where_conditions_provided = array();
44
+
45
+	/**
46
+	 * Custom where conditions. Model relation chains will be automatically added
47
+	 * onto any field names
48
+	 * @param array $custom_where_conditions
49
+	 */
50
+	public function __construct($custom_where_conditions = array())
51
+	{
52
+		$this->_where_conditions_provided = $custom_where_conditions;
53
+	}
54
+
55
+
56
+
57
+	/**
58
+	 * finalizes construction of the strategy for use in getting default where conditions
59
+	 * for querying of the model.
60
+	 * @param EEM_Base $model
61
+	 */
62
+	public function _finalize_construct(EEM_Base $model)
63
+	{
64
+		$this->_model = $model;
65
+	}
66
+
67
+
68
+
69
+	/**
70
+	 * Returns the where conditions explicitly passed in the constructor
71
+	 * @return array
72
+	 */
73
+	public function get_where_conditions_provided()
74
+	{
75
+		return $this->_where_conditions_provided;
76
+	}
77
+
78
+
79
+
80
+	/**
81
+	 * Gets the where conditions to be added onto the query
82
+	 * @param string $model_relation_chain
83
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
84
+	 */
85
+	public function get_default_where_conditions($model_relation_chain = '')
86
+	{
87
+		return $this->prepare_where_conditions_for_querying(array_merge($this->_get_default_where_conditions(), $this->get_where_conditions_provided()), $model_relation_chain);
88
+	}
89
+
90
+
91
+
92
+	/**
93
+	 * Gets the default where conditions that are specific to this child of EE_Default_Where_Conditions.
94
+	 * Adding model relation chains is handled by the public method get_default_where_conditions
95
+	 * @return array
96
+	 */
97
+	protected function _get_default_where_conditions()
98
+	{
99
+		return array();
100
+	}
101
+
102
+
103
+
104
+	/**
105
+	 * Takes the default query parameters, and traverses them, adding the model relation chain
106
+	 * onto them (intelligently doesn't do that to logic query params like NOT, OR, and AND)
107
+	 * @param array $where_conditions
108
+	 * @param string $model_relation_chain
109
+	 * @return array
110
+	 * @throws \EE_Error
111
+	 */
112
+	public function prepare_where_conditions_for_querying($where_conditions, $model_relation_chain)
113
+	{
114
+		$where_conditions_with_model_relation_chain_prefixes = array();
115
+		if (! is_array($where_conditions)) {
116
+			$where_conditions = array();
117
+		}
118
+		foreach ($where_conditions as $key => $value) {
119
+			if (in_array($key, array( 'OR', 'AND', 'NOT' ))
120
+				|| strpos($key, 'OR*') !== false
121
+				|| strpos($key, 'AND*') !== false
122
+				|| strpos($key, 'NOT*') !== false
123
+			) {
124
+				$where_conditions_with_model_relation_chain_prefixes[ $key ] = $this->prepare_where_conditions_for_querying(
125
+					$value,
126
+					$model_relation_chain
127
+				);
128
+			} else {
129
+				if ($model_relation_chain != ''
130
+					&& $model_relation_chain[ strlen($model_relation_chain) - 1 ] != '.'
131
+				) {
132
+					$model_relation_chain = $model_relation_chain . ".";
133
+				}
134
+				// check for the current user id place holder, and if present change it
135
+				if ($value === self::current_user_placeholder) {
136
+					$value = get_current_user_id();
137
+				}
138
+				// check for user field placeholder
139
+				if ($key == self::user_field_name_placeholder) {
140
+					if (! $this->_model->wp_user_field_name()) {
141
+						throw new EE_Error(
142
+							sprintf(
143
+								__(
144
+									'There is no foreign key to the WP_User model on model %s. Please either modify your default where conditions, add a _model_chain_to_wp_user onto the model, or a proper EE_WP_User_Field onto the model',
145
+									'event_espresso'
146
+								),
147
+								$this->_model->get_this_model_name()
148
+							)
149
+						);
150
+					}
151
+					$key = $this->_model->wp_user_field_name();
152
+				}
153
+				$where_conditions_with_model_relation_chain_prefixes[ $model_relation_chain . $key ] = $value;
154
+			}
155
+		}
156
+		return $where_conditions_with_model_relation_chain_prefixes;
157
+	}
158 158
 }
Please login to merge, or discard this patch.
core/db_models/strategies/EE_Soft_Delete_Where_Conditions.strategy.php 1 patch
Indentation   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -13,41 +13,41 @@
 block discarded – undo
13 13
  */
14 14
 class EE_Soft_Delete_Where_Conditions extends EE_Default_Where_Conditions
15 15
 {
16
-    /**
17
-     * Strategy for setting default soft delete where conditions. This strategy will find
18
-     * the field of type 'EE_Trashed_Flag_Field', and add a condition that it be FALSE on all queries involving
19
-     * the model.
20
-     * If you want to override these default where conditions, you may explicitly in the query you send to the model.
21
-     * Eg,
22
-     *
23
-     */
24
-    public function __construct()
25
-    {
26
-    }
27
-    /**
28
-     * Gets the where default where conditions for a custom post type model
29
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
30
-     */
31
-    protected function _get_default_where_conditions()
32
-    {
33
-        $trashed_field_name = $this->deleted_field_name();
34
-        return array(
35
-            $trashed_field_name => false
36
-        );
37
-    }
38
-    /**
39
-     * Searches for field on the model of type 'deleted_flag'. if it is found,
40
-     * returns it's name.
41
-     * @return string
42
-     * @throws EE_Error
43
-     */
44
-    private function deleted_field_name()
45
-    {
46
-        $field = $this->_model->get_a_field_of_type('EE_Trashed_Flag_Field');
47
-        if ($field) {
48
-            return $field->get_name();
49
-        } else {
50
-            throw new EE_Error(sprintf(__('We are trying to find the deleted flag field on %s, but none was found. Are you sure there is a field of type EE_Trashed_Flag_Field in %s constructor?', 'event_espresso'), get_class($this), get_class($this)));
51
-        }
52
-    }
16
+	/**
17
+	 * Strategy for setting default soft delete where conditions. This strategy will find
18
+	 * the field of type 'EE_Trashed_Flag_Field', and add a condition that it be FALSE on all queries involving
19
+	 * the model.
20
+	 * If you want to override these default where conditions, you may explicitly in the query you send to the model.
21
+	 * Eg,
22
+	 *
23
+	 */
24
+	public function __construct()
25
+	{
26
+	}
27
+	/**
28
+	 * Gets the where default where conditions for a custom post type model
29
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
30
+	 */
31
+	protected function _get_default_where_conditions()
32
+	{
33
+		$trashed_field_name = $this->deleted_field_name();
34
+		return array(
35
+			$trashed_field_name => false
36
+		);
37
+	}
38
+	/**
39
+	 * Searches for field on the model of type 'deleted_flag'. if it is found,
40
+	 * returns it's name.
41
+	 * @return string
42
+	 * @throws EE_Error
43
+	 */
44
+	private function deleted_field_name()
45
+	{
46
+		$field = $this->_model->get_a_field_of_type('EE_Trashed_Flag_Field');
47
+		if ($field) {
48
+			return $field->get_name();
49
+		} else {
50
+			throw new EE_Error(sprintf(__('We are trying to find the deleted flag field on %s, but none was found. Are you sure there is a field of type EE_Trashed_Flag_Field in %s constructor?', 'event_espresso'), get_class($this), get_class($this)));
51
+		}
52
+	}
53 53
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 1 patch
Indentation   +3140 added lines, -3140 removed lines patch added patch discarded remove patch
@@ -13,3144 +13,3144 @@
 block discarded – undo
13 13
 abstract class EE_Base_Class
14 14
 {
15 15
 
16
-    /**
17
-     * This is an array of the original properties and values provided during construction
18
-     * of this model object. (keys are model field names, values are their values).
19
-     * This list is important to remember so that when we are merging data from the db, we know
20
-     * which values to override and which to not override.
21
-     *
22
-     * @var array
23
-     */
24
-    protected $_props_n_values_provided_in_constructor;
25
-
26
-    /**
27
-     * Timezone
28
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
-     * access to it.
32
-     *
33
-     * @var string
34
-     */
35
-    protected $_timezone;
36
-
37
-    /**
38
-     * date format
39
-     * pattern or format for displaying dates
40
-     *
41
-     * @var string $_dt_frmt
42
-     */
43
-    protected $_dt_frmt;
44
-
45
-    /**
46
-     * time format
47
-     * pattern or format for displaying time
48
-     *
49
-     * @var string $_tm_frmt
50
-     */
51
-    protected $_tm_frmt;
52
-
53
-    /**
54
-     * This property is for holding a cached array of object properties indexed by property name as the key.
55
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
56
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
-     *
59
-     * @var array
60
-     */
61
-    protected $_cached_properties = array();
62
-
63
-    /**
64
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
65
-     * single
66
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
-     * all others have an array)
69
-     *
70
-     * @var array
71
-     */
72
-    protected $_model_relations = array();
73
-
74
-    /**
75
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
-     *
78
-     * @var array
79
-     */
80
-    protected $_fields = array();
81
-
82
-    /**
83
-     * @var boolean indicating whether or not this model object is intended to ever be saved
84
-     * For example, we might create model objects intended to only be used for the duration
85
-     * of this request and to be thrown away, and if they were accidentally saved
86
-     * it would be a bug.
87
-     */
88
-    protected $_allow_persist = true;
89
-
90
-    /**
91
-     * @var boolean indicating whether or not this model object's properties have changed since construction
92
-     */
93
-    protected $_has_changes = false;
94
-
95
-    /**
96
-     * @var EEM_Base
97
-     */
98
-    protected $_model;
99
-
100
-    /**
101
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
-     * array as:
107
-     * array(
108
-     *  'Registration_Count' => 24
109
-     * );
110
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
-     * info)
113
-     *
114
-     * @var array
115
-     */
116
-    protected $custom_selection_results = array();
117
-
118
-
119
-    /**
120
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
-     * play nice
122
-     *
123
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
-     *                                                         TXN_amount, QST_name, etc) and values are their values
126
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
127
-     *                                                         corresponding db model or not.
128
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
-     *                                                         be in when instantiating a EE_Base_Class object.
130
-     * @param array   $date_formats                            An array of date formats to set on construct where first
131
-     *                                                         value is the date_format and second value is the time
132
-     *                                                         format.
133
-     * @throws InvalidArgumentException
134
-     * @throws InvalidInterfaceException
135
-     * @throws InvalidDataTypeException
136
-     * @throws EE_Error
137
-     * @throws ReflectionException
138
-     */
139
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
140
-    {
141
-        $className = get_class($this);
142
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
-        $model = $this->get_model();
144
-        $model_fields = $model->field_settings(false);
145
-        // ensure $fieldValues is an array
146
-        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
147
-        // verify client code has not passed any invalid field names
148
-        foreach ($fieldValues as $field_name => $field_value) {
149
-            if (! isset($model_fields[ $field_name ])) {
150
-                throw new EE_Error(
151
-                    sprintf(
152
-                        esc_html__(
153
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
-                            'event_espresso'
155
-                        ),
156
-                        $field_name,
157
-                        get_class($this),
158
-                        implode(', ', array_keys($model_fields))
159
-                    )
160
-                );
161
-            }
162
-        }
163
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
-        if (! empty($date_formats) && is_array($date_formats)) {
165
-            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
166
-        } else {
167
-            // set default formats for date and time
168
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170
-        }
171
-        // if db model is instantiating
172
-        if ($bydb) {
173
-            // client code has indicated these field values are from the database
174
-            foreach ($model_fields as $fieldName => $field) {
175
-                $this->set_from_db(
176
-                    $fieldName,
177
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
-                );
179
-            }
180
-        } else {
181
-            // we're constructing a brand
182
-            // new instance of the model object. Generally, this means we'll need to do more field validation
183
-            foreach ($model_fields as $fieldName => $field) {
184
-                $this->set(
185
-                    $fieldName,
186
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
-                    true
188
-                );
189
-            }
190
-        }
191
-        // remember what values were passed to this constructor
192
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
193
-        // remember in entity mapper
194
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
-            $model->add_to_entity_map($this);
196
-        }
197
-        // setup all the relations
198
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
199
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
-                $this->_model_relations[ $relation_name ] = null;
201
-            } else {
202
-                $this->_model_relations[ $relation_name ] = array();
203
-            }
204
-        }
205
-        /**
206
-         * Action done at the end of each model object construction
207
-         *
208
-         * @param EE_Base_Class $this the model object just created
209
-         */
210
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
-    }
212
-
213
-
214
-    /**
215
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
216
-     *
217
-     * @return boolean
218
-     */
219
-    public function allow_persist()
220
-    {
221
-        return $this->_allow_persist;
222
-    }
223
-
224
-
225
-    /**
226
-     * Sets whether or not this model object should be allowed to be saved to the DB.
227
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
-     * you got new information that somehow made you change your mind.
229
-     *
230
-     * @param boolean $allow_persist
231
-     * @return boolean
232
-     */
233
-    public function set_allow_persist($allow_persist)
234
-    {
235
-        return $this->_allow_persist = $allow_persist;
236
-    }
237
-
238
-
239
-    /**
240
-     * Gets the field's original value when this object was constructed during this request.
241
-     * This can be helpful when determining if a model object has changed or not
242
-     *
243
-     * @param string $field_name
244
-     * @return mixed|null
245
-     * @throws ReflectionException
246
-     * @throws InvalidArgumentException
247
-     * @throws InvalidInterfaceException
248
-     * @throws InvalidDataTypeException
249
-     * @throws EE_Error
250
-     */
251
-    public function get_original($field_name)
252
-    {
253
-        if (isset($this->_props_n_values_provided_in_constructor[ $field_name ])
254
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
255
-        ) {
256
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
257
-        }
258
-        return null;
259
-    }
260
-
261
-
262
-    /**
263
-     * @param EE_Base_Class $obj
264
-     * @return string
265
-     */
266
-    public function get_class($obj)
267
-    {
268
-        return get_class($obj);
269
-    }
270
-
271
-
272
-    /**
273
-     * Overrides parent because parent expects old models.
274
-     * This also doesn't do any validation, and won't work for serialized arrays
275
-     *
276
-     * @param    string $field_name
277
-     * @param    mixed  $field_value
278
-     * @param bool      $use_default
279
-     * @throws InvalidArgumentException
280
-     * @throws InvalidInterfaceException
281
-     * @throws InvalidDataTypeException
282
-     * @throws EE_Error
283
-     * @throws ReflectionException
284
-     * @throws ReflectionException
285
-     * @throws ReflectionException
286
-     */
287
-    public function set($field_name, $field_value, $use_default = false)
288
-    {
289
-        // if not using default and nothing has changed, and object has already been setup (has ID),
290
-        // then don't do anything
291
-        if (! $use_default
292
-            && $this->_fields[ $field_name ] === $field_value
293
-            && $this->ID()
294
-        ) {
295
-            return;
296
-        }
297
-        $model = $this->get_model();
298
-        $this->_has_changes = true;
299
-        $field_obj = $model->field_settings_for($field_name);
300
-        if ($field_obj instanceof EE_Model_Field_Base) {
301
-            // if ( method_exists( $field_obj, 'set_timezone' )) {
302
-            if ($field_obj instanceof EE_Datetime_Field) {
303
-                $field_obj->set_timezone($this->_timezone);
304
-                $field_obj->set_date_format($this->_dt_frmt);
305
-                $field_obj->set_time_format($this->_tm_frmt);
306
-            }
307
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
308
-            // should the value be null?
309
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
310
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
311
-                /**
312
-                 * To save having to refactor all the models, if a default value is used for a
313
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
314
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
315
-                 * object.
316
-                 *
317
-                 * @since 4.6.10+
318
-                 */
319
-                if ($field_obj instanceof EE_Datetime_Field
320
-                    && $this->_fields[ $field_name ] !== null
321
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
322
-                ) {
323
-                    empty($this->_fields[ $field_name ])
324
-                        ? $this->set($field_name, time())
325
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
326
-                }
327
-            } else {
328
-                $this->_fields[ $field_name ] = $holder_of_value;
329
-            }
330
-            // if we're not in the constructor...
331
-            // now check if what we set was a primary key
332
-            if (// note: props_n_values_provided_in_constructor is only set at the END of the constructor
333
-                $this->_props_n_values_provided_in_constructor
334
-                && $field_value
335
-                && $field_name === $model->primary_key_name()
336
-            ) {
337
-                // if so, we want all this object's fields to be filled either with
338
-                // what we've explicitly set on this model
339
-                // or what we have in the db
340
-                // echo "setting primary key!";
341
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
342
-                $obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
343
-                foreach ($fields_on_model as $field_obj) {
344
-                    if (! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
345
-                        && $field_obj->get_name() !== $field_name
346
-                    ) {
347
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
348
-                    }
349
-                }
350
-                // oh this model object has an ID? well make sure its in the entity mapper
351
-                $model->add_to_entity_map($this);
352
-            }
353
-            // let's unset any cache for this field_name from the $_cached_properties property.
354
-            $this->_clear_cached_property($field_name);
355
-        } else {
356
-            throw new EE_Error(
357
-                sprintf(
358
-                    esc_html__(
359
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
360
-                        'event_espresso'
361
-                    ),
362
-                    $field_name
363
-                )
364
-            );
365
-        }
366
-    }
367
-
368
-
369
-    /**
370
-     * Set custom select values for model.
371
-     *
372
-     * @param array $custom_select_values
373
-     */
374
-    public function setCustomSelectsValues(array $custom_select_values)
375
-    {
376
-        $this->custom_selection_results = $custom_select_values;
377
-    }
378
-
379
-
380
-    /**
381
-     * Returns the custom select value for the provided alias if its set.
382
-     * If not set, returns null.
383
-     *
384
-     * @param string $alias
385
-     * @return string|int|float|null
386
-     */
387
-    public function getCustomSelect($alias)
388
-    {
389
-        return isset($this->custom_selection_results[ $alias ])
390
-            ? $this->custom_selection_results[ $alias ]
391
-            : null;
392
-    }
393
-
394
-
395
-    /**
396
-     * This sets the field value on the db column if it exists for the given $column_name or
397
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
398
-     *
399
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
400
-     * @param string $field_name  Must be the exact column name.
401
-     * @param mixed  $field_value The value to set.
402
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
403
-     * @throws InvalidArgumentException
404
-     * @throws InvalidInterfaceException
405
-     * @throws InvalidDataTypeException
406
-     * @throws EE_Error
407
-     * @throws ReflectionException
408
-     */
409
-    public function set_field_or_extra_meta($field_name, $field_value)
410
-    {
411
-        if ($this->get_model()->has_field($field_name)) {
412
-            $this->set($field_name, $field_value);
413
-            return true;
414
-        }
415
-        // ensure this object is saved first so that extra meta can be properly related.
416
-        $this->save();
417
-        return $this->update_extra_meta($field_name, $field_value);
418
-    }
419
-
420
-
421
-    /**
422
-     * This retrieves the value of the db column set on this class or if that's not present
423
-     * it will attempt to retrieve from extra_meta if found.
424
-     * Example Usage:
425
-     * Via EE_Message child class:
426
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
427
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
428
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
429
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
430
-     * value for those extra fields dynamically via the EE_message object.
431
-     *
432
-     * @param  string $field_name expecting the fully qualified field name.
433
-     * @return mixed|null  value for the field if found.  null if not found.
434
-     * @throws ReflectionException
435
-     * @throws InvalidArgumentException
436
-     * @throws InvalidInterfaceException
437
-     * @throws InvalidDataTypeException
438
-     * @throws EE_Error
439
-     */
440
-    public function get_field_or_extra_meta($field_name)
441
-    {
442
-        if ($this->get_model()->has_field($field_name)) {
443
-            $column_value = $this->get($field_name);
444
-        } else {
445
-            // This isn't a column in the main table, let's see if it is in the extra meta.
446
-            $column_value = $this->get_extra_meta($field_name, true, null);
447
-        }
448
-        return $column_value;
449
-    }
450
-
451
-
452
-    /**
453
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
454
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
455
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
456
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
457
-     *
458
-     * @access public
459
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
460
-     * @return void
461
-     * @throws InvalidArgumentException
462
-     * @throws InvalidInterfaceException
463
-     * @throws InvalidDataTypeException
464
-     * @throws EE_Error
465
-     * @throws ReflectionException
466
-     */
467
-    public function set_timezone($timezone = '')
468
-    {
469
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
470
-        // make sure we clear all cached properties because they won't be relevant now
471
-        $this->_clear_cached_properties();
472
-        // make sure we update field settings and the date for all EE_Datetime_Fields
473
-        $model_fields = $this->get_model()->field_settings(false);
474
-        foreach ($model_fields as $field_name => $field_obj) {
475
-            if ($field_obj instanceof EE_Datetime_Field) {
476
-                $field_obj->set_timezone($this->_timezone);
477
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
478
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
479
-                }
480
-            }
481
-        }
482
-    }
483
-
484
-
485
-    /**
486
-     * This just returns whatever is set for the current timezone.
487
-     *
488
-     * @access public
489
-     * @return string timezone string
490
-     */
491
-    public function get_timezone()
492
-    {
493
-        return $this->_timezone;
494
-    }
495
-
496
-
497
-    /**
498
-     * This sets the internal date format to what is sent in to be used as the new default for the class
499
-     * internally instead of wp set date format options
500
-     *
501
-     * @since 4.6
502
-     * @param string $format should be a format recognizable by PHP date() functions.
503
-     */
504
-    public function set_date_format($format)
505
-    {
506
-        $this->_dt_frmt = $format;
507
-        // clear cached_properties because they won't be relevant now.
508
-        $this->_clear_cached_properties();
509
-    }
510
-
511
-
512
-    /**
513
-     * This sets the internal time format string to what is sent in to be used as the new default for the
514
-     * class internally instead of wp set time format options.
515
-     *
516
-     * @since 4.6
517
-     * @param string $format should be a format recognizable by PHP date() functions.
518
-     */
519
-    public function set_time_format($format)
520
-    {
521
-        $this->_tm_frmt = $format;
522
-        // clear cached_properties because they won't be relevant now.
523
-        $this->_clear_cached_properties();
524
-    }
525
-
526
-
527
-    /**
528
-     * This returns the current internal set format for the date and time formats.
529
-     *
530
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
531
-     *                             where the first value is the date format and the second value is the time format.
532
-     * @return mixed string|array
533
-     */
534
-    public function get_format($full = true)
535
-    {
536
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
537
-    }
538
-
539
-
540
-    /**
541
-     * cache
542
-     * stores the passed model object on the current model object.
543
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
544
-     *
545
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
546
-     *                                       'Registration' associated with this model object
547
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
548
-     *                                       that could be a payment or a registration)
549
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
550
-     *                                       items which will be stored in an array on this object
551
-     * @throws ReflectionException
552
-     * @throws InvalidArgumentException
553
-     * @throws InvalidInterfaceException
554
-     * @throws InvalidDataTypeException
555
-     * @throws EE_Error
556
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
557
-     *                                       related thing, no array)
558
-     */
559
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
560
-    {
561
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
562
-        if (! $object_to_cache instanceof EE_Base_Class) {
563
-            return false;
564
-        }
565
-        // also get "how" the object is related, or throw an error
566
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
567
-            throw new EE_Error(
568
-                sprintf(
569
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
570
-                    $relationName,
571
-                    get_class($this)
572
-                )
573
-            );
574
-        }
575
-        // how many things are related ?
576
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
577
-            // if it's a "belongs to" relationship, then there's only one related model object
578
-            // eg, if this is a registration, there's only 1 attendee for it
579
-            // so for these model objects just set it to be cached
580
-            $this->_model_relations[ $relationName ] = $object_to_cache;
581
-            $return = true;
582
-        } else {
583
-            // otherwise, this is the "many" side of a one to many relationship,
584
-            // so we'll add the object to the array of related objects for that type.
585
-            // eg: if this is an event, there are many registrations for that event,
586
-            // so we cache the registrations in an array
587
-            if (! is_array($this->_model_relations[ $relationName ])) {
588
-                // if for some reason, the cached item is a model object,
589
-                // then stick that in the array, otherwise start with an empty array
590
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
591
-                                                           instanceof
592
-                                                           EE_Base_Class
593
-                    ? array($this->_model_relations[ $relationName ]) : array();
594
-            }
595
-            // first check for a cache_id which is normally empty
596
-            if (! empty($cache_id)) {
597
-                // if the cache_id exists, then it means we are purposely trying to cache this
598
-                // with a known key that can then be used to retrieve the object later on
599
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
600
-                $return = $cache_id;
601
-            } elseif ($object_to_cache->ID()) {
602
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
603
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
604
-                $return = $object_to_cache->ID();
605
-            } else {
606
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
607
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
608
-                // move the internal pointer to the end of the array
609
-                end($this->_model_relations[ $relationName ]);
610
-                // and grab the key so that we can return it
611
-                $return = key($this->_model_relations[ $relationName ]);
612
-            }
613
-        }
614
-        return $return;
615
-    }
616
-
617
-
618
-    /**
619
-     * For adding an item to the cached_properties property.
620
-     *
621
-     * @access protected
622
-     * @param string      $fieldname the property item the corresponding value is for.
623
-     * @param mixed       $value     The value we are caching.
624
-     * @param string|null $cache_type
625
-     * @return void
626
-     * @throws ReflectionException
627
-     * @throws InvalidArgumentException
628
-     * @throws InvalidInterfaceException
629
-     * @throws InvalidDataTypeException
630
-     * @throws EE_Error
631
-     */
632
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
633
-    {
634
-        // first make sure this property exists
635
-        $this->get_model()->field_settings_for($fieldname);
636
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
637
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
638
-    }
639
-
640
-
641
-    /**
642
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
643
-     * This also SETS the cache if we return the actual property!
644
-     *
645
-     * @param string $fieldname        the name of the property we're trying to retrieve
646
-     * @param bool   $pretty
647
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
648
-     *                                 (in cases where the same property may be used for different outputs
649
-     *                                 - i.e. datetime, money etc.)
650
-     *                                 It can also accept certain pre-defined "schema" strings
651
-     *                                 to define how to output the property.
652
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
653
-     * @return mixed                   whatever the value for the property is we're retrieving
654
-     * @throws ReflectionException
655
-     * @throws InvalidArgumentException
656
-     * @throws InvalidInterfaceException
657
-     * @throws InvalidDataTypeException
658
-     * @throws EE_Error
659
-     */
660
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
661
-    {
662
-        // verify the field exists
663
-        $model = $this->get_model();
664
-        $model->field_settings_for($fieldname);
665
-        $cache_type = $pretty ? 'pretty' : 'standard';
666
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
667
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
668
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
669
-        }
670
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
671
-        $this->_set_cached_property($fieldname, $value, $cache_type);
672
-        return $value;
673
-    }
674
-
675
-
676
-    /**
677
-     * If the cache didn't fetch the needed item, this fetches it.
678
-     *
679
-     * @param string $fieldname
680
-     * @param bool   $pretty
681
-     * @param string $extra_cache_ref
682
-     * @return mixed
683
-     * @throws InvalidArgumentException
684
-     * @throws InvalidInterfaceException
685
-     * @throws InvalidDataTypeException
686
-     * @throws EE_Error
687
-     * @throws ReflectionException
688
-     */
689
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
690
-    {
691
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
692
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
693
-        if ($field_obj instanceof EE_Datetime_Field) {
694
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
695
-        }
696
-        if (! isset($this->_fields[ $fieldname ])) {
697
-            $this->_fields[ $fieldname ] = null;
698
-        }
699
-        $value = $pretty
700
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
701
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
702
-        return $value;
703
-    }
704
-
705
-
706
-    /**
707
-     * set timezone, formats, and output for EE_Datetime_Field objects
708
-     *
709
-     * @param \EE_Datetime_Field $datetime_field
710
-     * @param bool               $pretty
711
-     * @param null               $date_or_time
712
-     * @return void
713
-     * @throws InvalidArgumentException
714
-     * @throws InvalidInterfaceException
715
-     * @throws InvalidDataTypeException
716
-     * @throws EE_Error
717
-     */
718
-    protected function _prepare_datetime_field(
719
-        EE_Datetime_Field $datetime_field,
720
-        $pretty = false,
721
-        $date_or_time = null
722
-    ) {
723
-        $datetime_field->set_timezone($this->_timezone);
724
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
725
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
726
-        // set the output returned
727
-        switch ($date_or_time) {
728
-            case 'D':
729
-                $datetime_field->set_date_time_output('date');
730
-                break;
731
-            case 'T':
732
-                $datetime_field->set_date_time_output('time');
733
-                break;
734
-            default:
735
-                $datetime_field->set_date_time_output();
736
-        }
737
-    }
738
-
739
-
740
-    /**
741
-     * This just takes care of clearing out the cached_properties
742
-     *
743
-     * @return void
744
-     */
745
-    protected function _clear_cached_properties()
746
-    {
747
-        $this->_cached_properties = array();
748
-    }
749
-
750
-
751
-    /**
752
-     * This just clears out ONE property if it exists in the cache
753
-     *
754
-     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
755
-     * @return void
756
-     */
757
-    protected function _clear_cached_property($property_name)
758
-    {
759
-        if (isset($this->_cached_properties[ $property_name ])) {
760
-            unset($this->_cached_properties[ $property_name ]);
761
-        }
762
-    }
763
-
764
-
765
-    /**
766
-     * Ensures that this related thing is a model object.
767
-     *
768
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
769
-     * @param string $model_name   name of the related thing, eg 'Attendee',
770
-     * @return EE_Base_Class
771
-     * @throws ReflectionException
772
-     * @throws InvalidArgumentException
773
-     * @throws InvalidInterfaceException
774
-     * @throws InvalidDataTypeException
775
-     * @throws EE_Error
776
-     */
777
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
778
-    {
779
-        $other_model_instance = self::_get_model_instance_with_name(
780
-            self::_get_model_classname($model_name),
781
-            $this->_timezone
782
-        );
783
-        return $other_model_instance->ensure_is_obj($object_or_id);
784
-    }
785
-
786
-
787
-    /**
788
-     * Forgets the cached model of the given relation Name. So the next time we request it,
789
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
790
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
791
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
792
-     *
793
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
794
-     *                                                     Eg 'Registration'
795
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
796
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
797
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
798
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
799
-     *                                                     this is HasMany or HABTM.
800
-     * @throws ReflectionException
801
-     * @throws InvalidArgumentException
802
-     * @throws InvalidInterfaceException
803
-     * @throws InvalidDataTypeException
804
-     * @throws EE_Error
805
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
806
-     *                                                     relation from all
807
-     */
808
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
809
-    {
810
-        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
811
-        $index_in_cache = '';
812
-        if (! $relationship_to_model) {
813
-            throw new EE_Error(
814
-                sprintf(
815
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
816
-                    $relationName,
817
-                    get_class($this)
818
-                )
819
-            );
820
-        }
821
-        if ($clear_all) {
822
-            $obj_removed = true;
823
-            $this->_model_relations[ $relationName ] = null;
824
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
825
-            $obj_removed = $this->_model_relations[ $relationName ];
826
-            $this->_model_relations[ $relationName ] = null;
827
-        } else {
828
-            if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
829
-                && $object_to_remove_or_index_into_array->ID()
830
-            ) {
831
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
832
-                if (is_array($this->_model_relations[ $relationName ])
833
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
834
-                ) {
835
-                    $index_found_at = null;
836
-                    // find this object in the array even though it has a different key
837
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
838
-                        /** @noinspection TypeUnsafeComparisonInspection */
839
-                        if ($obj instanceof EE_Base_Class
840
-                            && (
841
-                                $obj == $object_to_remove_or_index_into_array
842
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
843
-                            )
844
-                        ) {
845
-                            $index_found_at = $index;
846
-                            break;
847
-                        }
848
-                    }
849
-                    if ($index_found_at) {
850
-                        $index_in_cache = $index_found_at;
851
-                    } else {
852
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
853
-                        // if it wasn't in it to begin with. So we're done
854
-                        return $object_to_remove_or_index_into_array;
855
-                    }
856
-                }
857
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
858
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
859
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
860
-                    /** @noinspection TypeUnsafeComparisonInspection */
861
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
862
-                        $index_in_cache = $index;
863
-                    }
864
-                }
865
-            } else {
866
-                $index_in_cache = $object_to_remove_or_index_into_array;
867
-            }
868
-            // supposedly we've found it. But it could just be that the client code
869
-            // provided a bad index/object
870
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
871
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
872
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
873
-            } else {
874
-                // that thing was never cached anyways.
875
-                $obj_removed = null;
876
-            }
877
-        }
878
-        return $obj_removed;
879
-    }
880
-
881
-
882
-    /**
883
-     * update_cache_after_object_save
884
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
885
-     * obtained after being saved to the db
886
-     *
887
-     * @param string        $relationName       - the type of object that is cached
888
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
889
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
890
-     * @return boolean TRUE on success, FALSE on fail
891
-     * @throws ReflectionException
892
-     * @throws InvalidArgumentException
893
-     * @throws InvalidInterfaceException
894
-     * @throws InvalidDataTypeException
895
-     * @throws EE_Error
896
-     */
897
-    public function update_cache_after_object_save(
898
-        $relationName,
899
-        EE_Base_Class $newly_saved_object,
900
-        $current_cache_id = ''
901
-    ) {
902
-        // verify that incoming object is of the correct type
903
-        $obj_class = 'EE_' . $relationName;
904
-        if ($newly_saved_object instanceof $obj_class) {
905
-            /* @type EE_Base_Class $newly_saved_object */
906
-            // now get the type of relation
907
-            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
908
-            // if this is a 1:1 relationship
909
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
910
-                // then just replace the cached object with the newly saved object
911
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
912
-                return true;
913
-                // or if it's some kind of sordid feral polyamorous relationship...
914
-            }
915
-            if (is_array($this->_model_relations[ $relationName ])
916
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
917
-            ) {
918
-                // then remove the current cached item
919
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
920
-                // and cache the newly saved object using it's new ID
921
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
922
-                return true;
923
-            }
924
-        }
925
-        return false;
926
-    }
927
-
928
-
929
-    /**
930
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
931
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
932
-     *
933
-     * @param string $relationName
934
-     * @return EE_Base_Class
935
-     */
936
-    public function get_one_from_cache($relationName)
937
-    {
938
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
939
-            ? $this->_model_relations[ $relationName ]
940
-            : null;
941
-        if (is_array($cached_array_or_object)) {
942
-            return array_shift($cached_array_or_object);
943
-        }
944
-        return $cached_array_or_object;
945
-    }
946
-
947
-
948
-    /**
949
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
950
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
951
-     *
952
-     * @param string $relationName
953
-     * @throws ReflectionException
954
-     * @throws InvalidArgumentException
955
-     * @throws InvalidInterfaceException
956
-     * @throws InvalidDataTypeException
957
-     * @throws EE_Error
958
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
959
-     */
960
-    public function get_all_from_cache($relationName)
961
-    {
962
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
963
-        // if the result is not an array, but exists, make it an array
964
-        $objects = is_array($objects) ? $objects : array($objects);
965
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
966
-        // basically, if this model object was stored in the session, and these cached model objects
967
-        // already have IDs, let's make sure they're in their model's entity mapper
968
-        // otherwise we will have duplicates next time we call
969
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
970
-        $model = EE_Registry::instance()->load_model($relationName);
971
-        foreach ($objects as $model_object) {
972
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
973
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
974
-                if ($model_object->ID()) {
975
-                    $model->add_to_entity_map($model_object);
976
-                }
977
-            } else {
978
-                throw new EE_Error(
979
-                    sprintf(
980
-                        esc_html__(
981
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
982
-                            'event_espresso'
983
-                        ),
984
-                        $relationName,
985
-                        gettype($model_object)
986
-                    )
987
-                );
988
-            }
989
-        }
990
-        return $objects;
991
-    }
992
-
993
-
994
-    /**
995
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
996
-     * matching the given query conditions.
997
-     *
998
-     * @param null  $field_to_order_by  What field is being used as the reference point.
999
-     * @param int   $limit              How many objects to return.
1000
-     * @param array $query_params       Any additional conditions on the query.
1001
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1002
-     *                                  you can indicate just the columns you want returned
1003
-     * @return array|EE_Base_Class[]
1004
-     * @throws ReflectionException
1005
-     * @throws InvalidArgumentException
1006
-     * @throws InvalidInterfaceException
1007
-     * @throws InvalidDataTypeException
1008
-     * @throws EE_Error
1009
-     */
1010
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1011
-    {
1012
-        $model = $this->get_model();
1013
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1014
-            ? $model->get_primary_key_field()->get_name()
1015
-            : $field_to_order_by;
1016
-        $current_value = ! empty($field) ? $this->get($field) : null;
1017
-        if (empty($field) || empty($current_value)) {
1018
-            return array();
1019
-        }
1020
-        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1021
-    }
1022
-
1023
-
1024
-    /**
1025
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1026
-     * matching the given query conditions.
1027
-     *
1028
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1029
-     * @param int   $limit              How many objects to return.
1030
-     * @param array $query_params       Any additional conditions on the query.
1031
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1032
-     *                                  you can indicate just the columns you want returned
1033
-     * @return array|EE_Base_Class[]
1034
-     * @throws ReflectionException
1035
-     * @throws InvalidArgumentException
1036
-     * @throws InvalidInterfaceException
1037
-     * @throws InvalidDataTypeException
1038
-     * @throws EE_Error
1039
-     */
1040
-    public function previous_x(
1041
-        $field_to_order_by = null,
1042
-        $limit = 1,
1043
-        $query_params = array(),
1044
-        $columns_to_select = null
1045
-    ) {
1046
-        $model = $this->get_model();
1047
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1048
-            ? $model->get_primary_key_field()->get_name()
1049
-            : $field_to_order_by;
1050
-        $current_value = ! empty($field) ? $this->get($field) : null;
1051
-        if (empty($field) || empty($current_value)) {
1052
-            return array();
1053
-        }
1054
-        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1055
-    }
1056
-
1057
-
1058
-    /**
1059
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1060
-     * matching the given query conditions.
1061
-     *
1062
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1063
-     * @param array $query_params       Any additional conditions on the query.
1064
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1065
-     *                                  you can indicate just the columns you want returned
1066
-     * @return array|EE_Base_Class
1067
-     * @throws ReflectionException
1068
-     * @throws InvalidArgumentException
1069
-     * @throws InvalidInterfaceException
1070
-     * @throws InvalidDataTypeException
1071
-     * @throws EE_Error
1072
-     */
1073
-    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1074
-    {
1075
-        $model = $this->get_model();
1076
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1077
-            ? $model->get_primary_key_field()->get_name()
1078
-            : $field_to_order_by;
1079
-        $current_value = ! empty($field) ? $this->get($field) : null;
1080
-        if (empty($field) || empty($current_value)) {
1081
-            return array();
1082
-        }
1083
-        return $model->next($current_value, $field, $query_params, $columns_to_select);
1084
-    }
1085
-
1086
-
1087
-    /**
1088
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1089
-     * matching the given query conditions.
1090
-     *
1091
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1092
-     * @param array $query_params       Any additional conditions on the query.
1093
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1094
-     *                                  you can indicate just the column you want returned
1095
-     * @return array|EE_Base_Class
1096
-     * @throws ReflectionException
1097
-     * @throws InvalidArgumentException
1098
-     * @throws InvalidInterfaceException
1099
-     * @throws InvalidDataTypeException
1100
-     * @throws EE_Error
1101
-     */
1102
-    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1103
-    {
1104
-        $model = $this->get_model();
1105
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1106
-            ? $model->get_primary_key_field()->get_name()
1107
-            : $field_to_order_by;
1108
-        $current_value = ! empty($field) ? $this->get($field) : null;
1109
-        if (empty($field) || empty($current_value)) {
1110
-            return array();
1111
-        }
1112
-        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1113
-    }
1114
-
1115
-
1116
-    /**
1117
-     * Overrides parent because parent expects old models.
1118
-     * This also doesn't do any validation, and won't work for serialized arrays
1119
-     *
1120
-     * @param string $field_name
1121
-     * @param mixed  $field_value_from_db
1122
-     * @throws ReflectionException
1123
-     * @throws InvalidArgumentException
1124
-     * @throws InvalidInterfaceException
1125
-     * @throws InvalidDataTypeException
1126
-     * @throws EE_Error
1127
-     */
1128
-    public function set_from_db($field_name, $field_value_from_db)
1129
-    {
1130
-        $field_obj = $this->get_model()->field_settings_for($field_name);
1131
-        if ($field_obj instanceof EE_Model_Field_Base) {
1132
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
1133
-            // eg, a CPT model object could have an entry in the posts table, but no
1134
-            // entry in the meta table. Meaning that all its columns in the meta table
1135
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
1136
-            if ($field_value_from_db === null) {
1137
-                if ($field_obj->is_nullable()) {
1138
-                    // if the field allows nulls, then let it be null
1139
-                    $field_value = null;
1140
-                } else {
1141
-                    $field_value = $field_obj->get_default_value();
1142
-                }
1143
-            } else {
1144
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1145
-            }
1146
-            $this->_fields[ $field_name ] = $field_value;
1147
-            $this->_clear_cached_property($field_name);
1148
-        }
1149
-    }
1150
-
1151
-
1152
-    /**
1153
-     * verifies that the specified field is of the correct type
1154
-     *
1155
-     * @param string $field_name
1156
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1157
-     *                                (in cases where the same property may be used for different outputs
1158
-     *                                - i.e. datetime, money etc.)
1159
-     * @return mixed
1160
-     * @throws ReflectionException
1161
-     * @throws InvalidArgumentException
1162
-     * @throws InvalidInterfaceException
1163
-     * @throws InvalidDataTypeException
1164
-     * @throws EE_Error
1165
-     */
1166
-    public function get($field_name, $extra_cache_ref = null)
1167
-    {
1168
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1169
-    }
1170
-
1171
-
1172
-    /**
1173
-     * This method simply returns the RAW unprocessed value for the given property in this class
1174
-     *
1175
-     * @param  string $field_name A valid fieldname
1176
-     * @return mixed              Whatever the raw value stored on the property is.
1177
-     * @throws ReflectionException
1178
-     * @throws InvalidArgumentException
1179
-     * @throws InvalidInterfaceException
1180
-     * @throws InvalidDataTypeException
1181
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1182
-     */
1183
-    public function get_raw($field_name)
1184
-    {
1185
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1186
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1187
-            ? $this->_fields[ $field_name ]->format('U')
1188
-            : $this->_fields[ $field_name ];
1189
-    }
1190
-
1191
-
1192
-    /**
1193
-     * This is used to return the internal DateTime object used for a field that is a
1194
-     * EE_Datetime_Field.
1195
-     *
1196
-     * @param string $field_name               The field name retrieving the DateTime object.
1197
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1198
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1199
-     *                                         EE_Datetime_Field and but the field value is null, then
1200
-     *                                         just null is returned (because that indicates that likely
1201
-     *                                         this field is nullable).
1202
-     * @throws InvalidArgumentException
1203
-     * @throws InvalidDataTypeException
1204
-     * @throws InvalidInterfaceException
1205
-     * @throws ReflectionException
1206
-     */
1207
-    public function get_DateTime_object($field_name)
1208
-    {
1209
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1210
-        if (! $field_settings instanceof EE_Datetime_Field) {
1211
-            EE_Error::add_error(
1212
-                sprintf(
1213
-                    esc_html__(
1214
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1215
-                        'event_espresso'
1216
-                    ),
1217
-                    $field_name
1218
-                ),
1219
-                __FILE__,
1220
-                __FUNCTION__,
1221
-                __LINE__
1222
-            );
1223
-            return false;
1224
-        }
1225
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1226
-            ? clone $this->_fields[ $field_name ]
1227
-            : null;
1228
-    }
1229
-
1230
-
1231
-    /**
1232
-     * To be used in template to immediately echo out the value, and format it for output.
1233
-     * Eg, should call stripslashes and whatnot before echoing
1234
-     *
1235
-     * @param string $field_name      the name of the field as it appears in the DB
1236
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1237
-     *                                (in cases where the same property may be used for different outputs
1238
-     *                                - i.e. datetime, money etc.)
1239
-     * @return void
1240
-     * @throws ReflectionException
1241
-     * @throws InvalidArgumentException
1242
-     * @throws InvalidInterfaceException
1243
-     * @throws InvalidDataTypeException
1244
-     * @throws EE_Error
1245
-     */
1246
-    public function e($field_name, $extra_cache_ref = null)
1247
-    {
1248
-        echo $this->get_pretty($field_name, $extra_cache_ref);
1249
-    }
1250
-
1251
-
1252
-    /**
1253
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1254
-     * can be easily used as the value of form input.
1255
-     *
1256
-     * @param string $field_name
1257
-     * @return void
1258
-     * @throws ReflectionException
1259
-     * @throws InvalidArgumentException
1260
-     * @throws InvalidInterfaceException
1261
-     * @throws InvalidDataTypeException
1262
-     * @throws EE_Error
1263
-     */
1264
-    public function f($field_name)
1265
-    {
1266
-        $this->e($field_name, 'form_input');
1267
-    }
1268
-
1269
-
1270
-    /**
1271
-     * Same as `f()` but just returns the value instead of echoing it
1272
-     *
1273
-     * @param string $field_name
1274
-     * @return string
1275
-     * @throws ReflectionException
1276
-     * @throws InvalidArgumentException
1277
-     * @throws InvalidInterfaceException
1278
-     * @throws InvalidDataTypeException
1279
-     * @throws EE_Error
1280
-     */
1281
-    public function get_f($field_name)
1282
-    {
1283
-        return (string) $this->get_pretty($field_name, 'form_input');
1284
-    }
1285
-
1286
-
1287
-    /**
1288
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1289
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1290
-     * to see what options are available.
1291
-     *
1292
-     * @param string $field_name
1293
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1294
-     *                                (in cases where the same property may be used for different outputs
1295
-     *                                - i.e. datetime, money etc.)
1296
-     * @return mixed
1297
-     * @throws ReflectionException
1298
-     * @throws InvalidArgumentException
1299
-     * @throws InvalidInterfaceException
1300
-     * @throws InvalidDataTypeException
1301
-     * @throws EE_Error
1302
-     */
1303
-    public function get_pretty($field_name, $extra_cache_ref = null)
1304
-    {
1305
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1306
-    }
1307
-
1308
-
1309
-    /**
1310
-     * This simply returns the datetime for the given field name
1311
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1312
-     * (and the equivalent e_date, e_time, e_datetime).
1313
-     *
1314
-     * @access   protected
1315
-     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1316
-     * @param string   $dt_frmt      valid datetime format used for date
1317
-     *                               (if '' then we just use the default on the field,
1318
-     *                               if NULL we use the last-used format)
1319
-     * @param string   $tm_frmt      Same as above except this is for time format
1320
-     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1321
-     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1322
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1323
-     *                               if field is not a valid dtt field, or void if echoing
1324
-     * @throws ReflectionException
1325
-     * @throws InvalidArgumentException
1326
-     * @throws InvalidInterfaceException
1327
-     * @throws InvalidDataTypeException
1328
-     * @throws EE_Error
1329
-     */
1330
-    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1331
-    {
1332
-        // clear cached property
1333
-        $this->_clear_cached_property($field_name);
1334
-        // reset format properties because they are used in get()
1335
-        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1336
-        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1337
-        if ($echo) {
1338
-            $this->e($field_name, $date_or_time);
1339
-            return '';
1340
-        }
1341
-        return $this->get($field_name, $date_or_time);
1342
-    }
1343
-
1344
-
1345
-    /**
1346
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1347
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1348
-     * other echoes the pretty value for dtt)
1349
-     *
1350
-     * @param  string $field_name name of model object datetime field holding the value
1351
-     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1352
-     * @return string            datetime value formatted
1353
-     * @throws ReflectionException
1354
-     * @throws InvalidArgumentException
1355
-     * @throws InvalidInterfaceException
1356
-     * @throws InvalidDataTypeException
1357
-     * @throws EE_Error
1358
-     */
1359
-    public function get_date($field_name, $format = '')
1360
-    {
1361
-        return $this->_get_datetime($field_name, $format, null, 'D');
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * @param        $field_name
1367
-     * @param string $format
1368
-     * @throws ReflectionException
1369
-     * @throws InvalidArgumentException
1370
-     * @throws InvalidInterfaceException
1371
-     * @throws InvalidDataTypeException
1372
-     * @throws EE_Error
1373
-     */
1374
-    public function e_date($field_name, $format = '')
1375
-    {
1376
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1377
-    }
1378
-
1379
-
1380
-    /**
1381
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1382
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1383
-     * other echoes the pretty value for dtt)
1384
-     *
1385
-     * @param  string $field_name name of model object datetime field holding the value
1386
-     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1387
-     * @return string             datetime value formatted
1388
-     * @throws ReflectionException
1389
-     * @throws InvalidArgumentException
1390
-     * @throws InvalidInterfaceException
1391
-     * @throws InvalidDataTypeException
1392
-     * @throws EE_Error
1393
-     */
1394
-    public function get_time($field_name, $format = '')
1395
-    {
1396
-        return $this->_get_datetime($field_name, null, $format, 'T');
1397
-    }
1398
-
1399
-
1400
-    /**
1401
-     * @param        $field_name
1402
-     * @param string $format
1403
-     * @throws ReflectionException
1404
-     * @throws InvalidArgumentException
1405
-     * @throws InvalidInterfaceException
1406
-     * @throws InvalidDataTypeException
1407
-     * @throws EE_Error
1408
-     */
1409
-    public function e_time($field_name, $format = '')
1410
-    {
1411
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1412
-    }
1413
-
1414
-
1415
-    /**
1416
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1417
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1418
-     * other echoes the pretty value for dtt)
1419
-     *
1420
-     * @param  string $field_name name of model object datetime field holding the value
1421
-     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1422
-     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1423
-     * @return string             datetime value formatted
1424
-     * @throws ReflectionException
1425
-     * @throws InvalidArgumentException
1426
-     * @throws InvalidInterfaceException
1427
-     * @throws InvalidDataTypeException
1428
-     * @throws EE_Error
1429
-     */
1430
-    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1431
-    {
1432
-        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1433
-    }
1434
-
1435
-
1436
-    /**
1437
-     * @param string $field_name
1438
-     * @param string $dt_frmt
1439
-     * @param string $tm_frmt
1440
-     * @throws ReflectionException
1441
-     * @throws InvalidArgumentException
1442
-     * @throws InvalidInterfaceException
1443
-     * @throws InvalidDataTypeException
1444
-     * @throws EE_Error
1445
-     */
1446
-    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1447
-    {
1448
-        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1449
-    }
1450
-
1451
-
1452
-    /**
1453
-     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1454
-     *
1455
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1456
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1457
-     *                           on the object will be used.
1458
-     * @return string Date and time string in set locale or false if no field exists for the given
1459
-     * @throws ReflectionException
1460
-     * @throws InvalidArgumentException
1461
-     * @throws InvalidInterfaceException
1462
-     * @throws InvalidDataTypeException
1463
-     * @throws EE_Error
1464
-     *                           field name.
1465
-     */
1466
-    public function get_i18n_datetime($field_name, $format = '')
1467
-    {
1468
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1469
-        return date_i18n(
1470
-            $format,
1471
-            EEH_DTT_Helper::get_timestamp_with_offset(
1472
-                $this->get_raw($field_name),
1473
-                $this->_timezone
1474
-            )
1475
-        );
1476
-    }
1477
-
1478
-
1479
-    /**
1480
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1481
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1482
-     * thrown.
1483
-     *
1484
-     * @param  string $field_name The field name being checked
1485
-     * @throws ReflectionException
1486
-     * @throws InvalidArgumentException
1487
-     * @throws InvalidInterfaceException
1488
-     * @throws InvalidDataTypeException
1489
-     * @throws EE_Error
1490
-     * @return EE_Datetime_Field
1491
-     */
1492
-    protected function _get_dtt_field_settings($field_name)
1493
-    {
1494
-        $field = $this->get_model()->field_settings_for($field_name);
1495
-        // check if field is dtt
1496
-        if ($field instanceof EE_Datetime_Field) {
1497
-            return $field;
1498
-        }
1499
-        throw new EE_Error(
1500
-            sprintf(
1501
-                esc_html__(
1502
-                    'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1503
-                    'event_espresso'
1504
-                ),
1505
-                $field_name,
1506
-                self::_get_model_classname(get_class($this))
1507
-            )
1508
-        );
1509
-    }
1510
-
1511
-
1512
-
1513
-
1514
-    /**
1515
-     * NOTE ABOUT BELOW:
1516
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1517
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1518
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1519
-     * method and make sure you send the entire datetime value for setting.
1520
-     */
1521
-    /**
1522
-     * sets the time on a datetime property
1523
-     *
1524
-     * @access protected
1525
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1526
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1527
-     * @throws ReflectionException
1528
-     * @throws InvalidArgumentException
1529
-     * @throws InvalidInterfaceException
1530
-     * @throws InvalidDataTypeException
1531
-     * @throws EE_Error
1532
-     */
1533
-    protected function _set_time_for($time, $fieldname)
1534
-    {
1535
-        $this->_set_date_time('T', $time, $fieldname);
1536
-    }
1537
-
1538
-
1539
-    /**
1540
-     * sets the date on a datetime property
1541
-     *
1542
-     * @access protected
1543
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1544
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1545
-     * @throws ReflectionException
1546
-     * @throws InvalidArgumentException
1547
-     * @throws InvalidInterfaceException
1548
-     * @throws InvalidDataTypeException
1549
-     * @throws EE_Error
1550
-     */
1551
-    protected function _set_date_for($date, $fieldname)
1552
-    {
1553
-        $this->_set_date_time('D', $date, $fieldname);
1554
-    }
1555
-
1556
-
1557
-    /**
1558
-     * This takes care of setting a date or time independently on a given model object property. This method also
1559
-     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1560
-     *
1561
-     * @access protected
1562
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1563
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1564
-     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1565
-     *                                        EE_Datetime_Field property)
1566
-     * @throws ReflectionException
1567
-     * @throws InvalidArgumentException
1568
-     * @throws InvalidInterfaceException
1569
-     * @throws InvalidDataTypeException
1570
-     * @throws EE_Error
1571
-     */
1572
-    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1573
-    {
1574
-        $field = $this->_get_dtt_field_settings($fieldname);
1575
-        $field->set_timezone($this->_timezone);
1576
-        $field->set_date_format($this->_dt_frmt);
1577
-        $field->set_time_format($this->_tm_frmt);
1578
-        switch ($what) {
1579
-            case 'T':
1580
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1581
-                    $datetime_value,
1582
-                    $this->_fields[ $fieldname ]
1583
-                );
1584
-                break;
1585
-            case 'D':
1586
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1587
-                    $datetime_value,
1588
-                    $this->_fields[ $fieldname ]
1589
-                );
1590
-                break;
1591
-            case 'B':
1592
-                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1593
-                break;
1594
-        }
1595
-        $this->_clear_cached_property($fieldname);
1596
-    }
1597
-
1598
-
1599
-    /**
1600
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1601
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1602
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1603
-     * that could lead to some unexpected results!
1604
-     *
1605
-     * @access public
1606
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1607
-     *                                         value being returned.
1608
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1609
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1610
-     * @param string $prepend                  You can include something to prepend on the timestamp
1611
-     * @param string $append                   You can include something to append on the timestamp
1612
-     * @throws ReflectionException
1613
-     * @throws InvalidArgumentException
1614
-     * @throws InvalidInterfaceException
1615
-     * @throws InvalidDataTypeException
1616
-     * @throws EE_Error
1617
-     * @return string timestamp
1618
-     */
1619
-    public function display_in_my_timezone(
1620
-        $field_name,
1621
-        $callback = 'get_datetime',
1622
-        $args = null,
1623
-        $prepend = '',
1624
-        $append = ''
1625
-    ) {
1626
-        $timezone = EEH_DTT_Helper::get_timezone();
1627
-        if ($timezone === $this->_timezone) {
1628
-            return '';
1629
-        }
1630
-        $original_timezone = $this->_timezone;
1631
-        $this->set_timezone($timezone);
1632
-        $fn = (array) $field_name;
1633
-        $args = array_merge($fn, (array) $args);
1634
-        if (! method_exists($this, $callback)) {
1635
-            throw new EE_Error(
1636
-                sprintf(
1637
-                    esc_html__(
1638
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1639
-                        'event_espresso'
1640
-                    ),
1641
-                    $callback
1642
-                )
1643
-            );
1644
-        }
1645
-        $args = (array) $args;
1646
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1647
-        $this->set_timezone($original_timezone);
1648
-        return $return;
1649
-    }
1650
-
1651
-
1652
-    /**
1653
-     * Deletes this model object.
1654
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1655
-     * override
1656
-     * `EE_Base_Class::_delete` NOT this class.
1657
-     *
1658
-     * @return boolean | int
1659
-     * @throws ReflectionException
1660
-     * @throws InvalidArgumentException
1661
-     * @throws InvalidInterfaceException
1662
-     * @throws InvalidDataTypeException
1663
-     * @throws EE_Error
1664
-     */
1665
-    public function delete()
1666
-    {
1667
-        /**
1668
-         * Called just before the `EE_Base_Class::_delete` method call.
1669
-         * Note:
1670
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1671
-         * should be aware that `_delete` may not always result in a permanent delete.
1672
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1673
-         * soft deletes (trash) the object and does not permanently delete it.
1674
-         *
1675
-         * @param EE_Base_Class $model_object about to be 'deleted'
1676
-         */
1677
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1678
-        $result = $this->_delete();
1679
-        /**
1680
-         * Called just after the `EE_Base_Class::_delete` method call.
1681
-         * Note:
1682
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1683
-         * should be aware that `_delete` may not always result in a permanent delete.
1684
-         * For example `EE_Soft_Base_Class::_delete`
1685
-         * soft deletes (trash) the object and does not permanently delete it.
1686
-         *
1687
-         * @param EE_Base_Class $model_object that was just 'deleted'
1688
-         * @param boolean       $result
1689
-         */
1690
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1691
-        return $result;
1692
-    }
1693
-
1694
-
1695
-    /**
1696
-     * Calls the specific delete method for the instantiated class.
1697
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1698
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1699
-     * `EE_Base_Class::delete`
1700
-     *
1701
-     * @return bool|int
1702
-     * @throws ReflectionException
1703
-     * @throws InvalidArgumentException
1704
-     * @throws InvalidInterfaceException
1705
-     * @throws InvalidDataTypeException
1706
-     * @throws EE_Error
1707
-     */
1708
-    protected function _delete()
1709
-    {
1710
-        return $this->delete_permanently();
1711
-    }
1712
-
1713
-
1714
-    /**
1715
-     * Deletes this model object permanently from db
1716
-     * (but keep in mind related models may block the delete and return an error)
1717
-     *
1718
-     * @return bool | int
1719
-     * @throws ReflectionException
1720
-     * @throws InvalidArgumentException
1721
-     * @throws InvalidInterfaceException
1722
-     * @throws InvalidDataTypeException
1723
-     * @throws EE_Error
1724
-     */
1725
-    public function delete_permanently()
1726
-    {
1727
-        /**
1728
-         * Called just before HARD deleting a model object
1729
-         *
1730
-         * @param EE_Base_Class $model_object about to be 'deleted'
1731
-         */
1732
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1733
-        $model = $this->get_model();
1734
-        $result = $model->delete_permanently_by_ID($this->ID());
1735
-        $this->refresh_cache_of_related_objects();
1736
-        /**
1737
-         * Called just after HARD deleting a model object
1738
-         *
1739
-         * @param EE_Base_Class $model_object that was just 'deleted'
1740
-         * @param boolean       $result
1741
-         */
1742
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1743
-        return $result;
1744
-    }
1745
-
1746
-
1747
-    /**
1748
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1749
-     * related model objects
1750
-     *
1751
-     * @throws ReflectionException
1752
-     * @throws InvalidArgumentException
1753
-     * @throws InvalidInterfaceException
1754
-     * @throws InvalidDataTypeException
1755
-     * @throws EE_Error
1756
-     */
1757
-    public function refresh_cache_of_related_objects()
1758
-    {
1759
-        $model = $this->get_model();
1760
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1761
-            if (! empty($this->_model_relations[ $relation_name ])) {
1762
-                $related_objects = $this->_model_relations[ $relation_name ];
1763
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1764
-                    // this relation only stores a single model object, not an array
1765
-                    // but let's make it consistent
1766
-                    $related_objects = array($related_objects);
1767
-                }
1768
-                foreach ($related_objects as $related_object) {
1769
-                    // only refresh their cache if they're in memory
1770
-                    if ($related_object instanceof EE_Base_Class) {
1771
-                        $related_object->clear_cache(
1772
-                            $model->get_this_model_name(),
1773
-                            $this
1774
-                        );
1775
-                    }
1776
-                }
1777
-            }
1778
-        }
1779
-    }
1780
-
1781
-
1782
-    /**
1783
-     *        Saves this object to the database. An array may be supplied to set some values on this
1784
-     * object just before saving.
1785
-     *
1786
-     * @access public
1787
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1788
-     *                                 if provided during the save() method (often client code will change the fields'
1789
-     *                                 values before calling save)
1790
-     * @throws InvalidArgumentException
1791
-     * @throws InvalidInterfaceException
1792
-     * @throws InvalidDataTypeException
1793
-     * @throws EE_Error
1794
-     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1795
-     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1796
-     * @throws ReflectionException
1797
-     * @throws ReflectionException
1798
-     * @throws ReflectionException
1799
-     */
1800
-    public function save($set_cols_n_values = array())
1801
-    {
1802
-        $model = $this->get_model();
1803
-        /**
1804
-         * Filters the fields we're about to save on the model object
1805
-         *
1806
-         * @param array         $set_cols_n_values
1807
-         * @param EE_Base_Class $model_object
1808
-         */
1809
-        $set_cols_n_values = (array) apply_filters(
1810
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1811
-            $set_cols_n_values,
1812
-            $this
1813
-        );
1814
-        // set attributes as provided in $set_cols_n_values
1815
-        foreach ($set_cols_n_values as $column => $value) {
1816
-            $this->set($column, $value);
1817
-        }
1818
-        // no changes ? then don't do anything
1819
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1820
-            return 0;
1821
-        }
1822
-        /**
1823
-         * Saving a model object.
1824
-         * Before we perform a save, this action is fired.
1825
-         *
1826
-         * @param EE_Base_Class $model_object the model object about to be saved.
1827
-         */
1828
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1829
-        if (! $this->allow_persist()) {
1830
-            return 0;
1831
-        }
1832
-        // now get current attribute values
1833
-        $save_cols_n_values = $this->_fields;
1834
-        // if the object already has an ID, update it. Otherwise, insert it
1835
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1836
-        // They have been
1837
-        $old_assumption_concerning_value_preparation = $model
1838
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1839
-        $model->assume_values_already_prepared_by_model_object(true);
1840
-        // does this model have an autoincrement PK?
1841
-        if ($model->has_primary_key_field()) {
1842
-            if ($model->get_primary_key_field()->is_auto_increment()) {
1843
-                // ok check if it's set, if so: update; if not, insert
1844
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1845
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1846
-                } else {
1847
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1848
-                    $results = $model->insert($save_cols_n_values);
1849
-                    if ($results) {
1850
-                        // if successful, set the primary key
1851
-                        // but don't use the normal SET method, because it will check if
1852
-                        // an item with the same ID exists in the mapper & db, then
1853
-                        // will find it in the db (because we just added it) and THAT object
1854
-                        // will get added to the mapper before we can add this one!
1855
-                        // but if we just avoid using the SET method, all that headache can be avoided
1856
-                        $pk_field_name = $model->primary_key_name();
1857
-                        $this->_fields[ $pk_field_name ] = $results;
1858
-                        $this->_clear_cached_property($pk_field_name);
1859
-                        $model->add_to_entity_map($this);
1860
-                        $this->_update_cached_related_model_objs_fks();
1861
-                    }
1862
-                }
1863
-            } else {// PK is NOT auto-increment
1864
-                // so check if one like it already exists in the db
1865
-                if ($model->exists_by_ID($this->ID())) {
1866
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1867
-                        throw new EE_Error(
1868
-                            sprintf(
1869
-                                esc_html__(
1870
-                                    'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1871
-                                    'event_espresso'
1872
-                                ),
1873
-                                get_class($this),
1874
-                                get_class($model) . '::instance()->add_to_entity_map()',
1875
-                                get_class($model) . '::instance()->get_one_by_ID()',
1876
-                                '<br />'
1877
-                            )
1878
-                        );
1879
-                    }
1880
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1881
-                } else {
1882
-                    $results = $model->insert($save_cols_n_values);
1883
-                    $this->_update_cached_related_model_objs_fks();
1884
-                }
1885
-            }
1886
-        } else {// there is NO primary key
1887
-            $already_in_db = false;
1888
-            foreach ($model->unique_indexes() as $index) {
1889
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1890
-                if ($model->exists(array($uniqueness_where_params))) {
1891
-                    $already_in_db = true;
1892
-                }
1893
-            }
1894
-            if ($already_in_db) {
1895
-                $combined_pk_fields_n_values = array_intersect_key(
1896
-                    $save_cols_n_values,
1897
-                    $model->get_combined_primary_key_fields()
1898
-                );
1899
-                $results = $model->update(
1900
-                    $save_cols_n_values,
1901
-                    $combined_pk_fields_n_values
1902
-                );
1903
-            } else {
1904
-                $results = $model->insert($save_cols_n_values);
1905
-            }
1906
-        }
1907
-        // restore the old assumption about values being prepared by the model object
1908
-        $model->assume_values_already_prepared_by_model_object(
1909
-            $old_assumption_concerning_value_preparation
1910
-        );
1911
-        /**
1912
-         * After saving the model object this action is called
1913
-         *
1914
-         * @param EE_Base_Class $model_object which was just saved
1915
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1916
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1917
-         */
1918
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1919
-        $this->_has_changes = false;
1920
-        return $results;
1921
-    }
1922
-
1923
-
1924
-    /**
1925
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1926
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1927
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1928
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1929
-     * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1930
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1931
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1932
-     *
1933
-     * @return void
1934
-     * @throws ReflectionException
1935
-     * @throws InvalidArgumentException
1936
-     * @throws InvalidInterfaceException
1937
-     * @throws InvalidDataTypeException
1938
-     * @throws EE_Error
1939
-     */
1940
-    protected function _update_cached_related_model_objs_fks()
1941
-    {
1942
-        $model = $this->get_model();
1943
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1944
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1945
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1946
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1947
-                        $model->get_this_model_name()
1948
-                    );
1949
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1950
-                    if ($related_model_obj_in_cache->ID()) {
1951
-                        $related_model_obj_in_cache->save();
1952
-                    }
1953
-                }
1954
-            }
1955
-        }
1956
-    }
1957
-
1958
-
1959
-    /**
1960
-     * Saves this model object and its NEW cached relations to the database.
1961
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1962
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1963
-     * because otherwise, there's a potential for infinite looping of saving
1964
-     * Saves the cached related model objects, and ensures the relation between them
1965
-     * and this object and properly setup
1966
-     *
1967
-     * @return int ID of new model object on save; 0 on failure+
1968
-     * @throws ReflectionException
1969
-     * @throws InvalidArgumentException
1970
-     * @throws InvalidInterfaceException
1971
-     * @throws InvalidDataTypeException
1972
-     * @throws EE_Error
1973
-     */
1974
-    public function save_new_cached_related_model_objs()
1975
-    {
1976
-        // make sure this has been saved
1977
-        if (! $this->ID()) {
1978
-            $id = $this->save();
1979
-        } else {
1980
-            $id = $this->ID();
1981
-        }
1982
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
1983
-        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1984
-            if ($this->_model_relations[ $relationName ]) {
1985
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1986
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1987
-                /* @var $related_model_obj EE_Base_Class */
1988
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
1989
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
1990
-                    // but ONLY if it DOES NOT exist in the DB
1991
-                    $related_model_obj = $this->_model_relations[ $relationName ];
1992
-                    // if( ! $related_model_obj->ID()){
1993
-                    $this->_add_relation_to($related_model_obj, $relationName);
1994
-                    $related_model_obj->save_new_cached_related_model_objs();
1995
-                    // }
1996
-                } else {
1997
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
1998
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
1999
-                        // but ONLY if it DOES NOT exist in the DB
2000
-                        // if( ! $related_model_obj->ID()){
2001
-                        $this->_add_relation_to($related_model_obj, $relationName);
2002
-                        $related_model_obj->save_new_cached_related_model_objs();
2003
-                        // }
2004
-                    }
2005
-                }
2006
-            }
2007
-        }
2008
-        return $id;
2009
-    }
2010
-
2011
-
2012
-    /**
2013
-     * for getting a model while instantiated.
2014
-     *
2015
-     * @return EEM_Base | EEM_CPT_Base
2016
-     * @throws ReflectionException
2017
-     * @throws InvalidArgumentException
2018
-     * @throws InvalidInterfaceException
2019
-     * @throws InvalidDataTypeException
2020
-     * @throws EE_Error
2021
-     */
2022
-    public function get_model()
2023
-    {
2024
-        if (! $this->_model) {
2025
-            $modelName = self::_get_model_classname(get_class($this));
2026
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2027
-        } else {
2028
-            $this->_model->set_timezone($this->_timezone);
2029
-        }
2030
-        return $this->_model;
2031
-    }
2032
-
2033
-
2034
-    /**
2035
-     * @param $props_n_values
2036
-     * @param $classname
2037
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2038
-     * @throws ReflectionException
2039
-     * @throws InvalidArgumentException
2040
-     * @throws InvalidInterfaceException
2041
-     * @throws InvalidDataTypeException
2042
-     * @throws EE_Error
2043
-     */
2044
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2045
-    {
2046
-        // TODO: will not work for Term_Relationships because they have no PK!
2047
-        $primary_id_ref = self::_get_primary_key_name($classname);
2048
-        if (array_key_exists($primary_id_ref, $props_n_values)
2049
-            && ! empty($props_n_values[ $primary_id_ref ])
2050
-        ) {
2051
-            $id = $props_n_values[ $primary_id_ref ];
2052
-            return self::_get_model($classname)->get_from_entity_map($id);
2053
-        }
2054
-        return false;
2055
-    }
2056
-
2057
-
2058
-    /**
2059
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2060
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2061
-     * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2062
-     * we return false.
2063
-     *
2064
-     * @param  array  $props_n_values   incoming array of properties and their values
2065
-     * @param  string $classname        the classname of the child class
2066
-     * @param null    $timezone
2067
-     * @param array   $date_formats     incoming date_formats in an array where the first value is the
2068
-     *                                  date_format and the second value is the time format
2069
-     * @return mixed (EE_Base_Class|bool)
2070
-     * @throws InvalidArgumentException
2071
-     * @throws InvalidInterfaceException
2072
-     * @throws InvalidDataTypeException
2073
-     * @throws EE_Error
2074
-     * @throws ReflectionException
2075
-     * @throws ReflectionException
2076
-     * @throws ReflectionException
2077
-     */
2078
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2079
-    {
2080
-        $existing = null;
2081
-        $model = self::_get_model($classname, $timezone);
2082
-        if ($model->has_primary_key_field()) {
2083
-            $primary_id_ref = self::_get_primary_key_name($classname);
2084
-            if (array_key_exists($primary_id_ref, $props_n_values)
2085
-                && ! empty($props_n_values[ $primary_id_ref ])
2086
-            ) {
2087
-                $existing = $model->get_one_by_ID(
2088
-                    $props_n_values[ $primary_id_ref ]
2089
-                );
2090
-            }
2091
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2092
-            // no primary key on this model, but there's still a matching item in the DB
2093
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2094
-                self::_get_model($classname, $timezone)
2095
-                    ->get_index_primary_key_string($props_n_values)
2096
-            );
2097
-        }
2098
-        if ($existing) {
2099
-            // set date formats if present before setting values
2100
-            if (! empty($date_formats) && is_array($date_formats)) {
2101
-                $existing->set_date_format($date_formats[0]);
2102
-                $existing->set_time_format($date_formats[1]);
2103
-            } else {
2104
-                // set default formats for date and time
2105
-                $existing->set_date_format(get_option('date_format'));
2106
-                $existing->set_time_format(get_option('time_format'));
2107
-            }
2108
-            foreach ($props_n_values as $property => $field_value) {
2109
-                $existing->set($property, $field_value);
2110
-            }
2111
-            return $existing;
2112
-        }
2113
-        return false;
2114
-    }
2115
-
2116
-
2117
-    /**
2118
-     * Gets the EEM_*_Model for this class
2119
-     *
2120
-     * @access public now, as this is more convenient
2121
-     * @param      $classname
2122
-     * @param null $timezone
2123
-     * @throws ReflectionException
2124
-     * @throws InvalidArgumentException
2125
-     * @throws InvalidInterfaceException
2126
-     * @throws InvalidDataTypeException
2127
-     * @throws EE_Error
2128
-     * @return EEM_Base
2129
-     */
2130
-    protected static function _get_model($classname, $timezone = null)
2131
-    {
2132
-        // find model for this class
2133
-        if (! $classname) {
2134
-            throw new EE_Error(
2135
-                sprintf(
2136
-                    esc_html__(
2137
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2138
-                        'event_espresso'
2139
-                    ),
2140
-                    $classname
2141
-                )
2142
-            );
2143
-        }
2144
-        $modelName = self::_get_model_classname($classname);
2145
-        return self::_get_model_instance_with_name($modelName, $timezone);
2146
-    }
2147
-
2148
-
2149
-    /**
2150
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2151
-     *
2152
-     * @param string $model_classname
2153
-     * @param null   $timezone
2154
-     * @return EEM_Base
2155
-     * @throws ReflectionException
2156
-     * @throws InvalidArgumentException
2157
-     * @throws InvalidInterfaceException
2158
-     * @throws InvalidDataTypeException
2159
-     * @throws EE_Error
2160
-     */
2161
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2162
-    {
2163
-        $model_classname = str_replace('EEM_', '', $model_classname);
2164
-        $model = EE_Registry::instance()->load_model($model_classname);
2165
-        $model->set_timezone($timezone);
2166
-        return $model;
2167
-    }
2168
-
2169
-
2170
-    /**
2171
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2172
-     * Also works if a model class's classname is provided (eg EE_Registration).
2173
-     *
2174
-     * @param null $model_name
2175
-     * @return string like EEM_Attendee
2176
-     */
2177
-    private static function _get_model_classname($model_name = null)
2178
-    {
2179
-        if (strpos($model_name, 'EE_') === 0) {
2180
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2181
-        } else {
2182
-            $model_classname = 'EEM_' . $model_name;
2183
-        }
2184
-        return $model_classname;
2185
-    }
2186
-
2187
-
2188
-    /**
2189
-     * returns the name of the primary key attribute
2190
-     *
2191
-     * @param null $classname
2192
-     * @throws ReflectionException
2193
-     * @throws InvalidArgumentException
2194
-     * @throws InvalidInterfaceException
2195
-     * @throws InvalidDataTypeException
2196
-     * @throws EE_Error
2197
-     * @return string
2198
-     */
2199
-    protected static function _get_primary_key_name($classname = null)
2200
-    {
2201
-        if (! $classname) {
2202
-            throw new EE_Error(
2203
-                sprintf(
2204
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2205
-                    $classname
2206
-                )
2207
-            );
2208
-        }
2209
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2210
-    }
2211
-
2212
-
2213
-    /**
2214
-     * Gets the value of the primary key.
2215
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2216
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2217
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2218
-     *
2219
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2220
-     * @throws ReflectionException
2221
-     * @throws InvalidArgumentException
2222
-     * @throws InvalidInterfaceException
2223
-     * @throws InvalidDataTypeException
2224
-     * @throws EE_Error
2225
-     */
2226
-    public function ID()
2227
-    {
2228
-        $model = $this->get_model();
2229
-        // now that we know the name of the variable, use a variable variable to get its value and return its
2230
-        if ($model->has_primary_key_field()) {
2231
-            return $this->_fields[ $model->primary_key_name() ];
2232
-        }
2233
-        return $model->get_index_primary_key_string($this->_fields);
2234
-    }
2235
-
2236
-
2237
-    /**
2238
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2239
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2240
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2241
-     *
2242
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2243
-     * @param string $relationName                     eg 'Events','Question',etc.
2244
-     *                                                 an attendee to a group, you also want to specify which role they
2245
-     *                                                 will have in that group. So you would use this parameter to
2246
-     *                                                 specify array('role-column-name'=>'role-id')
2247
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2248
-     *                                                 allow you to further constrict the relation to being added.
2249
-     *                                                 However, keep in mind that the columns (keys) given must match a
2250
-     *                                                 column on the JOIN table and currently only the HABTM models
2251
-     *                                                 accept these additional conditions.  Also remember that if an
2252
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2253
-     *                                                 NEW row is created in the join table.
2254
-     * @param null   $cache_id
2255
-     * @throws ReflectionException
2256
-     * @throws InvalidArgumentException
2257
-     * @throws InvalidInterfaceException
2258
-     * @throws InvalidDataTypeException
2259
-     * @throws EE_Error
2260
-     * @return EE_Base_Class the object the relation was added to
2261
-     */
2262
-    public function _add_relation_to(
2263
-        $otherObjectModelObjectOrID,
2264
-        $relationName,
2265
-        $extra_join_model_fields_n_values = array(),
2266
-        $cache_id = null
2267
-    ) {
2268
-        $model = $this->get_model();
2269
-        // if this thing exists in the DB, save the relation to the DB
2270
-        if ($this->ID()) {
2271
-            $otherObject = $model->add_relationship_to(
2272
-                $this,
2273
-                $otherObjectModelObjectOrID,
2274
-                $relationName,
2275
-                $extra_join_model_fields_n_values
2276
-            );
2277
-            // clear cache so future get_many_related and get_first_related() return new results.
2278
-            $this->clear_cache($relationName, $otherObject, true);
2279
-            if ($otherObject instanceof EE_Base_Class) {
2280
-                $otherObject->clear_cache($model->get_this_model_name(), $this);
2281
-            }
2282
-        } else {
2283
-            // this thing doesn't exist in the DB,  so just cache it
2284
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2285
-                throw new EE_Error(
2286
-                    sprintf(
2287
-                        esc_html__(
2288
-                            'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2289
-                            'event_espresso'
2290
-                        ),
2291
-                        $otherObjectModelObjectOrID,
2292
-                        get_class($this)
2293
-                    )
2294
-                );
2295
-            }
2296
-            $otherObject = $otherObjectModelObjectOrID;
2297
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2298
-        }
2299
-        if ($otherObject instanceof EE_Base_Class) {
2300
-            // fix the reciprocal relation too
2301
-            if ($otherObject->ID()) {
2302
-                // its saved so assumed relations exist in the DB, so we can just
2303
-                // clear the cache so future queries use the updated info in the DB
2304
-                $otherObject->clear_cache(
2305
-                    $model->get_this_model_name(),
2306
-                    null,
2307
-                    true
2308
-                );
2309
-            } else {
2310
-                // it's not saved, so it caches relations like this
2311
-                $otherObject->cache($model->get_this_model_name(), $this);
2312
-            }
2313
-        }
2314
-        return $otherObject;
2315
-    }
2316
-
2317
-
2318
-    /**
2319
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2320
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2321
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2322
-     * from the cache
2323
-     *
2324
-     * @param mixed  $otherObjectModelObjectOrID
2325
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2326
-     *                to the DB yet
2327
-     * @param string $relationName
2328
-     * @param array  $where_query
2329
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2330
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2331
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2332
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2333
-     *                created in the join table.
2334
-     * @return EE_Base_Class the relation was removed from
2335
-     * @throws ReflectionException
2336
-     * @throws InvalidArgumentException
2337
-     * @throws InvalidInterfaceException
2338
-     * @throws InvalidDataTypeException
2339
-     * @throws EE_Error
2340
-     */
2341
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2342
-    {
2343
-        if ($this->ID()) {
2344
-            // if this exists in the DB, save the relation change to the DB too
2345
-            $otherObject = $this->get_model()->remove_relationship_to(
2346
-                $this,
2347
-                $otherObjectModelObjectOrID,
2348
-                $relationName,
2349
-                $where_query
2350
-            );
2351
-            $this->clear_cache(
2352
-                $relationName,
2353
-                $otherObject
2354
-            );
2355
-        } else {
2356
-            // this doesn't exist in the DB, just remove it from the cache
2357
-            $otherObject = $this->clear_cache(
2358
-                $relationName,
2359
-                $otherObjectModelObjectOrID
2360
-            );
2361
-        }
2362
-        if ($otherObject instanceof EE_Base_Class) {
2363
-            $otherObject->clear_cache(
2364
-                $this->get_model()->get_this_model_name(),
2365
-                $this
2366
-            );
2367
-        }
2368
-        return $otherObject;
2369
-    }
2370
-
2371
-
2372
-    /**
2373
-     * Removes ALL the related things for the $relationName.
2374
-     *
2375
-     * @param string $relationName
2376
-     * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2377
-     * @return EE_Base_Class
2378
-     * @throws ReflectionException
2379
-     * @throws InvalidArgumentException
2380
-     * @throws InvalidInterfaceException
2381
-     * @throws InvalidDataTypeException
2382
-     * @throws EE_Error
2383
-     */
2384
-    public function _remove_relations($relationName, $where_query_params = array())
2385
-    {
2386
-        if ($this->ID()) {
2387
-            // if this exists in the DB, save the relation change to the DB too
2388
-            $otherObjects = $this->get_model()->remove_relations(
2389
-                $this,
2390
-                $relationName,
2391
-                $where_query_params
2392
-            );
2393
-            $this->clear_cache(
2394
-                $relationName,
2395
-                null,
2396
-                true
2397
-            );
2398
-        } else {
2399
-            // this doesn't exist in the DB, just remove it from the cache
2400
-            $otherObjects = $this->clear_cache(
2401
-                $relationName,
2402
-                null,
2403
-                true
2404
-            );
2405
-        }
2406
-        if (is_array($otherObjects)) {
2407
-            foreach ($otherObjects as $otherObject) {
2408
-                $otherObject->clear_cache(
2409
-                    $this->get_model()->get_this_model_name(),
2410
-                    $this
2411
-                );
2412
-            }
2413
-        }
2414
-        return $otherObjects;
2415
-    }
2416
-
2417
-
2418
-    /**
2419
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2420
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2421
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2422
-     * because we want to get even deleted items etc.
2423
-     *
2424
-     * @param string $relationName key in the model's _model_relations array
2425
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2426
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2427
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2428
-     *                             results if you want IDs
2429
-     * @throws ReflectionException
2430
-     * @throws InvalidArgumentException
2431
-     * @throws InvalidInterfaceException
2432
-     * @throws InvalidDataTypeException
2433
-     * @throws EE_Error
2434
-     */
2435
-    public function get_many_related($relationName, $query_params = array())
2436
-    {
2437
-        if ($this->ID()) {
2438
-            // this exists in the DB, so get the related things from either the cache or the DB
2439
-            // if there are query parameters, forget about caching the related model objects.
2440
-            if ($query_params) {
2441
-                $related_model_objects = $this->get_model()->get_all_related(
2442
-                    $this,
2443
-                    $relationName,
2444
-                    $query_params
2445
-                );
2446
-            } else {
2447
-                // did we already cache the result of this query?
2448
-                $cached_results = $this->get_all_from_cache($relationName);
2449
-                if (! $cached_results) {
2450
-                    $related_model_objects = $this->get_model()->get_all_related(
2451
-                        $this,
2452
-                        $relationName,
2453
-                        $query_params
2454
-                    );
2455
-                    // if no query parameters were passed, then we got all the related model objects
2456
-                    // for that relation. We can cache them then.
2457
-                    foreach ($related_model_objects as $related_model_object) {
2458
-                        $this->cache($relationName, $related_model_object);
2459
-                    }
2460
-                } else {
2461
-                    $related_model_objects = $cached_results;
2462
-                }
2463
-            }
2464
-        } else {
2465
-            // this doesn't exist in the DB, so just get the related things from the cache
2466
-            $related_model_objects = $this->get_all_from_cache($relationName);
2467
-        }
2468
-        return $related_model_objects;
2469
-    }
2470
-
2471
-
2472
-    /**
2473
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2474
-     * unless otherwise specified in the $query_params
2475
-     *
2476
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2477
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2478
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2479
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2480
-     *                               that by the setting $distinct to TRUE;
2481
-     * @return int
2482
-     * @throws ReflectionException
2483
-     * @throws InvalidArgumentException
2484
-     * @throws InvalidInterfaceException
2485
-     * @throws InvalidDataTypeException
2486
-     * @throws EE_Error
2487
-     */
2488
-    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2489
-    {
2490
-        return $this->get_model()->count_related(
2491
-            $this,
2492
-            $relation_name,
2493
-            $query_params,
2494
-            $field_to_count,
2495
-            $distinct
2496
-        );
2497
-    }
2498
-
2499
-
2500
-    /**
2501
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2502
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2503
-     *
2504
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2505
-     * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2506
-     * @param string $field_to_sum  name of field to count by.
2507
-     *                              By default, uses primary key
2508
-     *                              (which doesn't make much sense, so you should probably change it)
2509
-     * @return int
2510
-     * @throws ReflectionException
2511
-     * @throws InvalidArgumentException
2512
-     * @throws InvalidInterfaceException
2513
-     * @throws InvalidDataTypeException
2514
-     * @throws EE_Error
2515
-     */
2516
-    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2517
-    {
2518
-        return $this->get_model()->sum_related(
2519
-            $this,
2520
-            $relation_name,
2521
-            $query_params,
2522
-            $field_to_sum
2523
-        );
2524
-    }
2525
-
2526
-
2527
-    /**
2528
-     * Gets the first (ie, one) related model object of the specified type.
2529
-     *
2530
-     * @param string $relationName key in the model's _model_relations array
2531
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2532
-     * @return EE_Base_Class (not an array, a single object)
2533
-     * @throws ReflectionException
2534
-     * @throws InvalidArgumentException
2535
-     * @throws InvalidInterfaceException
2536
-     * @throws InvalidDataTypeException
2537
-     * @throws EE_Error
2538
-     */
2539
-    public function get_first_related($relationName, $query_params = array())
2540
-    {
2541
-        $model = $this->get_model();
2542
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2543
-            // if they've provided some query parameters, don't bother trying to cache the result
2544
-            // also make sure we're not caching the result of get_first_related
2545
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
2546
-            if ($query_params
2547
-                || ! $model->related_settings_for($relationName)
2548
-                     instanceof
2549
-                     EE_Belongs_To_Relation
2550
-            ) {
2551
-                $related_model_object = $model->get_first_related(
2552
-                    $this,
2553
-                    $relationName,
2554
-                    $query_params
2555
-                );
2556
-            } else {
2557
-                // first, check if we've already cached the result of this query
2558
-                $cached_result = $this->get_one_from_cache($relationName);
2559
-                if (! $cached_result) {
2560
-                    $related_model_object = $model->get_first_related(
2561
-                        $this,
2562
-                        $relationName,
2563
-                        $query_params
2564
-                    );
2565
-                    $this->cache($relationName, $related_model_object);
2566
-                } else {
2567
-                    $related_model_object = $cached_result;
2568
-                }
2569
-            }
2570
-        } else {
2571
-            $related_model_object = null;
2572
-            // this doesn't exist in the Db,
2573
-            // but maybe the relation is of type belongs to, and so the related thing might
2574
-            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2575
-                $related_model_object = $model->get_first_related(
2576
-                    $this,
2577
-                    $relationName,
2578
-                    $query_params
2579
-                );
2580
-            }
2581
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2582
-            // just get what's cached on this object
2583
-            if (! $related_model_object) {
2584
-                $related_model_object = $this->get_one_from_cache($relationName);
2585
-            }
2586
-        }
2587
-        return $related_model_object;
2588
-    }
2589
-
2590
-
2591
-    /**
2592
-     * Does a delete on all related objects of type $relationName and removes
2593
-     * the current model object's relation to them. If they can't be deleted (because
2594
-     * of blocking related model objects) does nothing. If the related model objects are
2595
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2596
-     * If this model object doesn't exist yet in the DB, just removes its related things
2597
-     *
2598
-     * @param string $relationName
2599
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2600
-     * @return int how many deleted
2601
-     * @throws ReflectionException
2602
-     * @throws InvalidArgumentException
2603
-     * @throws InvalidInterfaceException
2604
-     * @throws InvalidDataTypeException
2605
-     * @throws EE_Error
2606
-     */
2607
-    public function delete_related($relationName, $query_params = array())
2608
-    {
2609
-        if ($this->ID()) {
2610
-            $count = $this->get_model()->delete_related(
2611
-                $this,
2612
-                $relationName,
2613
-                $query_params
2614
-            );
2615
-        } else {
2616
-            $count = count($this->get_all_from_cache($relationName));
2617
-            $this->clear_cache($relationName, null, true);
2618
-        }
2619
-        return $count;
2620
-    }
2621
-
2622
-
2623
-    /**
2624
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2625
-     * the current model object's relation to them. If they can't be deleted (because
2626
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2627
-     * If the related thing isn't a soft-deletable model object, this function is identical
2628
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2629
-     *
2630
-     * @param string $relationName
2631
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2632
-     * @return int how many deleted (including those soft deleted)
2633
-     * @throws ReflectionException
2634
-     * @throws InvalidArgumentException
2635
-     * @throws InvalidInterfaceException
2636
-     * @throws InvalidDataTypeException
2637
-     * @throws EE_Error
2638
-     */
2639
-    public function delete_related_permanently($relationName, $query_params = array())
2640
-    {
2641
-        if ($this->ID()) {
2642
-            $count = $this->get_model()->delete_related_permanently(
2643
-                $this,
2644
-                $relationName,
2645
-                $query_params
2646
-            );
2647
-        } else {
2648
-            $count = count($this->get_all_from_cache($relationName));
2649
-        }
2650
-        $this->clear_cache($relationName, null, true);
2651
-        return $count;
2652
-    }
2653
-
2654
-
2655
-    /**
2656
-     * is_set
2657
-     * Just a simple utility function children can use for checking if property exists
2658
-     *
2659
-     * @access  public
2660
-     * @param  string $field_name property to check
2661
-     * @return bool                              TRUE if existing,FALSE if not.
2662
-     */
2663
-    public function is_set($field_name)
2664
-    {
2665
-        return isset($this->_fields[ $field_name ]);
2666
-    }
2667
-
2668
-
2669
-    /**
2670
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2671
-     * EE_Error exception if they don't
2672
-     *
2673
-     * @param  mixed (string|array) $properties properties to check
2674
-     * @throws EE_Error
2675
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2676
-     */
2677
-    protected function _property_exists($properties)
2678
-    {
2679
-        foreach ((array) $properties as $property_name) {
2680
-            // first make sure this property exists
2681
-            if (! $this->_fields[ $property_name ]) {
2682
-                throw new EE_Error(
2683
-                    sprintf(
2684
-                        esc_html__(
2685
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2686
-                            'event_espresso'
2687
-                        ),
2688
-                        $property_name
2689
-                    )
2690
-                );
2691
-            }
2692
-        }
2693
-        return true;
2694
-    }
2695
-
2696
-
2697
-    /**
2698
-     * This simply returns an array of model fields for this object
2699
-     *
2700
-     * @return array
2701
-     * @throws ReflectionException
2702
-     * @throws InvalidArgumentException
2703
-     * @throws InvalidInterfaceException
2704
-     * @throws InvalidDataTypeException
2705
-     * @throws EE_Error
2706
-     */
2707
-    public function model_field_array()
2708
-    {
2709
-        $fields = $this->get_model()->field_settings(false);
2710
-        $properties = array();
2711
-        // remove prepended underscore
2712
-        foreach ($fields as $field_name => $settings) {
2713
-            $properties[ $field_name ] = $this->get($field_name);
2714
-        }
2715
-        return $properties;
2716
-    }
2717
-
2718
-
2719
-    /**
2720
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2721
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2722
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2723
-     * Instead of requiring a plugin to extend the EE_Base_Class
2724
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2725
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2726
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2727
-     * and accepts 2 arguments: the object on which the function was called,
2728
-     * and an array of the original arguments passed to the function.
2729
-     * Whatever their callback function returns will be returned by this function.
2730
-     * Example: in functions.php (or in a plugin):
2731
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2732
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2733
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2734
-     *          return $previousReturnValue.$returnString;
2735
-     *      }
2736
-     * require('EE_Answer.class.php');
2737
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2738
-     * echo $answer->my_callback('monkeys',100);
2739
-     * //will output "you called my_callback! and passed args:monkeys,100"
2740
-     *
2741
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2742
-     * @param array  $args       array of original arguments passed to the function
2743
-     * @throws EE_Error
2744
-     * @return mixed whatever the plugin which calls add_filter decides
2745
-     */
2746
-    public function __call($methodName, $args)
2747
-    {
2748
-        $className = get_class($this);
2749
-        $tagName = "FHEE__{$className}__{$methodName}";
2750
-        if (! has_filter($tagName)) {
2751
-            throw new EE_Error(
2752
-                sprintf(
2753
-                    esc_html__(
2754
-                        "Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2755
-                        'event_espresso'
2756
-                    ),
2757
-                    $methodName,
2758
-                    $className,
2759
-                    $tagName
2760
-                )
2761
-            );
2762
-        }
2763
-        return apply_filters($tagName, null, $this, $args);
2764
-    }
2765
-
2766
-
2767
-    /**
2768
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2769
-     * A $previous_value can be specified in case there are many meta rows with the same key
2770
-     *
2771
-     * @param string $meta_key
2772
-     * @param mixed  $meta_value
2773
-     * @param mixed  $previous_value
2774
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2775
-     *                  NOTE: if the values haven't changed, returns 0
2776
-     * @throws InvalidArgumentException
2777
-     * @throws InvalidInterfaceException
2778
-     * @throws InvalidDataTypeException
2779
-     * @throws EE_Error
2780
-     * @throws ReflectionException
2781
-     */
2782
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2783
-    {
2784
-        $query_params = array(
2785
-            array(
2786
-                'EXM_key'  => $meta_key,
2787
-                'OBJ_ID'   => $this->ID(),
2788
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2789
-            ),
2790
-        );
2791
-        if ($previous_value !== null) {
2792
-            $query_params[0]['EXM_value'] = $meta_value;
2793
-        }
2794
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2795
-        if (! $existing_rows_like_that) {
2796
-            return $this->add_extra_meta($meta_key, $meta_value);
2797
-        }
2798
-        foreach ($existing_rows_like_that as $existing_row) {
2799
-            $existing_row->save(array('EXM_value' => $meta_value));
2800
-        }
2801
-        return count($existing_rows_like_that);
2802
-    }
2803
-
2804
-
2805
-    /**
2806
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2807
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2808
-     * extra meta row was entered, false if not
2809
-     *
2810
-     * @param string  $meta_key
2811
-     * @param mixed   $meta_value
2812
-     * @param boolean $unique
2813
-     * @return boolean
2814
-     * @throws InvalidArgumentException
2815
-     * @throws InvalidInterfaceException
2816
-     * @throws InvalidDataTypeException
2817
-     * @throws EE_Error
2818
-     * @throws ReflectionException
2819
-     * @throws ReflectionException
2820
-     */
2821
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2822
-    {
2823
-        if ($unique) {
2824
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2825
-                array(
2826
-                    array(
2827
-                        'EXM_key'  => $meta_key,
2828
-                        'OBJ_ID'   => $this->ID(),
2829
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2830
-                    ),
2831
-                )
2832
-            );
2833
-            if ($existing_extra_meta) {
2834
-                return false;
2835
-            }
2836
-        }
2837
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2838
-            array(
2839
-                'EXM_key'   => $meta_key,
2840
-                'EXM_value' => $meta_value,
2841
-                'OBJ_ID'    => $this->ID(),
2842
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2843
-            )
2844
-        );
2845
-        $new_extra_meta->save();
2846
-        return true;
2847
-    }
2848
-
2849
-
2850
-    /**
2851
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2852
-     * is specified, only deletes extra meta records with that value.
2853
-     *
2854
-     * @param string $meta_key
2855
-     * @param mixed  $meta_value
2856
-     * @return int number of extra meta rows deleted
2857
-     * @throws InvalidArgumentException
2858
-     * @throws InvalidInterfaceException
2859
-     * @throws InvalidDataTypeException
2860
-     * @throws EE_Error
2861
-     * @throws ReflectionException
2862
-     */
2863
-    public function delete_extra_meta($meta_key, $meta_value = null)
2864
-    {
2865
-        $query_params = array(
2866
-            array(
2867
-                'EXM_key'  => $meta_key,
2868
-                'OBJ_ID'   => $this->ID(),
2869
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2870
-            ),
2871
-        );
2872
-        if ($meta_value !== null) {
2873
-            $query_params[0]['EXM_value'] = $meta_value;
2874
-        }
2875
-        return EEM_Extra_Meta::instance()->delete($query_params);
2876
-    }
2877
-
2878
-
2879
-    /**
2880
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2881
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2882
-     * You can specify $default is case you haven't found the extra meta
2883
-     *
2884
-     * @param string  $meta_key
2885
-     * @param boolean $single
2886
-     * @param mixed   $default if we don't find anything, what should we return?
2887
-     * @return mixed single value if $single; array if ! $single
2888
-     * @throws ReflectionException
2889
-     * @throws InvalidArgumentException
2890
-     * @throws InvalidInterfaceException
2891
-     * @throws InvalidDataTypeException
2892
-     * @throws EE_Error
2893
-     */
2894
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2895
-    {
2896
-        if ($single) {
2897
-            $result = $this->get_first_related(
2898
-                'Extra_Meta',
2899
-                array(array('EXM_key' => $meta_key))
2900
-            );
2901
-            if ($result instanceof EE_Extra_Meta) {
2902
-                return $result->value();
2903
-            }
2904
-        } else {
2905
-            $results = $this->get_many_related(
2906
-                'Extra_Meta',
2907
-                array(array('EXM_key' => $meta_key))
2908
-            );
2909
-            if ($results) {
2910
-                $values = array();
2911
-                foreach ($results as $result) {
2912
-                    if ($result instanceof EE_Extra_Meta) {
2913
-                        $values[ $result->ID() ] = $result->value();
2914
-                    }
2915
-                }
2916
-                return $values;
2917
-            }
2918
-        }
2919
-        // if nothing discovered yet return default.
2920
-        return apply_filters(
2921
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2922
-            $default,
2923
-            $meta_key,
2924
-            $single,
2925
-            $this
2926
-        );
2927
-    }
2928
-
2929
-
2930
-    /**
2931
-     * Returns a simple array of all the extra meta associated with this model object.
2932
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2933
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2934
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2935
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2936
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2937
-     * finally the extra meta's value as each sub-value. (eg
2938
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2939
-     *
2940
-     * @param boolean $one_of_each_key
2941
-     * @return array
2942
-     * @throws ReflectionException
2943
-     * @throws InvalidArgumentException
2944
-     * @throws InvalidInterfaceException
2945
-     * @throws InvalidDataTypeException
2946
-     * @throws EE_Error
2947
-     */
2948
-    public function all_extra_meta_array($one_of_each_key = true)
2949
-    {
2950
-        $return_array = array();
2951
-        if ($one_of_each_key) {
2952
-            $extra_meta_objs = $this->get_many_related(
2953
-                'Extra_Meta',
2954
-                array('group_by' => 'EXM_key')
2955
-            );
2956
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2957
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2958
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2959
-                }
2960
-            }
2961
-        } else {
2962
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2963
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2964
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2965
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2966
-                        $return_array[ $extra_meta_obj->key() ] = array();
2967
-                    }
2968
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2969
-                }
2970
-            }
2971
-        }
2972
-        return $return_array;
2973
-    }
2974
-
2975
-
2976
-    /**
2977
-     * Gets a pretty nice displayable nice for this model object. Often overridden
2978
-     *
2979
-     * @return string
2980
-     * @throws ReflectionException
2981
-     * @throws InvalidArgumentException
2982
-     * @throws InvalidInterfaceException
2983
-     * @throws InvalidDataTypeException
2984
-     * @throws EE_Error
2985
-     */
2986
-    public function name()
2987
-    {
2988
-        // find a field that's not a text field
2989
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2990
-        if ($field_we_can_use) {
2991
-            return $this->get($field_we_can_use->get_name());
2992
-        }
2993
-        $first_few_properties = $this->model_field_array();
2994
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
2995
-        $name_parts = array();
2996
-        foreach ($first_few_properties as $name => $value) {
2997
-            $name_parts[] = "$name:$value";
2998
-        }
2999
-        return implode(',', $name_parts);
3000
-    }
3001
-
3002
-
3003
-    /**
3004
-     * in_entity_map
3005
-     * Checks if this model object has been proven to already be in the entity map
3006
-     *
3007
-     * @return boolean
3008
-     * @throws ReflectionException
3009
-     * @throws InvalidArgumentException
3010
-     * @throws InvalidInterfaceException
3011
-     * @throws InvalidDataTypeException
3012
-     * @throws EE_Error
3013
-     */
3014
-    public function in_entity_map()
3015
-    {
3016
-        // well, if we looked, did we find it in the entity map?
3017
-        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3018
-    }
3019
-
3020
-
3021
-    /**
3022
-     * refresh_from_db
3023
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3024
-     *
3025
-     * @throws ReflectionException
3026
-     * @throws InvalidArgumentException
3027
-     * @throws InvalidInterfaceException
3028
-     * @throws InvalidDataTypeException
3029
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3030
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3031
-     */
3032
-    public function refresh_from_db()
3033
-    {
3034
-        if ($this->ID() && $this->in_entity_map()) {
3035
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
3036
-        } else {
3037
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3038
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
3039
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3040
-            // absolutely nothing in it for this ID
3041
-            if (WP_DEBUG) {
3042
-                throw new EE_Error(
3043
-                    sprintf(
3044
-                        esc_html__(
3045
-                            'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3046
-                            'event_espresso'
3047
-                        ),
3048
-                        $this->ID(),
3049
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3050
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3051
-                    )
3052
-                );
3053
-            }
3054
-        }
3055
-    }
3056
-
3057
-
3058
-    /**
3059
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3060
-     * (probably a bad assumption they have made, oh well)
3061
-     *
3062
-     * @return string
3063
-     */
3064
-    public function __toString()
3065
-    {
3066
-        try {
3067
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3068
-        } catch (Exception $e) {
3069
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3070
-            return '';
3071
-        }
3072
-    }
3073
-
3074
-
3075
-    /**
3076
-     * Clear related model objects if they're already in the DB, because otherwise when we
3077
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3078
-     * This means if we have made changes to those related model objects, and want to unserialize
3079
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3080
-     * Instead, those related model objects should be directly serialized and stored.
3081
-     * Eg, the following won't work:
3082
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3083
-     * $att = $reg->attendee();
3084
-     * $att->set( 'ATT_fname', 'Dirk' );
3085
-     * update_option( 'my_option', serialize( $reg ) );
3086
-     * //END REQUEST
3087
-     * //START NEXT REQUEST
3088
-     * $reg = get_option( 'my_option' );
3089
-     * $reg->attendee()->save();
3090
-     * And would need to be replace with:
3091
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3092
-     * $att = $reg->attendee();
3093
-     * $att->set( 'ATT_fname', 'Dirk' );
3094
-     * update_option( 'my_option', serialize( $reg ) );
3095
-     * //END REQUEST
3096
-     * //START NEXT REQUEST
3097
-     * $att = get_option( 'my_option' );
3098
-     * $att->save();
3099
-     *
3100
-     * @return array
3101
-     * @throws ReflectionException
3102
-     * @throws InvalidArgumentException
3103
-     * @throws InvalidInterfaceException
3104
-     * @throws InvalidDataTypeException
3105
-     * @throws EE_Error
3106
-     */
3107
-    public function __sleep()
3108
-    {
3109
-        $model = $this->get_model();
3110
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3111
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3112
-                $classname = 'EE_' . $model->get_this_model_name();
3113
-                if ($this->get_one_from_cache($relation_name) instanceof $classname
3114
-                    && $this->get_one_from_cache($relation_name)->ID()
3115
-                ) {
3116
-                    $this->clear_cache(
3117
-                        $relation_name,
3118
-                        $this->get_one_from_cache($relation_name)->ID()
3119
-                    );
3120
-                }
3121
-            }
3122
-        }
3123
-        $this->_props_n_values_provided_in_constructor = array();
3124
-        $properties_to_serialize = get_object_vars($this);
3125
-        // don't serialize the model. It's big and that risks recursion
3126
-        unset($properties_to_serialize['_model']);
3127
-        return array_keys($properties_to_serialize);
3128
-    }
3129
-
3130
-
3131
-    /**
3132
-     * restore _props_n_values_provided_in_constructor
3133
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3134
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3135
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3136
-     */
3137
-    public function __wakeup()
3138
-    {
3139
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3140
-    }
3141
-
3142
-
3143
-    /**
3144
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3145
-     * distinct with the clone host instance are also cloned.
3146
-     */
3147
-    public function __clone()
3148
-    {
3149
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3150
-        foreach ($this->_fields as $field => $value) {
3151
-            if ($value instanceof DateTime) {
3152
-                $this->_fields[ $field ] = clone $value;
3153
-            }
3154
-        }
3155
-    }
16
+	/**
17
+	 * This is an array of the original properties and values provided during construction
18
+	 * of this model object. (keys are model field names, values are their values).
19
+	 * This list is important to remember so that when we are merging data from the db, we know
20
+	 * which values to override and which to not override.
21
+	 *
22
+	 * @var array
23
+	 */
24
+	protected $_props_n_values_provided_in_constructor;
25
+
26
+	/**
27
+	 * Timezone
28
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
+	 * access to it.
32
+	 *
33
+	 * @var string
34
+	 */
35
+	protected $_timezone;
36
+
37
+	/**
38
+	 * date format
39
+	 * pattern or format for displaying dates
40
+	 *
41
+	 * @var string $_dt_frmt
42
+	 */
43
+	protected $_dt_frmt;
44
+
45
+	/**
46
+	 * time format
47
+	 * pattern or format for displaying time
48
+	 *
49
+	 * @var string $_tm_frmt
50
+	 */
51
+	protected $_tm_frmt;
52
+
53
+	/**
54
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
55
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
56
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
+	 *
59
+	 * @var array
60
+	 */
61
+	protected $_cached_properties = array();
62
+
63
+	/**
64
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
65
+	 * single
66
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
+	 * all others have an array)
69
+	 *
70
+	 * @var array
71
+	 */
72
+	protected $_model_relations = array();
73
+
74
+	/**
75
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
+	 *
78
+	 * @var array
79
+	 */
80
+	protected $_fields = array();
81
+
82
+	/**
83
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
84
+	 * For example, we might create model objects intended to only be used for the duration
85
+	 * of this request and to be thrown away, and if they were accidentally saved
86
+	 * it would be a bug.
87
+	 */
88
+	protected $_allow_persist = true;
89
+
90
+	/**
91
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
92
+	 */
93
+	protected $_has_changes = false;
94
+
95
+	/**
96
+	 * @var EEM_Base
97
+	 */
98
+	protected $_model;
99
+
100
+	/**
101
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
+	 * array as:
107
+	 * array(
108
+	 *  'Registration_Count' => 24
109
+	 * );
110
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
+	 * info)
113
+	 *
114
+	 * @var array
115
+	 */
116
+	protected $custom_selection_results = array();
117
+
118
+
119
+	/**
120
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
+	 * play nice
122
+	 *
123
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
126
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
127
+	 *                                                         corresponding db model or not.
128
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
+	 *                                                         be in when instantiating a EE_Base_Class object.
130
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
131
+	 *                                                         value is the date_format and second value is the time
132
+	 *                                                         format.
133
+	 * @throws InvalidArgumentException
134
+	 * @throws InvalidInterfaceException
135
+	 * @throws InvalidDataTypeException
136
+	 * @throws EE_Error
137
+	 * @throws ReflectionException
138
+	 */
139
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
140
+	{
141
+		$className = get_class($this);
142
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
+		$model = $this->get_model();
144
+		$model_fields = $model->field_settings(false);
145
+		// ensure $fieldValues is an array
146
+		$fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
147
+		// verify client code has not passed any invalid field names
148
+		foreach ($fieldValues as $field_name => $field_value) {
149
+			if (! isset($model_fields[ $field_name ])) {
150
+				throw new EE_Error(
151
+					sprintf(
152
+						esc_html__(
153
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
+							'event_espresso'
155
+						),
156
+						$field_name,
157
+						get_class($this),
158
+						implode(', ', array_keys($model_fields))
159
+					)
160
+				);
161
+			}
162
+		}
163
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
+		if (! empty($date_formats) && is_array($date_formats)) {
165
+			list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
166
+		} else {
167
+			// set default formats for date and time
168
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170
+		}
171
+		// if db model is instantiating
172
+		if ($bydb) {
173
+			// client code has indicated these field values are from the database
174
+			foreach ($model_fields as $fieldName => $field) {
175
+				$this->set_from_db(
176
+					$fieldName,
177
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
+				);
179
+			}
180
+		} else {
181
+			// we're constructing a brand
182
+			// new instance of the model object. Generally, this means we'll need to do more field validation
183
+			foreach ($model_fields as $fieldName => $field) {
184
+				$this->set(
185
+					$fieldName,
186
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
+					true
188
+				);
189
+			}
190
+		}
191
+		// remember what values were passed to this constructor
192
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
193
+		// remember in entity mapper
194
+		if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
+			$model->add_to_entity_map($this);
196
+		}
197
+		// setup all the relations
198
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
199
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
+				$this->_model_relations[ $relation_name ] = null;
201
+			} else {
202
+				$this->_model_relations[ $relation_name ] = array();
203
+			}
204
+		}
205
+		/**
206
+		 * Action done at the end of each model object construction
207
+		 *
208
+		 * @param EE_Base_Class $this the model object just created
209
+		 */
210
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
+	}
212
+
213
+
214
+	/**
215
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
216
+	 *
217
+	 * @return boolean
218
+	 */
219
+	public function allow_persist()
220
+	{
221
+		return $this->_allow_persist;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
227
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
+	 * you got new information that somehow made you change your mind.
229
+	 *
230
+	 * @param boolean $allow_persist
231
+	 * @return boolean
232
+	 */
233
+	public function set_allow_persist($allow_persist)
234
+	{
235
+		return $this->_allow_persist = $allow_persist;
236
+	}
237
+
238
+
239
+	/**
240
+	 * Gets the field's original value when this object was constructed during this request.
241
+	 * This can be helpful when determining if a model object has changed or not
242
+	 *
243
+	 * @param string $field_name
244
+	 * @return mixed|null
245
+	 * @throws ReflectionException
246
+	 * @throws InvalidArgumentException
247
+	 * @throws InvalidInterfaceException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws EE_Error
250
+	 */
251
+	public function get_original($field_name)
252
+	{
253
+		if (isset($this->_props_n_values_provided_in_constructor[ $field_name ])
254
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
255
+		) {
256
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
257
+		}
258
+		return null;
259
+	}
260
+
261
+
262
+	/**
263
+	 * @param EE_Base_Class $obj
264
+	 * @return string
265
+	 */
266
+	public function get_class($obj)
267
+	{
268
+		return get_class($obj);
269
+	}
270
+
271
+
272
+	/**
273
+	 * Overrides parent because parent expects old models.
274
+	 * This also doesn't do any validation, and won't work for serialized arrays
275
+	 *
276
+	 * @param    string $field_name
277
+	 * @param    mixed  $field_value
278
+	 * @param bool      $use_default
279
+	 * @throws InvalidArgumentException
280
+	 * @throws InvalidInterfaceException
281
+	 * @throws InvalidDataTypeException
282
+	 * @throws EE_Error
283
+	 * @throws ReflectionException
284
+	 * @throws ReflectionException
285
+	 * @throws ReflectionException
286
+	 */
287
+	public function set($field_name, $field_value, $use_default = false)
288
+	{
289
+		// if not using default and nothing has changed, and object has already been setup (has ID),
290
+		// then don't do anything
291
+		if (! $use_default
292
+			&& $this->_fields[ $field_name ] === $field_value
293
+			&& $this->ID()
294
+		) {
295
+			return;
296
+		}
297
+		$model = $this->get_model();
298
+		$this->_has_changes = true;
299
+		$field_obj = $model->field_settings_for($field_name);
300
+		if ($field_obj instanceof EE_Model_Field_Base) {
301
+			// if ( method_exists( $field_obj, 'set_timezone' )) {
302
+			if ($field_obj instanceof EE_Datetime_Field) {
303
+				$field_obj->set_timezone($this->_timezone);
304
+				$field_obj->set_date_format($this->_dt_frmt);
305
+				$field_obj->set_time_format($this->_tm_frmt);
306
+			}
307
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
308
+			// should the value be null?
309
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
310
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
311
+				/**
312
+				 * To save having to refactor all the models, if a default value is used for a
313
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
314
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
315
+				 * object.
316
+				 *
317
+				 * @since 4.6.10+
318
+				 */
319
+				if ($field_obj instanceof EE_Datetime_Field
320
+					&& $this->_fields[ $field_name ] !== null
321
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
322
+				) {
323
+					empty($this->_fields[ $field_name ])
324
+						? $this->set($field_name, time())
325
+						: $this->set($field_name, $this->_fields[ $field_name ]);
326
+				}
327
+			} else {
328
+				$this->_fields[ $field_name ] = $holder_of_value;
329
+			}
330
+			// if we're not in the constructor...
331
+			// now check if what we set was a primary key
332
+			if (// note: props_n_values_provided_in_constructor is only set at the END of the constructor
333
+				$this->_props_n_values_provided_in_constructor
334
+				&& $field_value
335
+				&& $field_name === $model->primary_key_name()
336
+			) {
337
+				// if so, we want all this object's fields to be filled either with
338
+				// what we've explicitly set on this model
339
+				// or what we have in the db
340
+				// echo "setting primary key!";
341
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
342
+				$obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
343
+				foreach ($fields_on_model as $field_obj) {
344
+					if (! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
345
+						&& $field_obj->get_name() !== $field_name
346
+					) {
347
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
348
+					}
349
+				}
350
+				// oh this model object has an ID? well make sure its in the entity mapper
351
+				$model->add_to_entity_map($this);
352
+			}
353
+			// let's unset any cache for this field_name from the $_cached_properties property.
354
+			$this->_clear_cached_property($field_name);
355
+		} else {
356
+			throw new EE_Error(
357
+				sprintf(
358
+					esc_html__(
359
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
360
+						'event_espresso'
361
+					),
362
+					$field_name
363
+				)
364
+			);
365
+		}
366
+	}
367
+
368
+
369
+	/**
370
+	 * Set custom select values for model.
371
+	 *
372
+	 * @param array $custom_select_values
373
+	 */
374
+	public function setCustomSelectsValues(array $custom_select_values)
375
+	{
376
+		$this->custom_selection_results = $custom_select_values;
377
+	}
378
+
379
+
380
+	/**
381
+	 * Returns the custom select value for the provided alias if its set.
382
+	 * If not set, returns null.
383
+	 *
384
+	 * @param string $alias
385
+	 * @return string|int|float|null
386
+	 */
387
+	public function getCustomSelect($alias)
388
+	{
389
+		return isset($this->custom_selection_results[ $alias ])
390
+			? $this->custom_selection_results[ $alias ]
391
+			: null;
392
+	}
393
+
394
+
395
+	/**
396
+	 * This sets the field value on the db column if it exists for the given $column_name or
397
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
398
+	 *
399
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
400
+	 * @param string $field_name  Must be the exact column name.
401
+	 * @param mixed  $field_value The value to set.
402
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
403
+	 * @throws InvalidArgumentException
404
+	 * @throws InvalidInterfaceException
405
+	 * @throws InvalidDataTypeException
406
+	 * @throws EE_Error
407
+	 * @throws ReflectionException
408
+	 */
409
+	public function set_field_or_extra_meta($field_name, $field_value)
410
+	{
411
+		if ($this->get_model()->has_field($field_name)) {
412
+			$this->set($field_name, $field_value);
413
+			return true;
414
+		}
415
+		// ensure this object is saved first so that extra meta can be properly related.
416
+		$this->save();
417
+		return $this->update_extra_meta($field_name, $field_value);
418
+	}
419
+
420
+
421
+	/**
422
+	 * This retrieves the value of the db column set on this class or if that's not present
423
+	 * it will attempt to retrieve from extra_meta if found.
424
+	 * Example Usage:
425
+	 * Via EE_Message child class:
426
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
427
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
428
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
429
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
430
+	 * value for those extra fields dynamically via the EE_message object.
431
+	 *
432
+	 * @param  string $field_name expecting the fully qualified field name.
433
+	 * @return mixed|null  value for the field if found.  null if not found.
434
+	 * @throws ReflectionException
435
+	 * @throws InvalidArgumentException
436
+	 * @throws InvalidInterfaceException
437
+	 * @throws InvalidDataTypeException
438
+	 * @throws EE_Error
439
+	 */
440
+	public function get_field_or_extra_meta($field_name)
441
+	{
442
+		if ($this->get_model()->has_field($field_name)) {
443
+			$column_value = $this->get($field_name);
444
+		} else {
445
+			// This isn't a column in the main table, let's see if it is in the extra meta.
446
+			$column_value = $this->get_extra_meta($field_name, true, null);
447
+		}
448
+		return $column_value;
449
+	}
450
+
451
+
452
+	/**
453
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
454
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
455
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
456
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
457
+	 *
458
+	 * @access public
459
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
460
+	 * @return void
461
+	 * @throws InvalidArgumentException
462
+	 * @throws InvalidInterfaceException
463
+	 * @throws InvalidDataTypeException
464
+	 * @throws EE_Error
465
+	 * @throws ReflectionException
466
+	 */
467
+	public function set_timezone($timezone = '')
468
+	{
469
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
470
+		// make sure we clear all cached properties because they won't be relevant now
471
+		$this->_clear_cached_properties();
472
+		// make sure we update field settings and the date for all EE_Datetime_Fields
473
+		$model_fields = $this->get_model()->field_settings(false);
474
+		foreach ($model_fields as $field_name => $field_obj) {
475
+			if ($field_obj instanceof EE_Datetime_Field) {
476
+				$field_obj->set_timezone($this->_timezone);
477
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
478
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
479
+				}
480
+			}
481
+		}
482
+	}
483
+
484
+
485
+	/**
486
+	 * This just returns whatever is set for the current timezone.
487
+	 *
488
+	 * @access public
489
+	 * @return string timezone string
490
+	 */
491
+	public function get_timezone()
492
+	{
493
+		return $this->_timezone;
494
+	}
495
+
496
+
497
+	/**
498
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
499
+	 * internally instead of wp set date format options
500
+	 *
501
+	 * @since 4.6
502
+	 * @param string $format should be a format recognizable by PHP date() functions.
503
+	 */
504
+	public function set_date_format($format)
505
+	{
506
+		$this->_dt_frmt = $format;
507
+		// clear cached_properties because they won't be relevant now.
508
+		$this->_clear_cached_properties();
509
+	}
510
+
511
+
512
+	/**
513
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
514
+	 * class internally instead of wp set time format options.
515
+	 *
516
+	 * @since 4.6
517
+	 * @param string $format should be a format recognizable by PHP date() functions.
518
+	 */
519
+	public function set_time_format($format)
520
+	{
521
+		$this->_tm_frmt = $format;
522
+		// clear cached_properties because they won't be relevant now.
523
+		$this->_clear_cached_properties();
524
+	}
525
+
526
+
527
+	/**
528
+	 * This returns the current internal set format for the date and time formats.
529
+	 *
530
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
531
+	 *                             where the first value is the date format and the second value is the time format.
532
+	 * @return mixed string|array
533
+	 */
534
+	public function get_format($full = true)
535
+	{
536
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
537
+	}
538
+
539
+
540
+	/**
541
+	 * cache
542
+	 * stores the passed model object on the current model object.
543
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
544
+	 *
545
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
546
+	 *                                       'Registration' associated with this model object
547
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
548
+	 *                                       that could be a payment or a registration)
549
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
550
+	 *                                       items which will be stored in an array on this object
551
+	 * @throws ReflectionException
552
+	 * @throws InvalidArgumentException
553
+	 * @throws InvalidInterfaceException
554
+	 * @throws InvalidDataTypeException
555
+	 * @throws EE_Error
556
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
557
+	 *                                       related thing, no array)
558
+	 */
559
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
560
+	{
561
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
562
+		if (! $object_to_cache instanceof EE_Base_Class) {
563
+			return false;
564
+		}
565
+		// also get "how" the object is related, or throw an error
566
+		if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
567
+			throw new EE_Error(
568
+				sprintf(
569
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
570
+					$relationName,
571
+					get_class($this)
572
+				)
573
+			);
574
+		}
575
+		// how many things are related ?
576
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
577
+			// if it's a "belongs to" relationship, then there's only one related model object
578
+			// eg, if this is a registration, there's only 1 attendee for it
579
+			// so for these model objects just set it to be cached
580
+			$this->_model_relations[ $relationName ] = $object_to_cache;
581
+			$return = true;
582
+		} else {
583
+			// otherwise, this is the "many" side of a one to many relationship,
584
+			// so we'll add the object to the array of related objects for that type.
585
+			// eg: if this is an event, there are many registrations for that event,
586
+			// so we cache the registrations in an array
587
+			if (! is_array($this->_model_relations[ $relationName ])) {
588
+				// if for some reason, the cached item is a model object,
589
+				// then stick that in the array, otherwise start with an empty array
590
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
591
+														   instanceof
592
+														   EE_Base_Class
593
+					? array($this->_model_relations[ $relationName ]) : array();
594
+			}
595
+			// first check for a cache_id which is normally empty
596
+			if (! empty($cache_id)) {
597
+				// if the cache_id exists, then it means we are purposely trying to cache this
598
+				// with a known key that can then be used to retrieve the object later on
599
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
600
+				$return = $cache_id;
601
+			} elseif ($object_to_cache->ID()) {
602
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
603
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
604
+				$return = $object_to_cache->ID();
605
+			} else {
606
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
607
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
608
+				// move the internal pointer to the end of the array
609
+				end($this->_model_relations[ $relationName ]);
610
+				// and grab the key so that we can return it
611
+				$return = key($this->_model_relations[ $relationName ]);
612
+			}
613
+		}
614
+		return $return;
615
+	}
616
+
617
+
618
+	/**
619
+	 * For adding an item to the cached_properties property.
620
+	 *
621
+	 * @access protected
622
+	 * @param string      $fieldname the property item the corresponding value is for.
623
+	 * @param mixed       $value     The value we are caching.
624
+	 * @param string|null $cache_type
625
+	 * @return void
626
+	 * @throws ReflectionException
627
+	 * @throws InvalidArgumentException
628
+	 * @throws InvalidInterfaceException
629
+	 * @throws InvalidDataTypeException
630
+	 * @throws EE_Error
631
+	 */
632
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
633
+	{
634
+		// first make sure this property exists
635
+		$this->get_model()->field_settings_for($fieldname);
636
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
637
+		$this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
638
+	}
639
+
640
+
641
+	/**
642
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
643
+	 * This also SETS the cache if we return the actual property!
644
+	 *
645
+	 * @param string $fieldname        the name of the property we're trying to retrieve
646
+	 * @param bool   $pretty
647
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
648
+	 *                                 (in cases where the same property may be used for different outputs
649
+	 *                                 - i.e. datetime, money etc.)
650
+	 *                                 It can also accept certain pre-defined "schema" strings
651
+	 *                                 to define how to output the property.
652
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
653
+	 * @return mixed                   whatever the value for the property is we're retrieving
654
+	 * @throws ReflectionException
655
+	 * @throws InvalidArgumentException
656
+	 * @throws InvalidInterfaceException
657
+	 * @throws InvalidDataTypeException
658
+	 * @throws EE_Error
659
+	 */
660
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
661
+	{
662
+		// verify the field exists
663
+		$model = $this->get_model();
664
+		$model->field_settings_for($fieldname);
665
+		$cache_type = $pretty ? 'pretty' : 'standard';
666
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
667
+		if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
668
+			return $this->_cached_properties[ $fieldname ][ $cache_type ];
669
+		}
670
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
671
+		$this->_set_cached_property($fieldname, $value, $cache_type);
672
+		return $value;
673
+	}
674
+
675
+
676
+	/**
677
+	 * If the cache didn't fetch the needed item, this fetches it.
678
+	 *
679
+	 * @param string $fieldname
680
+	 * @param bool   $pretty
681
+	 * @param string $extra_cache_ref
682
+	 * @return mixed
683
+	 * @throws InvalidArgumentException
684
+	 * @throws InvalidInterfaceException
685
+	 * @throws InvalidDataTypeException
686
+	 * @throws EE_Error
687
+	 * @throws ReflectionException
688
+	 */
689
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
690
+	{
691
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
692
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
693
+		if ($field_obj instanceof EE_Datetime_Field) {
694
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
695
+		}
696
+		if (! isset($this->_fields[ $fieldname ])) {
697
+			$this->_fields[ $fieldname ] = null;
698
+		}
699
+		$value = $pretty
700
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
701
+			: $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
702
+		return $value;
703
+	}
704
+
705
+
706
+	/**
707
+	 * set timezone, formats, and output for EE_Datetime_Field objects
708
+	 *
709
+	 * @param \EE_Datetime_Field $datetime_field
710
+	 * @param bool               $pretty
711
+	 * @param null               $date_or_time
712
+	 * @return void
713
+	 * @throws InvalidArgumentException
714
+	 * @throws InvalidInterfaceException
715
+	 * @throws InvalidDataTypeException
716
+	 * @throws EE_Error
717
+	 */
718
+	protected function _prepare_datetime_field(
719
+		EE_Datetime_Field $datetime_field,
720
+		$pretty = false,
721
+		$date_or_time = null
722
+	) {
723
+		$datetime_field->set_timezone($this->_timezone);
724
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
725
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
726
+		// set the output returned
727
+		switch ($date_or_time) {
728
+			case 'D':
729
+				$datetime_field->set_date_time_output('date');
730
+				break;
731
+			case 'T':
732
+				$datetime_field->set_date_time_output('time');
733
+				break;
734
+			default:
735
+				$datetime_field->set_date_time_output();
736
+		}
737
+	}
738
+
739
+
740
+	/**
741
+	 * This just takes care of clearing out the cached_properties
742
+	 *
743
+	 * @return void
744
+	 */
745
+	protected function _clear_cached_properties()
746
+	{
747
+		$this->_cached_properties = array();
748
+	}
749
+
750
+
751
+	/**
752
+	 * This just clears out ONE property if it exists in the cache
753
+	 *
754
+	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
755
+	 * @return void
756
+	 */
757
+	protected function _clear_cached_property($property_name)
758
+	{
759
+		if (isset($this->_cached_properties[ $property_name ])) {
760
+			unset($this->_cached_properties[ $property_name ]);
761
+		}
762
+	}
763
+
764
+
765
+	/**
766
+	 * Ensures that this related thing is a model object.
767
+	 *
768
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
769
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
770
+	 * @return EE_Base_Class
771
+	 * @throws ReflectionException
772
+	 * @throws InvalidArgumentException
773
+	 * @throws InvalidInterfaceException
774
+	 * @throws InvalidDataTypeException
775
+	 * @throws EE_Error
776
+	 */
777
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
778
+	{
779
+		$other_model_instance = self::_get_model_instance_with_name(
780
+			self::_get_model_classname($model_name),
781
+			$this->_timezone
782
+		);
783
+		return $other_model_instance->ensure_is_obj($object_or_id);
784
+	}
785
+
786
+
787
+	/**
788
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
789
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
790
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
791
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
792
+	 *
793
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
794
+	 *                                                     Eg 'Registration'
795
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
796
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
797
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
798
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
799
+	 *                                                     this is HasMany or HABTM.
800
+	 * @throws ReflectionException
801
+	 * @throws InvalidArgumentException
802
+	 * @throws InvalidInterfaceException
803
+	 * @throws InvalidDataTypeException
804
+	 * @throws EE_Error
805
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
806
+	 *                                                     relation from all
807
+	 */
808
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
809
+	{
810
+		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
811
+		$index_in_cache = '';
812
+		if (! $relationship_to_model) {
813
+			throw new EE_Error(
814
+				sprintf(
815
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
816
+					$relationName,
817
+					get_class($this)
818
+				)
819
+			);
820
+		}
821
+		if ($clear_all) {
822
+			$obj_removed = true;
823
+			$this->_model_relations[ $relationName ] = null;
824
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
825
+			$obj_removed = $this->_model_relations[ $relationName ];
826
+			$this->_model_relations[ $relationName ] = null;
827
+		} else {
828
+			if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
829
+				&& $object_to_remove_or_index_into_array->ID()
830
+			) {
831
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
832
+				if (is_array($this->_model_relations[ $relationName ])
833
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
834
+				) {
835
+					$index_found_at = null;
836
+					// find this object in the array even though it has a different key
837
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
838
+						/** @noinspection TypeUnsafeComparisonInspection */
839
+						if ($obj instanceof EE_Base_Class
840
+							&& (
841
+								$obj == $object_to_remove_or_index_into_array
842
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
843
+							)
844
+						) {
845
+							$index_found_at = $index;
846
+							break;
847
+						}
848
+					}
849
+					if ($index_found_at) {
850
+						$index_in_cache = $index_found_at;
851
+					} else {
852
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
853
+						// if it wasn't in it to begin with. So we're done
854
+						return $object_to_remove_or_index_into_array;
855
+					}
856
+				}
857
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
858
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
859
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
860
+					/** @noinspection TypeUnsafeComparisonInspection */
861
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
862
+						$index_in_cache = $index;
863
+					}
864
+				}
865
+			} else {
866
+				$index_in_cache = $object_to_remove_or_index_into_array;
867
+			}
868
+			// supposedly we've found it. But it could just be that the client code
869
+			// provided a bad index/object
870
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
871
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
872
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
873
+			} else {
874
+				// that thing was never cached anyways.
875
+				$obj_removed = null;
876
+			}
877
+		}
878
+		return $obj_removed;
879
+	}
880
+
881
+
882
+	/**
883
+	 * update_cache_after_object_save
884
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
885
+	 * obtained after being saved to the db
886
+	 *
887
+	 * @param string        $relationName       - the type of object that is cached
888
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
889
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
890
+	 * @return boolean TRUE on success, FALSE on fail
891
+	 * @throws ReflectionException
892
+	 * @throws InvalidArgumentException
893
+	 * @throws InvalidInterfaceException
894
+	 * @throws InvalidDataTypeException
895
+	 * @throws EE_Error
896
+	 */
897
+	public function update_cache_after_object_save(
898
+		$relationName,
899
+		EE_Base_Class $newly_saved_object,
900
+		$current_cache_id = ''
901
+	) {
902
+		// verify that incoming object is of the correct type
903
+		$obj_class = 'EE_' . $relationName;
904
+		if ($newly_saved_object instanceof $obj_class) {
905
+			/* @type EE_Base_Class $newly_saved_object */
906
+			// now get the type of relation
907
+			$relationship_to_model = $this->get_model()->related_settings_for($relationName);
908
+			// if this is a 1:1 relationship
909
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
910
+				// then just replace the cached object with the newly saved object
911
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
912
+				return true;
913
+				// or if it's some kind of sordid feral polyamorous relationship...
914
+			}
915
+			if (is_array($this->_model_relations[ $relationName ])
916
+				&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
917
+			) {
918
+				// then remove the current cached item
919
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
920
+				// and cache the newly saved object using it's new ID
921
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
922
+				return true;
923
+			}
924
+		}
925
+		return false;
926
+	}
927
+
928
+
929
+	/**
930
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
931
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
932
+	 *
933
+	 * @param string $relationName
934
+	 * @return EE_Base_Class
935
+	 */
936
+	public function get_one_from_cache($relationName)
937
+	{
938
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
939
+			? $this->_model_relations[ $relationName ]
940
+			: null;
941
+		if (is_array($cached_array_or_object)) {
942
+			return array_shift($cached_array_or_object);
943
+		}
944
+		return $cached_array_or_object;
945
+	}
946
+
947
+
948
+	/**
949
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
950
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
951
+	 *
952
+	 * @param string $relationName
953
+	 * @throws ReflectionException
954
+	 * @throws InvalidArgumentException
955
+	 * @throws InvalidInterfaceException
956
+	 * @throws InvalidDataTypeException
957
+	 * @throws EE_Error
958
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
959
+	 */
960
+	public function get_all_from_cache($relationName)
961
+	{
962
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
963
+		// if the result is not an array, but exists, make it an array
964
+		$objects = is_array($objects) ? $objects : array($objects);
965
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
966
+		// basically, if this model object was stored in the session, and these cached model objects
967
+		// already have IDs, let's make sure they're in their model's entity mapper
968
+		// otherwise we will have duplicates next time we call
969
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
970
+		$model = EE_Registry::instance()->load_model($relationName);
971
+		foreach ($objects as $model_object) {
972
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
973
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
974
+				if ($model_object->ID()) {
975
+					$model->add_to_entity_map($model_object);
976
+				}
977
+			} else {
978
+				throw new EE_Error(
979
+					sprintf(
980
+						esc_html__(
981
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
982
+							'event_espresso'
983
+						),
984
+						$relationName,
985
+						gettype($model_object)
986
+					)
987
+				);
988
+			}
989
+		}
990
+		return $objects;
991
+	}
992
+
993
+
994
+	/**
995
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
996
+	 * matching the given query conditions.
997
+	 *
998
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
999
+	 * @param int   $limit              How many objects to return.
1000
+	 * @param array $query_params       Any additional conditions on the query.
1001
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1002
+	 *                                  you can indicate just the columns you want returned
1003
+	 * @return array|EE_Base_Class[]
1004
+	 * @throws ReflectionException
1005
+	 * @throws InvalidArgumentException
1006
+	 * @throws InvalidInterfaceException
1007
+	 * @throws InvalidDataTypeException
1008
+	 * @throws EE_Error
1009
+	 */
1010
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1011
+	{
1012
+		$model = $this->get_model();
1013
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1014
+			? $model->get_primary_key_field()->get_name()
1015
+			: $field_to_order_by;
1016
+		$current_value = ! empty($field) ? $this->get($field) : null;
1017
+		if (empty($field) || empty($current_value)) {
1018
+			return array();
1019
+		}
1020
+		return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1021
+	}
1022
+
1023
+
1024
+	/**
1025
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1026
+	 * matching the given query conditions.
1027
+	 *
1028
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1029
+	 * @param int   $limit              How many objects to return.
1030
+	 * @param array $query_params       Any additional conditions on the query.
1031
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1032
+	 *                                  you can indicate just the columns you want returned
1033
+	 * @return array|EE_Base_Class[]
1034
+	 * @throws ReflectionException
1035
+	 * @throws InvalidArgumentException
1036
+	 * @throws InvalidInterfaceException
1037
+	 * @throws InvalidDataTypeException
1038
+	 * @throws EE_Error
1039
+	 */
1040
+	public function previous_x(
1041
+		$field_to_order_by = null,
1042
+		$limit = 1,
1043
+		$query_params = array(),
1044
+		$columns_to_select = null
1045
+	) {
1046
+		$model = $this->get_model();
1047
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1048
+			? $model->get_primary_key_field()->get_name()
1049
+			: $field_to_order_by;
1050
+		$current_value = ! empty($field) ? $this->get($field) : null;
1051
+		if (empty($field) || empty($current_value)) {
1052
+			return array();
1053
+		}
1054
+		return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1055
+	}
1056
+
1057
+
1058
+	/**
1059
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1060
+	 * matching the given query conditions.
1061
+	 *
1062
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1063
+	 * @param array $query_params       Any additional conditions on the query.
1064
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1065
+	 *                                  you can indicate just the columns you want returned
1066
+	 * @return array|EE_Base_Class
1067
+	 * @throws ReflectionException
1068
+	 * @throws InvalidArgumentException
1069
+	 * @throws InvalidInterfaceException
1070
+	 * @throws InvalidDataTypeException
1071
+	 * @throws EE_Error
1072
+	 */
1073
+	public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1074
+	{
1075
+		$model = $this->get_model();
1076
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1077
+			? $model->get_primary_key_field()->get_name()
1078
+			: $field_to_order_by;
1079
+		$current_value = ! empty($field) ? $this->get($field) : null;
1080
+		if (empty($field) || empty($current_value)) {
1081
+			return array();
1082
+		}
1083
+		return $model->next($current_value, $field, $query_params, $columns_to_select);
1084
+	}
1085
+
1086
+
1087
+	/**
1088
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1089
+	 * matching the given query conditions.
1090
+	 *
1091
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1092
+	 * @param array $query_params       Any additional conditions on the query.
1093
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1094
+	 *                                  you can indicate just the column you want returned
1095
+	 * @return array|EE_Base_Class
1096
+	 * @throws ReflectionException
1097
+	 * @throws InvalidArgumentException
1098
+	 * @throws InvalidInterfaceException
1099
+	 * @throws InvalidDataTypeException
1100
+	 * @throws EE_Error
1101
+	 */
1102
+	public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1103
+	{
1104
+		$model = $this->get_model();
1105
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1106
+			? $model->get_primary_key_field()->get_name()
1107
+			: $field_to_order_by;
1108
+		$current_value = ! empty($field) ? $this->get($field) : null;
1109
+		if (empty($field) || empty($current_value)) {
1110
+			return array();
1111
+		}
1112
+		return $model->previous($current_value, $field, $query_params, $columns_to_select);
1113
+	}
1114
+
1115
+
1116
+	/**
1117
+	 * Overrides parent because parent expects old models.
1118
+	 * This also doesn't do any validation, and won't work for serialized arrays
1119
+	 *
1120
+	 * @param string $field_name
1121
+	 * @param mixed  $field_value_from_db
1122
+	 * @throws ReflectionException
1123
+	 * @throws InvalidArgumentException
1124
+	 * @throws InvalidInterfaceException
1125
+	 * @throws InvalidDataTypeException
1126
+	 * @throws EE_Error
1127
+	 */
1128
+	public function set_from_db($field_name, $field_value_from_db)
1129
+	{
1130
+		$field_obj = $this->get_model()->field_settings_for($field_name);
1131
+		if ($field_obj instanceof EE_Model_Field_Base) {
1132
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
1133
+			// eg, a CPT model object could have an entry in the posts table, but no
1134
+			// entry in the meta table. Meaning that all its columns in the meta table
1135
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
1136
+			if ($field_value_from_db === null) {
1137
+				if ($field_obj->is_nullable()) {
1138
+					// if the field allows nulls, then let it be null
1139
+					$field_value = null;
1140
+				} else {
1141
+					$field_value = $field_obj->get_default_value();
1142
+				}
1143
+			} else {
1144
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1145
+			}
1146
+			$this->_fields[ $field_name ] = $field_value;
1147
+			$this->_clear_cached_property($field_name);
1148
+		}
1149
+	}
1150
+
1151
+
1152
+	/**
1153
+	 * verifies that the specified field is of the correct type
1154
+	 *
1155
+	 * @param string $field_name
1156
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1157
+	 *                                (in cases where the same property may be used for different outputs
1158
+	 *                                - i.e. datetime, money etc.)
1159
+	 * @return mixed
1160
+	 * @throws ReflectionException
1161
+	 * @throws InvalidArgumentException
1162
+	 * @throws InvalidInterfaceException
1163
+	 * @throws InvalidDataTypeException
1164
+	 * @throws EE_Error
1165
+	 */
1166
+	public function get($field_name, $extra_cache_ref = null)
1167
+	{
1168
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1169
+	}
1170
+
1171
+
1172
+	/**
1173
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1174
+	 *
1175
+	 * @param  string $field_name A valid fieldname
1176
+	 * @return mixed              Whatever the raw value stored on the property is.
1177
+	 * @throws ReflectionException
1178
+	 * @throws InvalidArgumentException
1179
+	 * @throws InvalidInterfaceException
1180
+	 * @throws InvalidDataTypeException
1181
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1182
+	 */
1183
+	public function get_raw($field_name)
1184
+	{
1185
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1186
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1187
+			? $this->_fields[ $field_name ]->format('U')
1188
+			: $this->_fields[ $field_name ];
1189
+	}
1190
+
1191
+
1192
+	/**
1193
+	 * This is used to return the internal DateTime object used for a field that is a
1194
+	 * EE_Datetime_Field.
1195
+	 *
1196
+	 * @param string $field_name               The field name retrieving the DateTime object.
1197
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1198
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1199
+	 *                                         EE_Datetime_Field and but the field value is null, then
1200
+	 *                                         just null is returned (because that indicates that likely
1201
+	 *                                         this field is nullable).
1202
+	 * @throws InvalidArgumentException
1203
+	 * @throws InvalidDataTypeException
1204
+	 * @throws InvalidInterfaceException
1205
+	 * @throws ReflectionException
1206
+	 */
1207
+	public function get_DateTime_object($field_name)
1208
+	{
1209
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1210
+		if (! $field_settings instanceof EE_Datetime_Field) {
1211
+			EE_Error::add_error(
1212
+				sprintf(
1213
+					esc_html__(
1214
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1215
+						'event_espresso'
1216
+					),
1217
+					$field_name
1218
+				),
1219
+				__FILE__,
1220
+				__FUNCTION__,
1221
+				__LINE__
1222
+			);
1223
+			return false;
1224
+		}
1225
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1226
+			? clone $this->_fields[ $field_name ]
1227
+			: null;
1228
+	}
1229
+
1230
+
1231
+	/**
1232
+	 * To be used in template to immediately echo out the value, and format it for output.
1233
+	 * Eg, should call stripslashes and whatnot before echoing
1234
+	 *
1235
+	 * @param string $field_name      the name of the field as it appears in the DB
1236
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1237
+	 *                                (in cases where the same property may be used for different outputs
1238
+	 *                                - i.e. datetime, money etc.)
1239
+	 * @return void
1240
+	 * @throws ReflectionException
1241
+	 * @throws InvalidArgumentException
1242
+	 * @throws InvalidInterfaceException
1243
+	 * @throws InvalidDataTypeException
1244
+	 * @throws EE_Error
1245
+	 */
1246
+	public function e($field_name, $extra_cache_ref = null)
1247
+	{
1248
+		echo $this->get_pretty($field_name, $extra_cache_ref);
1249
+	}
1250
+
1251
+
1252
+	/**
1253
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1254
+	 * can be easily used as the value of form input.
1255
+	 *
1256
+	 * @param string $field_name
1257
+	 * @return void
1258
+	 * @throws ReflectionException
1259
+	 * @throws InvalidArgumentException
1260
+	 * @throws InvalidInterfaceException
1261
+	 * @throws InvalidDataTypeException
1262
+	 * @throws EE_Error
1263
+	 */
1264
+	public function f($field_name)
1265
+	{
1266
+		$this->e($field_name, 'form_input');
1267
+	}
1268
+
1269
+
1270
+	/**
1271
+	 * Same as `f()` but just returns the value instead of echoing it
1272
+	 *
1273
+	 * @param string $field_name
1274
+	 * @return string
1275
+	 * @throws ReflectionException
1276
+	 * @throws InvalidArgumentException
1277
+	 * @throws InvalidInterfaceException
1278
+	 * @throws InvalidDataTypeException
1279
+	 * @throws EE_Error
1280
+	 */
1281
+	public function get_f($field_name)
1282
+	{
1283
+		return (string) $this->get_pretty($field_name, 'form_input');
1284
+	}
1285
+
1286
+
1287
+	/**
1288
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1289
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1290
+	 * to see what options are available.
1291
+	 *
1292
+	 * @param string $field_name
1293
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1294
+	 *                                (in cases where the same property may be used for different outputs
1295
+	 *                                - i.e. datetime, money etc.)
1296
+	 * @return mixed
1297
+	 * @throws ReflectionException
1298
+	 * @throws InvalidArgumentException
1299
+	 * @throws InvalidInterfaceException
1300
+	 * @throws InvalidDataTypeException
1301
+	 * @throws EE_Error
1302
+	 */
1303
+	public function get_pretty($field_name, $extra_cache_ref = null)
1304
+	{
1305
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1306
+	}
1307
+
1308
+
1309
+	/**
1310
+	 * This simply returns the datetime for the given field name
1311
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1312
+	 * (and the equivalent e_date, e_time, e_datetime).
1313
+	 *
1314
+	 * @access   protected
1315
+	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1316
+	 * @param string   $dt_frmt      valid datetime format used for date
1317
+	 *                               (if '' then we just use the default on the field,
1318
+	 *                               if NULL we use the last-used format)
1319
+	 * @param string   $tm_frmt      Same as above except this is for time format
1320
+	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1321
+	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1322
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1323
+	 *                               if field is not a valid dtt field, or void if echoing
1324
+	 * @throws ReflectionException
1325
+	 * @throws InvalidArgumentException
1326
+	 * @throws InvalidInterfaceException
1327
+	 * @throws InvalidDataTypeException
1328
+	 * @throws EE_Error
1329
+	 */
1330
+	protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1331
+	{
1332
+		// clear cached property
1333
+		$this->_clear_cached_property($field_name);
1334
+		// reset format properties because they are used in get()
1335
+		$this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1336
+		$this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1337
+		if ($echo) {
1338
+			$this->e($field_name, $date_or_time);
1339
+			return '';
1340
+		}
1341
+		return $this->get($field_name, $date_or_time);
1342
+	}
1343
+
1344
+
1345
+	/**
1346
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1347
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1348
+	 * other echoes the pretty value for dtt)
1349
+	 *
1350
+	 * @param  string $field_name name of model object datetime field holding the value
1351
+	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1352
+	 * @return string            datetime value formatted
1353
+	 * @throws ReflectionException
1354
+	 * @throws InvalidArgumentException
1355
+	 * @throws InvalidInterfaceException
1356
+	 * @throws InvalidDataTypeException
1357
+	 * @throws EE_Error
1358
+	 */
1359
+	public function get_date($field_name, $format = '')
1360
+	{
1361
+		return $this->_get_datetime($field_name, $format, null, 'D');
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * @param        $field_name
1367
+	 * @param string $format
1368
+	 * @throws ReflectionException
1369
+	 * @throws InvalidArgumentException
1370
+	 * @throws InvalidInterfaceException
1371
+	 * @throws InvalidDataTypeException
1372
+	 * @throws EE_Error
1373
+	 */
1374
+	public function e_date($field_name, $format = '')
1375
+	{
1376
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1377
+	}
1378
+
1379
+
1380
+	/**
1381
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1382
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1383
+	 * other echoes the pretty value for dtt)
1384
+	 *
1385
+	 * @param  string $field_name name of model object datetime field holding the value
1386
+	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1387
+	 * @return string             datetime value formatted
1388
+	 * @throws ReflectionException
1389
+	 * @throws InvalidArgumentException
1390
+	 * @throws InvalidInterfaceException
1391
+	 * @throws InvalidDataTypeException
1392
+	 * @throws EE_Error
1393
+	 */
1394
+	public function get_time($field_name, $format = '')
1395
+	{
1396
+		return $this->_get_datetime($field_name, null, $format, 'T');
1397
+	}
1398
+
1399
+
1400
+	/**
1401
+	 * @param        $field_name
1402
+	 * @param string $format
1403
+	 * @throws ReflectionException
1404
+	 * @throws InvalidArgumentException
1405
+	 * @throws InvalidInterfaceException
1406
+	 * @throws InvalidDataTypeException
1407
+	 * @throws EE_Error
1408
+	 */
1409
+	public function e_time($field_name, $format = '')
1410
+	{
1411
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1412
+	}
1413
+
1414
+
1415
+	/**
1416
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1417
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1418
+	 * other echoes the pretty value for dtt)
1419
+	 *
1420
+	 * @param  string $field_name name of model object datetime field holding the value
1421
+	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1422
+	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1423
+	 * @return string             datetime value formatted
1424
+	 * @throws ReflectionException
1425
+	 * @throws InvalidArgumentException
1426
+	 * @throws InvalidInterfaceException
1427
+	 * @throws InvalidDataTypeException
1428
+	 * @throws EE_Error
1429
+	 */
1430
+	public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1431
+	{
1432
+		return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1433
+	}
1434
+
1435
+
1436
+	/**
1437
+	 * @param string $field_name
1438
+	 * @param string $dt_frmt
1439
+	 * @param string $tm_frmt
1440
+	 * @throws ReflectionException
1441
+	 * @throws InvalidArgumentException
1442
+	 * @throws InvalidInterfaceException
1443
+	 * @throws InvalidDataTypeException
1444
+	 * @throws EE_Error
1445
+	 */
1446
+	public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1447
+	{
1448
+		$this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1449
+	}
1450
+
1451
+
1452
+	/**
1453
+	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1454
+	 *
1455
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1456
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1457
+	 *                           on the object will be used.
1458
+	 * @return string Date and time string in set locale or false if no field exists for the given
1459
+	 * @throws ReflectionException
1460
+	 * @throws InvalidArgumentException
1461
+	 * @throws InvalidInterfaceException
1462
+	 * @throws InvalidDataTypeException
1463
+	 * @throws EE_Error
1464
+	 *                           field name.
1465
+	 */
1466
+	public function get_i18n_datetime($field_name, $format = '')
1467
+	{
1468
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1469
+		return date_i18n(
1470
+			$format,
1471
+			EEH_DTT_Helper::get_timestamp_with_offset(
1472
+				$this->get_raw($field_name),
1473
+				$this->_timezone
1474
+			)
1475
+		);
1476
+	}
1477
+
1478
+
1479
+	/**
1480
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1481
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1482
+	 * thrown.
1483
+	 *
1484
+	 * @param  string $field_name The field name being checked
1485
+	 * @throws ReflectionException
1486
+	 * @throws InvalidArgumentException
1487
+	 * @throws InvalidInterfaceException
1488
+	 * @throws InvalidDataTypeException
1489
+	 * @throws EE_Error
1490
+	 * @return EE_Datetime_Field
1491
+	 */
1492
+	protected function _get_dtt_field_settings($field_name)
1493
+	{
1494
+		$field = $this->get_model()->field_settings_for($field_name);
1495
+		// check if field is dtt
1496
+		if ($field instanceof EE_Datetime_Field) {
1497
+			return $field;
1498
+		}
1499
+		throw new EE_Error(
1500
+			sprintf(
1501
+				esc_html__(
1502
+					'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1503
+					'event_espresso'
1504
+				),
1505
+				$field_name,
1506
+				self::_get_model_classname(get_class($this))
1507
+			)
1508
+		);
1509
+	}
1510
+
1511
+
1512
+
1513
+
1514
+	/**
1515
+	 * NOTE ABOUT BELOW:
1516
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1517
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1518
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1519
+	 * method and make sure you send the entire datetime value for setting.
1520
+	 */
1521
+	/**
1522
+	 * sets the time on a datetime property
1523
+	 *
1524
+	 * @access protected
1525
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1526
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1527
+	 * @throws ReflectionException
1528
+	 * @throws InvalidArgumentException
1529
+	 * @throws InvalidInterfaceException
1530
+	 * @throws InvalidDataTypeException
1531
+	 * @throws EE_Error
1532
+	 */
1533
+	protected function _set_time_for($time, $fieldname)
1534
+	{
1535
+		$this->_set_date_time('T', $time, $fieldname);
1536
+	}
1537
+
1538
+
1539
+	/**
1540
+	 * sets the date on a datetime property
1541
+	 *
1542
+	 * @access protected
1543
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1544
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1545
+	 * @throws ReflectionException
1546
+	 * @throws InvalidArgumentException
1547
+	 * @throws InvalidInterfaceException
1548
+	 * @throws InvalidDataTypeException
1549
+	 * @throws EE_Error
1550
+	 */
1551
+	protected function _set_date_for($date, $fieldname)
1552
+	{
1553
+		$this->_set_date_time('D', $date, $fieldname);
1554
+	}
1555
+
1556
+
1557
+	/**
1558
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1559
+	 * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1560
+	 *
1561
+	 * @access protected
1562
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1563
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1564
+	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1565
+	 *                                        EE_Datetime_Field property)
1566
+	 * @throws ReflectionException
1567
+	 * @throws InvalidArgumentException
1568
+	 * @throws InvalidInterfaceException
1569
+	 * @throws InvalidDataTypeException
1570
+	 * @throws EE_Error
1571
+	 */
1572
+	protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1573
+	{
1574
+		$field = $this->_get_dtt_field_settings($fieldname);
1575
+		$field->set_timezone($this->_timezone);
1576
+		$field->set_date_format($this->_dt_frmt);
1577
+		$field->set_time_format($this->_tm_frmt);
1578
+		switch ($what) {
1579
+			case 'T':
1580
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1581
+					$datetime_value,
1582
+					$this->_fields[ $fieldname ]
1583
+				);
1584
+				break;
1585
+			case 'D':
1586
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1587
+					$datetime_value,
1588
+					$this->_fields[ $fieldname ]
1589
+				);
1590
+				break;
1591
+			case 'B':
1592
+				$this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1593
+				break;
1594
+		}
1595
+		$this->_clear_cached_property($fieldname);
1596
+	}
1597
+
1598
+
1599
+	/**
1600
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1601
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1602
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1603
+	 * that could lead to some unexpected results!
1604
+	 *
1605
+	 * @access public
1606
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1607
+	 *                                         value being returned.
1608
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1609
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1610
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1611
+	 * @param string $append                   You can include something to append on the timestamp
1612
+	 * @throws ReflectionException
1613
+	 * @throws InvalidArgumentException
1614
+	 * @throws InvalidInterfaceException
1615
+	 * @throws InvalidDataTypeException
1616
+	 * @throws EE_Error
1617
+	 * @return string timestamp
1618
+	 */
1619
+	public function display_in_my_timezone(
1620
+		$field_name,
1621
+		$callback = 'get_datetime',
1622
+		$args = null,
1623
+		$prepend = '',
1624
+		$append = ''
1625
+	) {
1626
+		$timezone = EEH_DTT_Helper::get_timezone();
1627
+		if ($timezone === $this->_timezone) {
1628
+			return '';
1629
+		}
1630
+		$original_timezone = $this->_timezone;
1631
+		$this->set_timezone($timezone);
1632
+		$fn = (array) $field_name;
1633
+		$args = array_merge($fn, (array) $args);
1634
+		if (! method_exists($this, $callback)) {
1635
+			throw new EE_Error(
1636
+				sprintf(
1637
+					esc_html__(
1638
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1639
+						'event_espresso'
1640
+					),
1641
+					$callback
1642
+				)
1643
+			);
1644
+		}
1645
+		$args = (array) $args;
1646
+		$return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1647
+		$this->set_timezone($original_timezone);
1648
+		return $return;
1649
+	}
1650
+
1651
+
1652
+	/**
1653
+	 * Deletes this model object.
1654
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1655
+	 * override
1656
+	 * `EE_Base_Class::_delete` NOT this class.
1657
+	 *
1658
+	 * @return boolean | int
1659
+	 * @throws ReflectionException
1660
+	 * @throws InvalidArgumentException
1661
+	 * @throws InvalidInterfaceException
1662
+	 * @throws InvalidDataTypeException
1663
+	 * @throws EE_Error
1664
+	 */
1665
+	public function delete()
1666
+	{
1667
+		/**
1668
+		 * Called just before the `EE_Base_Class::_delete` method call.
1669
+		 * Note:
1670
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1671
+		 * should be aware that `_delete` may not always result in a permanent delete.
1672
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1673
+		 * soft deletes (trash) the object and does not permanently delete it.
1674
+		 *
1675
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1676
+		 */
1677
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1678
+		$result = $this->_delete();
1679
+		/**
1680
+		 * Called just after the `EE_Base_Class::_delete` method call.
1681
+		 * Note:
1682
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1683
+		 * should be aware that `_delete` may not always result in a permanent delete.
1684
+		 * For example `EE_Soft_Base_Class::_delete`
1685
+		 * soft deletes (trash) the object and does not permanently delete it.
1686
+		 *
1687
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1688
+		 * @param boolean       $result
1689
+		 */
1690
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1691
+		return $result;
1692
+	}
1693
+
1694
+
1695
+	/**
1696
+	 * Calls the specific delete method for the instantiated class.
1697
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1698
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1699
+	 * `EE_Base_Class::delete`
1700
+	 *
1701
+	 * @return bool|int
1702
+	 * @throws ReflectionException
1703
+	 * @throws InvalidArgumentException
1704
+	 * @throws InvalidInterfaceException
1705
+	 * @throws InvalidDataTypeException
1706
+	 * @throws EE_Error
1707
+	 */
1708
+	protected function _delete()
1709
+	{
1710
+		return $this->delete_permanently();
1711
+	}
1712
+
1713
+
1714
+	/**
1715
+	 * Deletes this model object permanently from db
1716
+	 * (but keep in mind related models may block the delete and return an error)
1717
+	 *
1718
+	 * @return bool | int
1719
+	 * @throws ReflectionException
1720
+	 * @throws InvalidArgumentException
1721
+	 * @throws InvalidInterfaceException
1722
+	 * @throws InvalidDataTypeException
1723
+	 * @throws EE_Error
1724
+	 */
1725
+	public function delete_permanently()
1726
+	{
1727
+		/**
1728
+		 * Called just before HARD deleting a model object
1729
+		 *
1730
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1731
+		 */
1732
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1733
+		$model = $this->get_model();
1734
+		$result = $model->delete_permanently_by_ID($this->ID());
1735
+		$this->refresh_cache_of_related_objects();
1736
+		/**
1737
+		 * Called just after HARD deleting a model object
1738
+		 *
1739
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1740
+		 * @param boolean       $result
1741
+		 */
1742
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1743
+		return $result;
1744
+	}
1745
+
1746
+
1747
+	/**
1748
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1749
+	 * related model objects
1750
+	 *
1751
+	 * @throws ReflectionException
1752
+	 * @throws InvalidArgumentException
1753
+	 * @throws InvalidInterfaceException
1754
+	 * @throws InvalidDataTypeException
1755
+	 * @throws EE_Error
1756
+	 */
1757
+	public function refresh_cache_of_related_objects()
1758
+	{
1759
+		$model = $this->get_model();
1760
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1761
+			if (! empty($this->_model_relations[ $relation_name ])) {
1762
+				$related_objects = $this->_model_relations[ $relation_name ];
1763
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1764
+					// this relation only stores a single model object, not an array
1765
+					// but let's make it consistent
1766
+					$related_objects = array($related_objects);
1767
+				}
1768
+				foreach ($related_objects as $related_object) {
1769
+					// only refresh their cache if they're in memory
1770
+					if ($related_object instanceof EE_Base_Class) {
1771
+						$related_object->clear_cache(
1772
+							$model->get_this_model_name(),
1773
+							$this
1774
+						);
1775
+					}
1776
+				}
1777
+			}
1778
+		}
1779
+	}
1780
+
1781
+
1782
+	/**
1783
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1784
+	 * object just before saving.
1785
+	 *
1786
+	 * @access public
1787
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1788
+	 *                                 if provided during the save() method (often client code will change the fields'
1789
+	 *                                 values before calling save)
1790
+	 * @throws InvalidArgumentException
1791
+	 * @throws InvalidInterfaceException
1792
+	 * @throws InvalidDataTypeException
1793
+	 * @throws EE_Error
1794
+	 * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1795
+	 *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1796
+	 * @throws ReflectionException
1797
+	 * @throws ReflectionException
1798
+	 * @throws ReflectionException
1799
+	 */
1800
+	public function save($set_cols_n_values = array())
1801
+	{
1802
+		$model = $this->get_model();
1803
+		/**
1804
+		 * Filters the fields we're about to save on the model object
1805
+		 *
1806
+		 * @param array         $set_cols_n_values
1807
+		 * @param EE_Base_Class $model_object
1808
+		 */
1809
+		$set_cols_n_values = (array) apply_filters(
1810
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1811
+			$set_cols_n_values,
1812
+			$this
1813
+		);
1814
+		// set attributes as provided in $set_cols_n_values
1815
+		foreach ($set_cols_n_values as $column => $value) {
1816
+			$this->set($column, $value);
1817
+		}
1818
+		// no changes ? then don't do anything
1819
+		if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1820
+			return 0;
1821
+		}
1822
+		/**
1823
+		 * Saving a model object.
1824
+		 * Before we perform a save, this action is fired.
1825
+		 *
1826
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1827
+		 */
1828
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1829
+		if (! $this->allow_persist()) {
1830
+			return 0;
1831
+		}
1832
+		// now get current attribute values
1833
+		$save_cols_n_values = $this->_fields;
1834
+		// if the object already has an ID, update it. Otherwise, insert it
1835
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1836
+		// They have been
1837
+		$old_assumption_concerning_value_preparation = $model
1838
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1839
+		$model->assume_values_already_prepared_by_model_object(true);
1840
+		// does this model have an autoincrement PK?
1841
+		if ($model->has_primary_key_field()) {
1842
+			if ($model->get_primary_key_field()->is_auto_increment()) {
1843
+				// ok check if it's set, if so: update; if not, insert
1844
+				if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1845
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1846
+				} else {
1847
+					unset($save_cols_n_values[ $model->primary_key_name() ]);
1848
+					$results = $model->insert($save_cols_n_values);
1849
+					if ($results) {
1850
+						// if successful, set the primary key
1851
+						// but don't use the normal SET method, because it will check if
1852
+						// an item with the same ID exists in the mapper & db, then
1853
+						// will find it in the db (because we just added it) and THAT object
1854
+						// will get added to the mapper before we can add this one!
1855
+						// but if we just avoid using the SET method, all that headache can be avoided
1856
+						$pk_field_name = $model->primary_key_name();
1857
+						$this->_fields[ $pk_field_name ] = $results;
1858
+						$this->_clear_cached_property($pk_field_name);
1859
+						$model->add_to_entity_map($this);
1860
+						$this->_update_cached_related_model_objs_fks();
1861
+					}
1862
+				}
1863
+			} else {// PK is NOT auto-increment
1864
+				// so check if one like it already exists in the db
1865
+				if ($model->exists_by_ID($this->ID())) {
1866
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1867
+						throw new EE_Error(
1868
+							sprintf(
1869
+								esc_html__(
1870
+									'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1871
+									'event_espresso'
1872
+								),
1873
+								get_class($this),
1874
+								get_class($model) . '::instance()->add_to_entity_map()',
1875
+								get_class($model) . '::instance()->get_one_by_ID()',
1876
+								'<br />'
1877
+							)
1878
+						);
1879
+					}
1880
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1881
+				} else {
1882
+					$results = $model->insert($save_cols_n_values);
1883
+					$this->_update_cached_related_model_objs_fks();
1884
+				}
1885
+			}
1886
+		} else {// there is NO primary key
1887
+			$already_in_db = false;
1888
+			foreach ($model->unique_indexes() as $index) {
1889
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1890
+				if ($model->exists(array($uniqueness_where_params))) {
1891
+					$already_in_db = true;
1892
+				}
1893
+			}
1894
+			if ($already_in_db) {
1895
+				$combined_pk_fields_n_values = array_intersect_key(
1896
+					$save_cols_n_values,
1897
+					$model->get_combined_primary_key_fields()
1898
+				);
1899
+				$results = $model->update(
1900
+					$save_cols_n_values,
1901
+					$combined_pk_fields_n_values
1902
+				);
1903
+			} else {
1904
+				$results = $model->insert($save_cols_n_values);
1905
+			}
1906
+		}
1907
+		// restore the old assumption about values being prepared by the model object
1908
+		$model->assume_values_already_prepared_by_model_object(
1909
+			$old_assumption_concerning_value_preparation
1910
+		);
1911
+		/**
1912
+		 * After saving the model object this action is called
1913
+		 *
1914
+		 * @param EE_Base_Class $model_object which was just saved
1915
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1916
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1917
+		 */
1918
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1919
+		$this->_has_changes = false;
1920
+		return $results;
1921
+	}
1922
+
1923
+
1924
+	/**
1925
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1926
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1927
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1928
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1929
+	 * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1930
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1931
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1932
+	 *
1933
+	 * @return void
1934
+	 * @throws ReflectionException
1935
+	 * @throws InvalidArgumentException
1936
+	 * @throws InvalidInterfaceException
1937
+	 * @throws InvalidDataTypeException
1938
+	 * @throws EE_Error
1939
+	 */
1940
+	protected function _update_cached_related_model_objs_fks()
1941
+	{
1942
+		$model = $this->get_model();
1943
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1944
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1945
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1946
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1947
+						$model->get_this_model_name()
1948
+					);
1949
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1950
+					if ($related_model_obj_in_cache->ID()) {
1951
+						$related_model_obj_in_cache->save();
1952
+					}
1953
+				}
1954
+			}
1955
+		}
1956
+	}
1957
+
1958
+
1959
+	/**
1960
+	 * Saves this model object and its NEW cached relations to the database.
1961
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1962
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1963
+	 * because otherwise, there's a potential for infinite looping of saving
1964
+	 * Saves the cached related model objects, and ensures the relation between them
1965
+	 * and this object and properly setup
1966
+	 *
1967
+	 * @return int ID of new model object on save; 0 on failure+
1968
+	 * @throws ReflectionException
1969
+	 * @throws InvalidArgumentException
1970
+	 * @throws InvalidInterfaceException
1971
+	 * @throws InvalidDataTypeException
1972
+	 * @throws EE_Error
1973
+	 */
1974
+	public function save_new_cached_related_model_objs()
1975
+	{
1976
+		// make sure this has been saved
1977
+		if (! $this->ID()) {
1978
+			$id = $this->save();
1979
+		} else {
1980
+			$id = $this->ID();
1981
+		}
1982
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
1983
+		foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1984
+			if ($this->_model_relations[ $relationName ]) {
1985
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1986
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1987
+				/* @var $related_model_obj EE_Base_Class */
1988
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
1989
+					// add a relation to that relation type (which saves the appropriate thing in the process)
1990
+					// but ONLY if it DOES NOT exist in the DB
1991
+					$related_model_obj = $this->_model_relations[ $relationName ];
1992
+					// if( ! $related_model_obj->ID()){
1993
+					$this->_add_relation_to($related_model_obj, $relationName);
1994
+					$related_model_obj->save_new_cached_related_model_objs();
1995
+					// }
1996
+				} else {
1997
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
1998
+						// add a relation to that relation type (which saves the appropriate thing in the process)
1999
+						// but ONLY if it DOES NOT exist in the DB
2000
+						// if( ! $related_model_obj->ID()){
2001
+						$this->_add_relation_to($related_model_obj, $relationName);
2002
+						$related_model_obj->save_new_cached_related_model_objs();
2003
+						// }
2004
+					}
2005
+				}
2006
+			}
2007
+		}
2008
+		return $id;
2009
+	}
2010
+
2011
+
2012
+	/**
2013
+	 * for getting a model while instantiated.
2014
+	 *
2015
+	 * @return EEM_Base | EEM_CPT_Base
2016
+	 * @throws ReflectionException
2017
+	 * @throws InvalidArgumentException
2018
+	 * @throws InvalidInterfaceException
2019
+	 * @throws InvalidDataTypeException
2020
+	 * @throws EE_Error
2021
+	 */
2022
+	public function get_model()
2023
+	{
2024
+		if (! $this->_model) {
2025
+			$modelName = self::_get_model_classname(get_class($this));
2026
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2027
+		} else {
2028
+			$this->_model->set_timezone($this->_timezone);
2029
+		}
2030
+		return $this->_model;
2031
+	}
2032
+
2033
+
2034
+	/**
2035
+	 * @param $props_n_values
2036
+	 * @param $classname
2037
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2038
+	 * @throws ReflectionException
2039
+	 * @throws InvalidArgumentException
2040
+	 * @throws InvalidInterfaceException
2041
+	 * @throws InvalidDataTypeException
2042
+	 * @throws EE_Error
2043
+	 */
2044
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2045
+	{
2046
+		// TODO: will not work for Term_Relationships because they have no PK!
2047
+		$primary_id_ref = self::_get_primary_key_name($classname);
2048
+		if (array_key_exists($primary_id_ref, $props_n_values)
2049
+			&& ! empty($props_n_values[ $primary_id_ref ])
2050
+		) {
2051
+			$id = $props_n_values[ $primary_id_ref ];
2052
+			return self::_get_model($classname)->get_from_entity_map($id);
2053
+		}
2054
+		return false;
2055
+	}
2056
+
2057
+
2058
+	/**
2059
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2060
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2061
+	 * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2062
+	 * we return false.
2063
+	 *
2064
+	 * @param  array  $props_n_values   incoming array of properties and their values
2065
+	 * @param  string $classname        the classname of the child class
2066
+	 * @param null    $timezone
2067
+	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
2068
+	 *                                  date_format and the second value is the time format
2069
+	 * @return mixed (EE_Base_Class|bool)
2070
+	 * @throws InvalidArgumentException
2071
+	 * @throws InvalidInterfaceException
2072
+	 * @throws InvalidDataTypeException
2073
+	 * @throws EE_Error
2074
+	 * @throws ReflectionException
2075
+	 * @throws ReflectionException
2076
+	 * @throws ReflectionException
2077
+	 */
2078
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2079
+	{
2080
+		$existing = null;
2081
+		$model = self::_get_model($classname, $timezone);
2082
+		if ($model->has_primary_key_field()) {
2083
+			$primary_id_ref = self::_get_primary_key_name($classname);
2084
+			if (array_key_exists($primary_id_ref, $props_n_values)
2085
+				&& ! empty($props_n_values[ $primary_id_ref ])
2086
+			) {
2087
+				$existing = $model->get_one_by_ID(
2088
+					$props_n_values[ $primary_id_ref ]
2089
+				);
2090
+			}
2091
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2092
+			// no primary key on this model, but there's still a matching item in the DB
2093
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2094
+				self::_get_model($classname, $timezone)
2095
+					->get_index_primary_key_string($props_n_values)
2096
+			);
2097
+		}
2098
+		if ($existing) {
2099
+			// set date formats if present before setting values
2100
+			if (! empty($date_formats) && is_array($date_formats)) {
2101
+				$existing->set_date_format($date_formats[0]);
2102
+				$existing->set_time_format($date_formats[1]);
2103
+			} else {
2104
+				// set default formats for date and time
2105
+				$existing->set_date_format(get_option('date_format'));
2106
+				$existing->set_time_format(get_option('time_format'));
2107
+			}
2108
+			foreach ($props_n_values as $property => $field_value) {
2109
+				$existing->set($property, $field_value);
2110
+			}
2111
+			return $existing;
2112
+		}
2113
+		return false;
2114
+	}
2115
+
2116
+
2117
+	/**
2118
+	 * Gets the EEM_*_Model for this class
2119
+	 *
2120
+	 * @access public now, as this is more convenient
2121
+	 * @param      $classname
2122
+	 * @param null $timezone
2123
+	 * @throws ReflectionException
2124
+	 * @throws InvalidArgumentException
2125
+	 * @throws InvalidInterfaceException
2126
+	 * @throws InvalidDataTypeException
2127
+	 * @throws EE_Error
2128
+	 * @return EEM_Base
2129
+	 */
2130
+	protected static function _get_model($classname, $timezone = null)
2131
+	{
2132
+		// find model for this class
2133
+		if (! $classname) {
2134
+			throw new EE_Error(
2135
+				sprintf(
2136
+					esc_html__(
2137
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2138
+						'event_espresso'
2139
+					),
2140
+					$classname
2141
+				)
2142
+			);
2143
+		}
2144
+		$modelName = self::_get_model_classname($classname);
2145
+		return self::_get_model_instance_with_name($modelName, $timezone);
2146
+	}
2147
+
2148
+
2149
+	/**
2150
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2151
+	 *
2152
+	 * @param string $model_classname
2153
+	 * @param null   $timezone
2154
+	 * @return EEM_Base
2155
+	 * @throws ReflectionException
2156
+	 * @throws InvalidArgumentException
2157
+	 * @throws InvalidInterfaceException
2158
+	 * @throws InvalidDataTypeException
2159
+	 * @throws EE_Error
2160
+	 */
2161
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2162
+	{
2163
+		$model_classname = str_replace('EEM_', '', $model_classname);
2164
+		$model = EE_Registry::instance()->load_model($model_classname);
2165
+		$model->set_timezone($timezone);
2166
+		return $model;
2167
+	}
2168
+
2169
+
2170
+	/**
2171
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2172
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2173
+	 *
2174
+	 * @param null $model_name
2175
+	 * @return string like EEM_Attendee
2176
+	 */
2177
+	private static function _get_model_classname($model_name = null)
2178
+	{
2179
+		if (strpos($model_name, 'EE_') === 0) {
2180
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
2181
+		} else {
2182
+			$model_classname = 'EEM_' . $model_name;
2183
+		}
2184
+		return $model_classname;
2185
+	}
2186
+
2187
+
2188
+	/**
2189
+	 * returns the name of the primary key attribute
2190
+	 *
2191
+	 * @param null $classname
2192
+	 * @throws ReflectionException
2193
+	 * @throws InvalidArgumentException
2194
+	 * @throws InvalidInterfaceException
2195
+	 * @throws InvalidDataTypeException
2196
+	 * @throws EE_Error
2197
+	 * @return string
2198
+	 */
2199
+	protected static function _get_primary_key_name($classname = null)
2200
+	{
2201
+		if (! $classname) {
2202
+			throw new EE_Error(
2203
+				sprintf(
2204
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2205
+					$classname
2206
+				)
2207
+			);
2208
+		}
2209
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2210
+	}
2211
+
2212
+
2213
+	/**
2214
+	 * Gets the value of the primary key.
2215
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2216
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2217
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2218
+	 *
2219
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2220
+	 * @throws ReflectionException
2221
+	 * @throws InvalidArgumentException
2222
+	 * @throws InvalidInterfaceException
2223
+	 * @throws InvalidDataTypeException
2224
+	 * @throws EE_Error
2225
+	 */
2226
+	public function ID()
2227
+	{
2228
+		$model = $this->get_model();
2229
+		// now that we know the name of the variable, use a variable variable to get its value and return its
2230
+		if ($model->has_primary_key_field()) {
2231
+			return $this->_fields[ $model->primary_key_name() ];
2232
+		}
2233
+		return $model->get_index_primary_key_string($this->_fields);
2234
+	}
2235
+
2236
+
2237
+	/**
2238
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2239
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2240
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2241
+	 *
2242
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2243
+	 * @param string $relationName                     eg 'Events','Question',etc.
2244
+	 *                                                 an attendee to a group, you also want to specify which role they
2245
+	 *                                                 will have in that group. So you would use this parameter to
2246
+	 *                                                 specify array('role-column-name'=>'role-id')
2247
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2248
+	 *                                                 allow you to further constrict the relation to being added.
2249
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2250
+	 *                                                 column on the JOIN table and currently only the HABTM models
2251
+	 *                                                 accept these additional conditions.  Also remember that if an
2252
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2253
+	 *                                                 NEW row is created in the join table.
2254
+	 * @param null   $cache_id
2255
+	 * @throws ReflectionException
2256
+	 * @throws InvalidArgumentException
2257
+	 * @throws InvalidInterfaceException
2258
+	 * @throws InvalidDataTypeException
2259
+	 * @throws EE_Error
2260
+	 * @return EE_Base_Class the object the relation was added to
2261
+	 */
2262
+	public function _add_relation_to(
2263
+		$otherObjectModelObjectOrID,
2264
+		$relationName,
2265
+		$extra_join_model_fields_n_values = array(),
2266
+		$cache_id = null
2267
+	) {
2268
+		$model = $this->get_model();
2269
+		// if this thing exists in the DB, save the relation to the DB
2270
+		if ($this->ID()) {
2271
+			$otherObject = $model->add_relationship_to(
2272
+				$this,
2273
+				$otherObjectModelObjectOrID,
2274
+				$relationName,
2275
+				$extra_join_model_fields_n_values
2276
+			);
2277
+			// clear cache so future get_many_related and get_first_related() return new results.
2278
+			$this->clear_cache($relationName, $otherObject, true);
2279
+			if ($otherObject instanceof EE_Base_Class) {
2280
+				$otherObject->clear_cache($model->get_this_model_name(), $this);
2281
+			}
2282
+		} else {
2283
+			// this thing doesn't exist in the DB,  so just cache it
2284
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2285
+				throw new EE_Error(
2286
+					sprintf(
2287
+						esc_html__(
2288
+							'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2289
+							'event_espresso'
2290
+						),
2291
+						$otherObjectModelObjectOrID,
2292
+						get_class($this)
2293
+					)
2294
+				);
2295
+			}
2296
+			$otherObject = $otherObjectModelObjectOrID;
2297
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2298
+		}
2299
+		if ($otherObject instanceof EE_Base_Class) {
2300
+			// fix the reciprocal relation too
2301
+			if ($otherObject->ID()) {
2302
+				// its saved so assumed relations exist in the DB, so we can just
2303
+				// clear the cache so future queries use the updated info in the DB
2304
+				$otherObject->clear_cache(
2305
+					$model->get_this_model_name(),
2306
+					null,
2307
+					true
2308
+				);
2309
+			} else {
2310
+				// it's not saved, so it caches relations like this
2311
+				$otherObject->cache($model->get_this_model_name(), $this);
2312
+			}
2313
+		}
2314
+		return $otherObject;
2315
+	}
2316
+
2317
+
2318
+	/**
2319
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2320
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2321
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2322
+	 * from the cache
2323
+	 *
2324
+	 * @param mixed  $otherObjectModelObjectOrID
2325
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2326
+	 *                to the DB yet
2327
+	 * @param string $relationName
2328
+	 * @param array  $where_query
2329
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2330
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2331
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2332
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2333
+	 *                created in the join table.
2334
+	 * @return EE_Base_Class the relation was removed from
2335
+	 * @throws ReflectionException
2336
+	 * @throws InvalidArgumentException
2337
+	 * @throws InvalidInterfaceException
2338
+	 * @throws InvalidDataTypeException
2339
+	 * @throws EE_Error
2340
+	 */
2341
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2342
+	{
2343
+		if ($this->ID()) {
2344
+			// if this exists in the DB, save the relation change to the DB too
2345
+			$otherObject = $this->get_model()->remove_relationship_to(
2346
+				$this,
2347
+				$otherObjectModelObjectOrID,
2348
+				$relationName,
2349
+				$where_query
2350
+			);
2351
+			$this->clear_cache(
2352
+				$relationName,
2353
+				$otherObject
2354
+			);
2355
+		} else {
2356
+			// this doesn't exist in the DB, just remove it from the cache
2357
+			$otherObject = $this->clear_cache(
2358
+				$relationName,
2359
+				$otherObjectModelObjectOrID
2360
+			);
2361
+		}
2362
+		if ($otherObject instanceof EE_Base_Class) {
2363
+			$otherObject->clear_cache(
2364
+				$this->get_model()->get_this_model_name(),
2365
+				$this
2366
+			);
2367
+		}
2368
+		return $otherObject;
2369
+	}
2370
+
2371
+
2372
+	/**
2373
+	 * Removes ALL the related things for the $relationName.
2374
+	 *
2375
+	 * @param string $relationName
2376
+	 * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2377
+	 * @return EE_Base_Class
2378
+	 * @throws ReflectionException
2379
+	 * @throws InvalidArgumentException
2380
+	 * @throws InvalidInterfaceException
2381
+	 * @throws InvalidDataTypeException
2382
+	 * @throws EE_Error
2383
+	 */
2384
+	public function _remove_relations($relationName, $where_query_params = array())
2385
+	{
2386
+		if ($this->ID()) {
2387
+			// if this exists in the DB, save the relation change to the DB too
2388
+			$otherObjects = $this->get_model()->remove_relations(
2389
+				$this,
2390
+				$relationName,
2391
+				$where_query_params
2392
+			);
2393
+			$this->clear_cache(
2394
+				$relationName,
2395
+				null,
2396
+				true
2397
+			);
2398
+		} else {
2399
+			// this doesn't exist in the DB, just remove it from the cache
2400
+			$otherObjects = $this->clear_cache(
2401
+				$relationName,
2402
+				null,
2403
+				true
2404
+			);
2405
+		}
2406
+		if (is_array($otherObjects)) {
2407
+			foreach ($otherObjects as $otherObject) {
2408
+				$otherObject->clear_cache(
2409
+					$this->get_model()->get_this_model_name(),
2410
+					$this
2411
+				);
2412
+			}
2413
+		}
2414
+		return $otherObjects;
2415
+	}
2416
+
2417
+
2418
+	/**
2419
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2420
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2421
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2422
+	 * because we want to get even deleted items etc.
2423
+	 *
2424
+	 * @param string $relationName key in the model's _model_relations array
2425
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2426
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2427
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2428
+	 *                             results if you want IDs
2429
+	 * @throws ReflectionException
2430
+	 * @throws InvalidArgumentException
2431
+	 * @throws InvalidInterfaceException
2432
+	 * @throws InvalidDataTypeException
2433
+	 * @throws EE_Error
2434
+	 */
2435
+	public function get_many_related($relationName, $query_params = array())
2436
+	{
2437
+		if ($this->ID()) {
2438
+			// this exists in the DB, so get the related things from either the cache or the DB
2439
+			// if there are query parameters, forget about caching the related model objects.
2440
+			if ($query_params) {
2441
+				$related_model_objects = $this->get_model()->get_all_related(
2442
+					$this,
2443
+					$relationName,
2444
+					$query_params
2445
+				);
2446
+			} else {
2447
+				// did we already cache the result of this query?
2448
+				$cached_results = $this->get_all_from_cache($relationName);
2449
+				if (! $cached_results) {
2450
+					$related_model_objects = $this->get_model()->get_all_related(
2451
+						$this,
2452
+						$relationName,
2453
+						$query_params
2454
+					);
2455
+					// if no query parameters were passed, then we got all the related model objects
2456
+					// for that relation. We can cache them then.
2457
+					foreach ($related_model_objects as $related_model_object) {
2458
+						$this->cache($relationName, $related_model_object);
2459
+					}
2460
+				} else {
2461
+					$related_model_objects = $cached_results;
2462
+				}
2463
+			}
2464
+		} else {
2465
+			// this doesn't exist in the DB, so just get the related things from the cache
2466
+			$related_model_objects = $this->get_all_from_cache($relationName);
2467
+		}
2468
+		return $related_model_objects;
2469
+	}
2470
+
2471
+
2472
+	/**
2473
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2474
+	 * unless otherwise specified in the $query_params
2475
+	 *
2476
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2477
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2478
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2479
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2480
+	 *                               that by the setting $distinct to TRUE;
2481
+	 * @return int
2482
+	 * @throws ReflectionException
2483
+	 * @throws InvalidArgumentException
2484
+	 * @throws InvalidInterfaceException
2485
+	 * @throws InvalidDataTypeException
2486
+	 * @throws EE_Error
2487
+	 */
2488
+	public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2489
+	{
2490
+		return $this->get_model()->count_related(
2491
+			$this,
2492
+			$relation_name,
2493
+			$query_params,
2494
+			$field_to_count,
2495
+			$distinct
2496
+		);
2497
+	}
2498
+
2499
+
2500
+	/**
2501
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2502
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2503
+	 *
2504
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2505
+	 * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2506
+	 * @param string $field_to_sum  name of field to count by.
2507
+	 *                              By default, uses primary key
2508
+	 *                              (which doesn't make much sense, so you should probably change it)
2509
+	 * @return int
2510
+	 * @throws ReflectionException
2511
+	 * @throws InvalidArgumentException
2512
+	 * @throws InvalidInterfaceException
2513
+	 * @throws InvalidDataTypeException
2514
+	 * @throws EE_Error
2515
+	 */
2516
+	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2517
+	{
2518
+		return $this->get_model()->sum_related(
2519
+			$this,
2520
+			$relation_name,
2521
+			$query_params,
2522
+			$field_to_sum
2523
+		);
2524
+	}
2525
+
2526
+
2527
+	/**
2528
+	 * Gets the first (ie, one) related model object of the specified type.
2529
+	 *
2530
+	 * @param string $relationName key in the model's _model_relations array
2531
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2532
+	 * @return EE_Base_Class (not an array, a single object)
2533
+	 * @throws ReflectionException
2534
+	 * @throws InvalidArgumentException
2535
+	 * @throws InvalidInterfaceException
2536
+	 * @throws InvalidDataTypeException
2537
+	 * @throws EE_Error
2538
+	 */
2539
+	public function get_first_related($relationName, $query_params = array())
2540
+	{
2541
+		$model = $this->get_model();
2542
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2543
+			// if they've provided some query parameters, don't bother trying to cache the result
2544
+			// also make sure we're not caching the result of get_first_related
2545
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
2546
+			if ($query_params
2547
+				|| ! $model->related_settings_for($relationName)
2548
+					 instanceof
2549
+					 EE_Belongs_To_Relation
2550
+			) {
2551
+				$related_model_object = $model->get_first_related(
2552
+					$this,
2553
+					$relationName,
2554
+					$query_params
2555
+				);
2556
+			} else {
2557
+				// first, check if we've already cached the result of this query
2558
+				$cached_result = $this->get_one_from_cache($relationName);
2559
+				if (! $cached_result) {
2560
+					$related_model_object = $model->get_first_related(
2561
+						$this,
2562
+						$relationName,
2563
+						$query_params
2564
+					);
2565
+					$this->cache($relationName, $related_model_object);
2566
+				} else {
2567
+					$related_model_object = $cached_result;
2568
+				}
2569
+			}
2570
+		} else {
2571
+			$related_model_object = null;
2572
+			// this doesn't exist in the Db,
2573
+			// but maybe the relation is of type belongs to, and so the related thing might
2574
+			if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2575
+				$related_model_object = $model->get_first_related(
2576
+					$this,
2577
+					$relationName,
2578
+					$query_params
2579
+				);
2580
+			}
2581
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2582
+			// just get what's cached on this object
2583
+			if (! $related_model_object) {
2584
+				$related_model_object = $this->get_one_from_cache($relationName);
2585
+			}
2586
+		}
2587
+		return $related_model_object;
2588
+	}
2589
+
2590
+
2591
+	/**
2592
+	 * Does a delete on all related objects of type $relationName and removes
2593
+	 * the current model object's relation to them. If they can't be deleted (because
2594
+	 * of blocking related model objects) does nothing. If the related model objects are
2595
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2596
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2597
+	 *
2598
+	 * @param string $relationName
2599
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2600
+	 * @return int how many deleted
2601
+	 * @throws ReflectionException
2602
+	 * @throws InvalidArgumentException
2603
+	 * @throws InvalidInterfaceException
2604
+	 * @throws InvalidDataTypeException
2605
+	 * @throws EE_Error
2606
+	 */
2607
+	public function delete_related($relationName, $query_params = array())
2608
+	{
2609
+		if ($this->ID()) {
2610
+			$count = $this->get_model()->delete_related(
2611
+				$this,
2612
+				$relationName,
2613
+				$query_params
2614
+			);
2615
+		} else {
2616
+			$count = count($this->get_all_from_cache($relationName));
2617
+			$this->clear_cache($relationName, null, true);
2618
+		}
2619
+		return $count;
2620
+	}
2621
+
2622
+
2623
+	/**
2624
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2625
+	 * the current model object's relation to them. If they can't be deleted (because
2626
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2627
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2628
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2629
+	 *
2630
+	 * @param string $relationName
2631
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2632
+	 * @return int how many deleted (including those soft deleted)
2633
+	 * @throws ReflectionException
2634
+	 * @throws InvalidArgumentException
2635
+	 * @throws InvalidInterfaceException
2636
+	 * @throws InvalidDataTypeException
2637
+	 * @throws EE_Error
2638
+	 */
2639
+	public function delete_related_permanently($relationName, $query_params = array())
2640
+	{
2641
+		if ($this->ID()) {
2642
+			$count = $this->get_model()->delete_related_permanently(
2643
+				$this,
2644
+				$relationName,
2645
+				$query_params
2646
+			);
2647
+		} else {
2648
+			$count = count($this->get_all_from_cache($relationName));
2649
+		}
2650
+		$this->clear_cache($relationName, null, true);
2651
+		return $count;
2652
+	}
2653
+
2654
+
2655
+	/**
2656
+	 * is_set
2657
+	 * Just a simple utility function children can use for checking if property exists
2658
+	 *
2659
+	 * @access  public
2660
+	 * @param  string $field_name property to check
2661
+	 * @return bool                              TRUE if existing,FALSE if not.
2662
+	 */
2663
+	public function is_set($field_name)
2664
+	{
2665
+		return isset($this->_fields[ $field_name ]);
2666
+	}
2667
+
2668
+
2669
+	/**
2670
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2671
+	 * EE_Error exception if they don't
2672
+	 *
2673
+	 * @param  mixed (string|array) $properties properties to check
2674
+	 * @throws EE_Error
2675
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2676
+	 */
2677
+	protected function _property_exists($properties)
2678
+	{
2679
+		foreach ((array) $properties as $property_name) {
2680
+			// first make sure this property exists
2681
+			if (! $this->_fields[ $property_name ]) {
2682
+				throw new EE_Error(
2683
+					sprintf(
2684
+						esc_html__(
2685
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2686
+							'event_espresso'
2687
+						),
2688
+						$property_name
2689
+					)
2690
+				);
2691
+			}
2692
+		}
2693
+		return true;
2694
+	}
2695
+
2696
+
2697
+	/**
2698
+	 * This simply returns an array of model fields for this object
2699
+	 *
2700
+	 * @return array
2701
+	 * @throws ReflectionException
2702
+	 * @throws InvalidArgumentException
2703
+	 * @throws InvalidInterfaceException
2704
+	 * @throws InvalidDataTypeException
2705
+	 * @throws EE_Error
2706
+	 */
2707
+	public function model_field_array()
2708
+	{
2709
+		$fields = $this->get_model()->field_settings(false);
2710
+		$properties = array();
2711
+		// remove prepended underscore
2712
+		foreach ($fields as $field_name => $settings) {
2713
+			$properties[ $field_name ] = $this->get($field_name);
2714
+		}
2715
+		return $properties;
2716
+	}
2717
+
2718
+
2719
+	/**
2720
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2721
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2722
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2723
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2724
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2725
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2726
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2727
+	 * and accepts 2 arguments: the object on which the function was called,
2728
+	 * and an array of the original arguments passed to the function.
2729
+	 * Whatever their callback function returns will be returned by this function.
2730
+	 * Example: in functions.php (or in a plugin):
2731
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2732
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2733
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2734
+	 *          return $previousReturnValue.$returnString;
2735
+	 *      }
2736
+	 * require('EE_Answer.class.php');
2737
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2738
+	 * echo $answer->my_callback('monkeys',100);
2739
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2740
+	 *
2741
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2742
+	 * @param array  $args       array of original arguments passed to the function
2743
+	 * @throws EE_Error
2744
+	 * @return mixed whatever the plugin which calls add_filter decides
2745
+	 */
2746
+	public function __call($methodName, $args)
2747
+	{
2748
+		$className = get_class($this);
2749
+		$tagName = "FHEE__{$className}__{$methodName}";
2750
+		if (! has_filter($tagName)) {
2751
+			throw new EE_Error(
2752
+				sprintf(
2753
+					esc_html__(
2754
+						"Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2755
+						'event_espresso'
2756
+					),
2757
+					$methodName,
2758
+					$className,
2759
+					$tagName
2760
+				)
2761
+			);
2762
+		}
2763
+		return apply_filters($tagName, null, $this, $args);
2764
+	}
2765
+
2766
+
2767
+	/**
2768
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2769
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2770
+	 *
2771
+	 * @param string $meta_key
2772
+	 * @param mixed  $meta_value
2773
+	 * @param mixed  $previous_value
2774
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2775
+	 *                  NOTE: if the values haven't changed, returns 0
2776
+	 * @throws InvalidArgumentException
2777
+	 * @throws InvalidInterfaceException
2778
+	 * @throws InvalidDataTypeException
2779
+	 * @throws EE_Error
2780
+	 * @throws ReflectionException
2781
+	 */
2782
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2783
+	{
2784
+		$query_params = array(
2785
+			array(
2786
+				'EXM_key'  => $meta_key,
2787
+				'OBJ_ID'   => $this->ID(),
2788
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2789
+			),
2790
+		);
2791
+		if ($previous_value !== null) {
2792
+			$query_params[0]['EXM_value'] = $meta_value;
2793
+		}
2794
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2795
+		if (! $existing_rows_like_that) {
2796
+			return $this->add_extra_meta($meta_key, $meta_value);
2797
+		}
2798
+		foreach ($existing_rows_like_that as $existing_row) {
2799
+			$existing_row->save(array('EXM_value' => $meta_value));
2800
+		}
2801
+		return count($existing_rows_like_that);
2802
+	}
2803
+
2804
+
2805
+	/**
2806
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2807
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2808
+	 * extra meta row was entered, false if not
2809
+	 *
2810
+	 * @param string  $meta_key
2811
+	 * @param mixed   $meta_value
2812
+	 * @param boolean $unique
2813
+	 * @return boolean
2814
+	 * @throws InvalidArgumentException
2815
+	 * @throws InvalidInterfaceException
2816
+	 * @throws InvalidDataTypeException
2817
+	 * @throws EE_Error
2818
+	 * @throws ReflectionException
2819
+	 * @throws ReflectionException
2820
+	 */
2821
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2822
+	{
2823
+		if ($unique) {
2824
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2825
+				array(
2826
+					array(
2827
+						'EXM_key'  => $meta_key,
2828
+						'OBJ_ID'   => $this->ID(),
2829
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2830
+					),
2831
+				)
2832
+			);
2833
+			if ($existing_extra_meta) {
2834
+				return false;
2835
+			}
2836
+		}
2837
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2838
+			array(
2839
+				'EXM_key'   => $meta_key,
2840
+				'EXM_value' => $meta_value,
2841
+				'OBJ_ID'    => $this->ID(),
2842
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2843
+			)
2844
+		);
2845
+		$new_extra_meta->save();
2846
+		return true;
2847
+	}
2848
+
2849
+
2850
+	/**
2851
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2852
+	 * is specified, only deletes extra meta records with that value.
2853
+	 *
2854
+	 * @param string $meta_key
2855
+	 * @param mixed  $meta_value
2856
+	 * @return int number of extra meta rows deleted
2857
+	 * @throws InvalidArgumentException
2858
+	 * @throws InvalidInterfaceException
2859
+	 * @throws InvalidDataTypeException
2860
+	 * @throws EE_Error
2861
+	 * @throws ReflectionException
2862
+	 */
2863
+	public function delete_extra_meta($meta_key, $meta_value = null)
2864
+	{
2865
+		$query_params = array(
2866
+			array(
2867
+				'EXM_key'  => $meta_key,
2868
+				'OBJ_ID'   => $this->ID(),
2869
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2870
+			),
2871
+		);
2872
+		if ($meta_value !== null) {
2873
+			$query_params[0]['EXM_value'] = $meta_value;
2874
+		}
2875
+		return EEM_Extra_Meta::instance()->delete($query_params);
2876
+	}
2877
+
2878
+
2879
+	/**
2880
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2881
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2882
+	 * You can specify $default is case you haven't found the extra meta
2883
+	 *
2884
+	 * @param string  $meta_key
2885
+	 * @param boolean $single
2886
+	 * @param mixed   $default if we don't find anything, what should we return?
2887
+	 * @return mixed single value if $single; array if ! $single
2888
+	 * @throws ReflectionException
2889
+	 * @throws InvalidArgumentException
2890
+	 * @throws InvalidInterfaceException
2891
+	 * @throws InvalidDataTypeException
2892
+	 * @throws EE_Error
2893
+	 */
2894
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2895
+	{
2896
+		if ($single) {
2897
+			$result = $this->get_first_related(
2898
+				'Extra_Meta',
2899
+				array(array('EXM_key' => $meta_key))
2900
+			);
2901
+			if ($result instanceof EE_Extra_Meta) {
2902
+				return $result->value();
2903
+			}
2904
+		} else {
2905
+			$results = $this->get_many_related(
2906
+				'Extra_Meta',
2907
+				array(array('EXM_key' => $meta_key))
2908
+			);
2909
+			if ($results) {
2910
+				$values = array();
2911
+				foreach ($results as $result) {
2912
+					if ($result instanceof EE_Extra_Meta) {
2913
+						$values[ $result->ID() ] = $result->value();
2914
+					}
2915
+				}
2916
+				return $values;
2917
+			}
2918
+		}
2919
+		// if nothing discovered yet return default.
2920
+		return apply_filters(
2921
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2922
+			$default,
2923
+			$meta_key,
2924
+			$single,
2925
+			$this
2926
+		);
2927
+	}
2928
+
2929
+
2930
+	/**
2931
+	 * Returns a simple array of all the extra meta associated with this model object.
2932
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2933
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2934
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2935
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2936
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2937
+	 * finally the extra meta's value as each sub-value. (eg
2938
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2939
+	 *
2940
+	 * @param boolean $one_of_each_key
2941
+	 * @return array
2942
+	 * @throws ReflectionException
2943
+	 * @throws InvalidArgumentException
2944
+	 * @throws InvalidInterfaceException
2945
+	 * @throws InvalidDataTypeException
2946
+	 * @throws EE_Error
2947
+	 */
2948
+	public function all_extra_meta_array($one_of_each_key = true)
2949
+	{
2950
+		$return_array = array();
2951
+		if ($one_of_each_key) {
2952
+			$extra_meta_objs = $this->get_many_related(
2953
+				'Extra_Meta',
2954
+				array('group_by' => 'EXM_key')
2955
+			);
2956
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2957
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2958
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2959
+				}
2960
+			}
2961
+		} else {
2962
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2963
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2964
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2965
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2966
+						$return_array[ $extra_meta_obj->key() ] = array();
2967
+					}
2968
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2969
+				}
2970
+			}
2971
+		}
2972
+		return $return_array;
2973
+	}
2974
+
2975
+
2976
+	/**
2977
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
2978
+	 *
2979
+	 * @return string
2980
+	 * @throws ReflectionException
2981
+	 * @throws InvalidArgumentException
2982
+	 * @throws InvalidInterfaceException
2983
+	 * @throws InvalidDataTypeException
2984
+	 * @throws EE_Error
2985
+	 */
2986
+	public function name()
2987
+	{
2988
+		// find a field that's not a text field
2989
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2990
+		if ($field_we_can_use) {
2991
+			return $this->get($field_we_can_use->get_name());
2992
+		}
2993
+		$first_few_properties = $this->model_field_array();
2994
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
2995
+		$name_parts = array();
2996
+		foreach ($first_few_properties as $name => $value) {
2997
+			$name_parts[] = "$name:$value";
2998
+		}
2999
+		return implode(',', $name_parts);
3000
+	}
3001
+
3002
+
3003
+	/**
3004
+	 * in_entity_map
3005
+	 * Checks if this model object has been proven to already be in the entity map
3006
+	 *
3007
+	 * @return boolean
3008
+	 * @throws ReflectionException
3009
+	 * @throws InvalidArgumentException
3010
+	 * @throws InvalidInterfaceException
3011
+	 * @throws InvalidDataTypeException
3012
+	 * @throws EE_Error
3013
+	 */
3014
+	public function in_entity_map()
3015
+	{
3016
+		// well, if we looked, did we find it in the entity map?
3017
+		return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3018
+	}
3019
+
3020
+
3021
+	/**
3022
+	 * refresh_from_db
3023
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3024
+	 *
3025
+	 * @throws ReflectionException
3026
+	 * @throws InvalidArgumentException
3027
+	 * @throws InvalidInterfaceException
3028
+	 * @throws InvalidDataTypeException
3029
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3030
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3031
+	 */
3032
+	public function refresh_from_db()
3033
+	{
3034
+		if ($this->ID() && $this->in_entity_map()) {
3035
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
3036
+		} else {
3037
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3038
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
3039
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3040
+			// absolutely nothing in it for this ID
3041
+			if (WP_DEBUG) {
3042
+				throw new EE_Error(
3043
+					sprintf(
3044
+						esc_html__(
3045
+							'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3046
+							'event_espresso'
3047
+						),
3048
+						$this->ID(),
3049
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3050
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3051
+					)
3052
+				);
3053
+			}
3054
+		}
3055
+	}
3056
+
3057
+
3058
+	/**
3059
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3060
+	 * (probably a bad assumption they have made, oh well)
3061
+	 *
3062
+	 * @return string
3063
+	 */
3064
+	public function __toString()
3065
+	{
3066
+		try {
3067
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3068
+		} catch (Exception $e) {
3069
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3070
+			return '';
3071
+		}
3072
+	}
3073
+
3074
+
3075
+	/**
3076
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3077
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3078
+	 * This means if we have made changes to those related model objects, and want to unserialize
3079
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3080
+	 * Instead, those related model objects should be directly serialized and stored.
3081
+	 * Eg, the following won't work:
3082
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3083
+	 * $att = $reg->attendee();
3084
+	 * $att->set( 'ATT_fname', 'Dirk' );
3085
+	 * update_option( 'my_option', serialize( $reg ) );
3086
+	 * //END REQUEST
3087
+	 * //START NEXT REQUEST
3088
+	 * $reg = get_option( 'my_option' );
3089
+	 * $reg->attendee()->save();
3090
+	 * And would need to be replace with:
3091
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3092
+	 * $att = $reg->attendee();
3093
+	 * $att->set( 'ATT_fname', 'Dirk' );
3094
+	 * update_option( 'my_option', serialize( $reg ) );
3095
+	 * //END REQUEST
3096
+	 * //START NEXT REQUEST
3097
+	 * $att = get_option( 'my_option' );
3098
+	 * $att->save();
3099
+	 *
3100
+	 * @return array
3101
+	 * @throws ReflectionException
3102
+	 * @throws InvalidArgumentException
3103
+	 * @throws InvalidInterfaceException
3104
+	 * @throws InvalidDataTypeException
3105
+	 * @throws EE_Error
3106
+	 */
3107
+	public function __sleep()
3108
+	{
3109
+		$model = $this->get_model();
3110
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3111
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3112
+				$classname = 'EE_' . $model->get_this_model_name();
3113
+				if ($this->get_one_from_cache($relation_name) instanceof $classname
3114
+					&& $this->get_one_from_cache($relation_name)->ID()
3115
+				) {
3116
+					$this->clear_cache(
3117
+						$relation_name,
3118
+						$this->get_one_from_cache($relation_name)->ID()
3119
+					);
3120
+				}
3121
+			}
3122
+		}
3123
+		$this->_props_n_values_provided_in_constructor = array();
3124
+		$properties_to_serialize = get_object_vars($this);
3125
+		// don't serialize the model. It's big and that risks recursion
3126
+		unset($properties_to_serialize['_model']);
3127
+		return array_keys($properties_to_serialize);
3128
+	}
3129
+
3130
+
3131
+	/**
3132
+	 * restore _props_n_values_provided_in_constructor
3133
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3134
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3135
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3136
+	 */
3137
+	public function __wakeup()
3138
+	{
3139
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3140
+	}
3141
+
3142
+
3143
+	/**
3144
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3145
+	 * distinct with the clone host instance are also cloned.
3146
+	 */
3147
+	public function __clone()
3148
+	{
3149
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3150
+		foreach ($this->_fields as $field => $value) {
3151
+			if ($value instanceof DateTime) {
3152
+				$this->_fields[ $field ] = clone $value;
3153
+			}
3154
+		}
3155
+	}
3156 3156
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Message_Template_Group.class.php 1 patch
Indentation   +483 added lines, -483 removed lines patch added patch discarded remove patch
@@ -13,487 +13,487 @@
 block discarded – undo
13 13
 class EE_Message_Template_Group extends EE_Soft_Delete_Base_Class
14 14
 {
15 15
 
16
-    /**
17
-     * Extra Meta key prefix for whether a given context for this message tmeplate group is active or not.
18
-     */
19
-    const ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX = 'active_context_';
20
-
21
-    /**
22
-     * @param array  $props_n_values
23
-     * @param string $timezone
24
-     * @return EE_Message_Template_Group|mixed
25
-     * @throws EE_Error
26
-     */
27
-    public static function new_instance($props_n_values = array(), $timezone = '')
28
-    {
29
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone);
30
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone);
31
-    }
32
-
33
-
34
-    /**
35
-     * @param array  $props_n_values
36
-     * @param string $timezone
37
-     * @return EE_Message_Template_Group
38
-     */
39
-    public static function new_instance_from_db($props_n_values = array(), $timezone = '')
40
-    {
41
-        return new self($props_n_values, true, $timezone);
42
-    }
43
-
44
-
45
-    /**
46
-     * @param bool $message_type
47
-     * @throws EE_Error
48
-     */
49
-    public function set_message_type($message_type = false)
50
-    {
51
-        if (! $message_type) {
52
-            throw new EE_Error(esc_html__('Missing required value for the message_type parameter', 'event_espresso'));
53
-        }
54
-        $this->set('MTP_message_type', $message_type);
55
-    }
56
-
57
-
58
-    /**
59
-     * @param bool $messenger
60
-     * @throws EE_Error
61
-     */
62
-    public function set_messenger($messenger = false)
63
-    {
64
-        if (! $messenger) {
65
-            throw new EE_Error(esc_html__('Missing required value for the messenger parameter', 'event_espresso'));
66
-        }
67
-        $this->set('MTP_messenger', $messenger);
68
-    }
69
-
70
-
71
-    /**
72
-     * @param bool $GRP_ID
73
-     * @throws EE_Error
74
-     */
75
-    public function set_group_template_id($GRP_ID = false)
76
-    {
77
-        if (! $GRP_ID) {
78
-            throw new EE_Error(
79
-                esc_html__(
80
-                    'Missing required value for the message template group id',
81
-                    'event_espresso'
82
-                )
83
-            );
84
-        }
85
-        $this->set('GRP_ID', $GRP_ID);
86
-    }
87
-
88
-
89
-    /**
90
-     * get Group ID
91
-     *
92
-     * @access public
93
-     * @return int
94
-     * @throws EE_Error
95
-     */
96
-    public function GRP_ID()
97
-    {
98
-        return $this->get('GRP_ID');
99
-    }
100
-
101
-
102
-    /**
103
-     * get User ID
104
-     *
105
-     * @access public
106
-     * @return int
107
-     * @throws EE_Error
108
-     */
109
-    public function user()
110
-    {
111
-        $user_id = $this->get('MTP_user_id');
112
-        return empty($user_id) ? get_current_user_id() : $user_id;
113
-    }
114
-
115
-
116
-    /**
117
-     * Wrapper for the user function() (preserve backward compat)
118
-     *
119
-     * @since  4.5.0
120
-     * @return int
121
-     * @throws EE_Error
122
-     */
123
-    public function wp_user()
124
-    {
125
-        return $this->user();
126
-    }
127
-
128
-
129
-    /**
130
-     * This simply returns a count of all related events to this message template group
131
-     *
132
-     * @return int
133
-     */
134
-    public function count_events()
135
-    {
136
-        return $this->count_related('Event');
137
-    }
138
-
139
-
140
-    /**
141
-     * returns the name saved in the db for this template
142
-     *
143
-     * @return string
144
-     * @throws EE_Error
145
-     */
146
-    public function name()
147
-    {
148
-        return $this->get('MTP_name');
149
-    }
150
-
151
-
152
-    /**
153
-     * Returns the description saved in the db for this template group
154
-     *
155
-     * @return string
156
-     * @throws EE_Error
157
-     */
158
-    public function description()
159
-    {
160
-        return $this->get('MTP_description');
161
-    }
162
-
163
-
164
-    /**
165
-     * returns all related EE_Message_Template objects
166
-     *
167
-     * @param  array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
168
-     * @return EE_Message_Template[]
169
-     * @throws EE_Error
170
-     */
171
-    public function message_templates($query_params = array())
172
-    {
173
-        return $this->get_many_related('Message_Template', $query_params);
174
-    }
175
-
176
-
177
-    /**
178
-     * get Message Messenger
179
-     *
180
-     * @access public
181
-     * @return string
182
-     * @throws EE_Error
183
-     */
184
-    public function messenger()
185
-    {
186
-        return $this->get('MTP_messenger');
187
-    }
188
-
189
-
190
-    /**
191
-     * get Message Messenger OBJECT
192
-     * If an attempt to get the corresponding messenger object fails, then we set this message
193
-     * template group to inactive, and save to db.  Then return null so client code can handle
194
-     * appropriately.
195
-     *
196
-     * @return EE_messenger
197
-     * @throws EE_Error
198
-     */
199
-    public function messenger_obj()
200
-    {
201
-        $messenger = $this->messenger();
202
-        try {
203
-            $messenger = EEH_MSG_Template::messenger_obj($messenger);
204
-        } catch (EE_Error $e) {
205
-            // if an exception was thrown then let's deactivate this message template group because it means there is no
206
-            // class for this messenger in this group.
207
-            $this->set('MTP_is_active', false);
208
-            $this->save();
209
-            return null;
210
-        }
211
-        return $messenger;
212
-    }
213
-
214
-
215
-    /**
216
-     * get Message Type
217
-     *
218
-     * @access public
219
-     * @return string
220
-     * @throws EE_Error
221
-     */
222
-    public function message_type()
223
-    {
224
-        return $this->get('MTP_message_type');
225
-    }
226
-
227
-
228
-    /**
229
-     * get Message type OBJECT
230
-     * If an attempt to get the corresponding message type object fails, then we set this message
231
-     * template group to inactive, and save to db.  Then return null so client code can handle
232
-     * appropriately.
233
-     *
234
-     * @throws EE_Error
235
-     * @return EE_message_type|false if exception thrown.
236
-     */
237
-    public function message_type_obj()
238
-    {
239
-        $message_type = $this->message_type();
240
-        try {
241
-            $message_type = EEH_MSG_Template::message_type_obj($message_type);
242
-        } catch (EE_Error $e) {
243
-            // if an exception was thrown then let's deactivate this message template group because it means there is no
244
-            // class for the message type in this group.
245
-            $this->set('MTP_is_active', false);
246
-            $this->save();
247
-            return null;
248
-        }
249
-        return $message_type;
250
-    }
251
-
252
-
253
-    /**
254
-     * @return array
255
-     * @throws EE_Error
256
-     */
257
-    public function contexts_config()
258
-    {
259
-        return $this->message_type_obj()->get_contexts();
260
-    }
261
-
262
-
263
-    /**
264
-     * This returns the context_label for contexts as set in the message type object
265
-     * Note this is an array with singular and plural keys
266
-     *
267
-     * @access public
268
-     * @return array labels for "context"
269
-     * @throws EE_Error
270
-     */
271
-    public function context_label()
272
-    {
273
-        $obj = $this->message_type_obj();
274
-        return $obj->get_context_label();
275
-    }
276
-
277
-
278
-    /**
279
-     * This returns an array of EE_Message_Template objects indexed by context and field.
280
-     *
281
-     * @return array ()
282
-     * @throws EE_Error
283
-     */
284
-    public function context_templates()
285
-    {
286
-        $mtps_arr = array();
287
-        $mtps = $this->get_many_related('Message_Template');
288
-        if (empty($mtps)) {
289
-            return array();
290
-        }
291
-        // note contexts could have CHECKBOX fields per context. So we return the objects indexed by context AND field.
292
-        foreach ($mtps as $mtp) {
293
-            $mtps_arr[ $mtp->get('MTP_context') ][ $mtp->get('MTP_template_field') ] = $mtp;
294
-        }
295
-        return $mtps_arr;
296
-    }
297
-
298
-
299
-    /**
300
-     * this returns if the template group this template belongs to is global
301
-     *
302
-     * @return bool true if it is, false if it isn't
303
-     * @throws EE_Error
304
-     */
305
-    public function is_global()
306
-    {
307
-        return $this->get('MTP_is_global');
308
-    }
309
-
310
-
311
-    /**
312
-     * this returns if the template group this template belongs to is active (i.e. turned "on" or not)
313
-     *
314
-     * @return bool true if it is, false if it isn't
315
-     * @throws EE_Error
316
-     */
317
-    public function is_active()
318
-    {
319
-        return $this->get('MTP_is_active');
320
-    }
321
-
322
-
323
-    /**
324
-     * This will return an array of shortcodes => labels from the messenger and message_type objects associated with
325
-     * this template.
326
-     *
327
-     * @since 4.3.0
328
-     * @uses  EEH_MSG_Template::get_shortcodes()
329
-     * @param string $context what context we're going to return shortcodes for
330
-     * @param array  $fields  what fields we're returning valid shortcodes for.  If empty then we assume all fields are
331
-     *                        to be returned.
332
-     * @param bool   $merged  If TRUE then we don't return shortcodes indexed by field but instead an array of the
333
-     *                        unique shortcodes for all the given (or all) fields.
334
-     * @return mixed (array|bool) an array of shortcodes in the format array( '[shortcode] => 'label') OR FALSE if no
335
-     *                        shortcodes found.
336
-     * @throws EE_Error
337
-     */
338
-    public function get_shortcodes($context, $fields = array(), $merged = false)
339
-    {
340
-        $messenger = $this->messenger();
341
-        $message_type = $this->message_type();
342
-        return EEH_MSG_Template::get_shortcodes($message_type, $messenger, $fields, $context, $merged);
343
-    }
344
-
345
-
346
-    /**
347
-     * This just gets the template pack name assigned to this message template group.  If it's not set, then we just
348
-     * use the default template pack.
349
-     *
350
-     * @since 4.5.0
351
-     * @return string
352
-     * @throws EE_Error
353
-     */
354
-    public function get_template_pack_name()
355
-    {
356
-        return $this->get_extra_meta('MTP_template_pack', true, 'default');
357
-    }
358
-
359
-
360
-    /**
361
-     * This returns the specific template pack object referenced by the template pack name attached to this message
362
-     * template group.  If no template pack is assigned then the default template pack is retrieved.
363
-     *
364
-     * @since 4.5.0
365
-     * @return EE_Messages_Template_Pack
366
-     * @throws EE_Error
367
-     * @throws InvalidArgumentException
368
-     * @throws ReflectionException
369
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
370
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
371
-     */
372
-    public function get_template_pack()
373
-    {
374
-        $pack_name = $this->get_template_pack_name();
375
-        EE_Registry::instance()->load_helper('MSG_Template');
376
-        return EEH_MSG_Template::get_template_pack($pack_name);
377
-    }
378
-
379
-
380
-    /**
381
-     * This retrieves the template variation assigned to this message template group.  If it's not set, then we just
382
-     * use the default template variation.
383
-     *
384
-     * @since 4.5.0
385
-     * @return string
386
-     * @throws EE_Error
387
-     */
388
-    public function get_template_pack_variation()
389
-    {
390
-        return $this->get_extra_meta('MTP_variation', true, 'default');
391
-    }
392
-
393
-
394
-    /**
395
-     * This just sets the template pack name attached to this message template group.
396
-     *
397
-     * @since 4.5.0
398
-     * @param string $template_pack_name What message template pack is assigned.
399
-     * @return int
400
-     * @throws EE_Error
401
-     */
402
-    public function set_template_pack_name($template_pack_name)
403
-    {
404
-        return $this->update_extra_meta('MTP_template_pack', $template_pack_name);
405
-    }
406
-
407
-
408
-    /**
409
-     * This just sets the template pack variation attached to this message template group.
410
-     *
411
-     * @since 4.5.0
412
-     * @param string $variation What variation is being set on the message template group.
413
-     * @return int
414
-     * @throws EE_Error
415
-     */
416
-    public function set_template_pack_variation($variation)
417
-    {
418
-        return $this->update_extra_meta('MTP_variation', $variation);
419
-    }
420
-
421
-
422
-    /**
423
-     * Deactivates the given context.
424
-     *
425
-     * @param $context
426
-     * @return bool|int
427
-     * @throws EE_Error
428
-     * @throws InvalidIdentifierException
429
-     */
430
-    public function deactivate_context($context)
431
-    {
432
-        $this->validate_context($context);
433
-        return $this->update_extra_meta(self::ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX . $context, false);
434
-    }
435
-
436
-
437
-    /**
438
-     * Activates the given context.
439
-     *
440
-     * @param $context
441
-     * @return bool|int
442
-     * @throws EE_Error
443
-     * @throws InvalidIdentifierException
444
-     */
445
-    public function activate_context($context)
446
-    {
447
-        $this->validate_context($context);
448
-        return $this->update_extra_meta(self::ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX . $context, true);
449
-    }
450
-
451
-
452
-    /**
453
-     * Returns whether the context is active or not.
454
-     * Note, this will default to true if the extra meta record doesn't exist.
455
-     * Also, this does NOT account for whether the "To" field is empty or not. Some messengers may allow the "To" field
456
-     * to be empty (@see EE_Messenger::allow_empty_to_field()) so an empty "To" field is not always an indicator of
457
-     * whether a context is "active" or not.
458
-     *
459
-     * @param $context
460
-     * @return bool
461
-     * @throws EE_Error
462
-     * @throws InvalidIdentifierException
463
-     */
464
-    public function is_context_active($context)
465
-    {
466
-        $this->validate_context($context);
467
-        return filter_var(
468
-            $this->get_extra_meta(self::ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX . $context, true, true),
469
-            FILTER_VALIDATE_BOOLEAN
470
-        );
471
-    }
472
-
473
-
474
-    /**
475
-     * Validates the incoming context to verify it matches a registered context for the related message type.
476
-     *
477
-     * @param string $context
478
-     * @throws EE_Error
479
-     * @throws InvalidIdentifierException
480
-     */
481
-    public function validate_context($context)
482
-    {
483
-        $contexts = $this->contexts_config();
484
-        if (! isset($contexts[ $context ])) {
485
-            throw new InvalidIdentifierException(
486
-                '',
487
-                '',
488
-                sprintf(
489
-                    esc_html__(
490
-                        'An invalid string identifying a context was provided.  "%1$s" was received, and one of "%2$s" was expected.',
491
-                        'event_espresso'
492
-                    ),
493
-                    $context,
494
-                    implode(',', array_keys($contexts))
495
-                )
496
-            );
497
-        }
498
-    }
16
+	/**
17
+	 * Extra Meta key prefix for whether a given context for this message tmeplate group is active or not.
18
+	 */
19
+	const ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX = 'active_context_';
20
+
21
+	/**
22
+	 * @param array  $props_n_values
23
+	 * @param string $timezone
24
+	 * @return EE_Message_Template_Group|mixed
25
+	 * @throws EE_Error
26
+	 */
27
+	public static function new_instance($props_n_values = array(), $timezone = '')
28
+	{
29
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone);
30
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone);
31
+	}
32
+
33
+
34
+	/**
35
+	 * @param array  $props_n_values
36
+	 * @param string $timezone
37
+	 * @return EE_Message_Template_Group
38
+	 */
39
+	public static function new_instance_from_db($props_n_values = array(), $timezone = '')
40
+	{
41
+		return new self($props_n_values, true, $timezone);
42
+	}
43
+
44
+
45
+	/**
46
+	 * @param bool $message_type
47
+	 * @throws EE_Error
48
+	 */
49
+	public function set_message_type($message_type = false)
50
+	{
51
+		if (! $message_type) {
52
+			throw new EE_Error(esc_html__('Missing required value for the message_type parameter', 'event_espresso'));
53
+		}
54
+		$this->set('MTP_message_type', $message_type);
55
+	}
56
+
57
+
58
+	/**
59
+	 * @param bool $messenger
60
+	 * @throws EE_Error
61
+	 */
62
+	public function set_messenger($messenger = false)
63
+	{
64
+		if (! $messenger) {
65
+			throw new EE_Error(esc_html__('Missing required value for the messenger parameter', 'event_espresso'));
66
+		}
67
+		$this->set('MTP_messenger', $messenger);
68
+	}
69
+
70
+
71
+	/**
72
+	 * @param bool $GRP_ID
73
+	 * @throws EE_Error
74
+	 */
75
+	public function set_group_template_id($GRP_ID = false)
76
+	{
77
+		if (! $GRP_ID) {
78
+			throw new EE_Error(
79
+				esc_html__(
80
+					'Missing required value for the message template group id',
81
+					'event_espresso'
82
+				)
83
+			);
84
+		}
85
+		$this->set('GRP_ID', $GRP_ID);
86
+	}
87
+
88
+
89
+	/**
90
+	 * get Group ID
91
+	 *
92
+	 * @access public
93
+	 * @return int
94
+	 * @throws EE_Error
95
+	 */
96
+	public function GRP_ID()
97
+	{
98
+		return $this->get('GRP_ID');
99
+	}
100
+
101
+
102
+	/**
103
+	 * get User ID
104
+	 *
105
+	 * @access public
106
+	 * @return int
107
+	 * @throws EE_Error
108
+	 */
109
+	public function user()
110
+	{
111
+		$user_id = $this->get('MTP_user_id');
112
+		return empty($user_id) ? get_current_user_id() : $user_id;
113
+	}
114
+
115
+
116
+	/**
117
+	 * Wrapper for the user function() (preserve backward compat)
118
+	 *
119
+	 * @since  4.5.0
120
+	 * @return int
121
+	 * @throws EE_Error
122
+	 */
123
+	public function wp_user()
124
+	{
125
+		return $this->user();
126
+	}
127
+
128
+
129
+	/**
130
+	 * This simply returns a count of all related events to this message template group
131
+	 *
132
+	 * @return int
133
+	 */
134
+	public function count_events()
135
+	{
136
+		return $this->count_related('Event');
137
+	}
138
+
139
+
140
+	/**
141
+	 * returns the name saved in the db for this template
142
+	 *
143
+	 * @return string
144
+	 * @throws EE_Error
145
+	 */
146
+	public function name()
147
+	{
148
+		return $this->get('MTP_name');
149
+	}
150
+
151
+
152
+	/**
153
+	 * Returns the description saved in the db for this template group
154
+	 *
155
+	 * @return string
156
+	 * @throws EE_Error
157
+	 */
158
+	public function description()
159
+	{
160
+		return $this->get('MTP_description');
161
+	}
162
+
163
+
164
+	/**
165
+	 * returns all related EE_Message_Template objects
166
+	 *
167
+	 * @param  array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
168
+	 * @return EE_Message_Template[]
169
+	 * @throws EE_Error
170
+	 */
171
+	public function message_templates($query_params = array())
172
+	{
173
+		return $this->get_many_related('Message_Template', $query_params);
174
+	}
175
+
176
+
177
+	/**
178
+	 * get Message Messenger
179
+	 *
180
+	 * @access public
181
+	 * @return string
182
+	 * @throws EE_Error
183
+	 */
184
+	public function messenger()
185
+	{
186
+		return $this->get('MTP_messenger');
187
+	}
188
+
189
+
190
+	/**
191
+	 * get Message Messenger OBJECT
192
+	 * If an attempt to get the corresponding messenger object fails, then we set this message
193
+	 * template group to inactive, and save to db.  Then return null so client code can handle
194
+	 * appropriately.
195
+	 *
196
+	 * @return EE_messenger
197
+	 * @throws EE_Error
198
+	 */
199
+	public function messenger_obj()
200
+	{
201
+		$messenger = $this->messenger();
202
+		try {
203
+			$messenger = EEH_MSG_Template::messenger_obj($messenger);
204
+		} catch (EE_Error $e) {
205
+			// if an exception was thrown then let's deactivate this message template group because it means there is no
206
+			// class for this messenger in this group.
207
+			$this->set('MTP_is_active', false);
208
+			$this->save();
209
+			return null;
210
+		}
211
+		return $messenger;
212
+	}
213
+
214
+
215
+	/**
216
+	 * get Message Type
217
+	 *
218
+	 * @access public
219
+	 * @return string
220
+	 * @throws EE_Error
221
+	 */
222
+	public function message_type()
223
+	{
224
+		return $this->get('MTP_message_type');
225
+	}
226
+
227
+
228
+	/**
229
+	 * get Message type OBJECT
230
+	 * If an attempt to get the corresponding message type object fails, then we set this message
231
+	 * template group to inactive, and save to db.  Then return null so client code can handle
232
+	 * appropriately.
233
+	 *
234
+	 * @throws EE_Error
235
+	 * @return EE_message_type|false if exception thrown.
236
+	 */
237
+	public function message_type_obj()
238
+	{
239
+		$message_type = $this->message_type();
240
+		try {
241
+			$message_type = EEH_MSG_Template::message_type_obj($message_type);
242
+		} catch (EE_Error $e) {
243
+			// if an exception was thrown then let's deactivate this message template group because it means there is no
244
+			// class for the message type in this group.
245
+			$this->set('MTP_is_active', false);
246
+			$this->save();
247
+			return null;
248
+		}
249
+		return $message_type;
250
+	}
251
+
252
+
253
+	/**
254
+	 * @return array
255
+	 * @throws EE_Error
256
+	 */
257
+	public function contexts_config()
258
+	{
259
+		return $this->message_type_obj()->get_contexts();
260
+	}
261
+
262
+
263
+	/**
264
+	 * This returns the context_label for contexts as set in the message type object
265
+	 * Note this is an array with singular and plural keys
266
+	 *
267
+	 * @access public
268
+	 * @return array labels for "context"
269
+	 * @throws EE_Error
270
+	 */
271
+	public function context_label()
272
+	{
273
+		$obj = $this->message_type_obj();
274
+		return $obj->get_context_label();
275
+	}
276
+
277
+
278
+	/**
279
+	 * This returns an array of EE_Message_Template objects indexed by context and field.
280
+	 *
281
+	 * @return array ()
282
+	 * @throws EE_Error
283
+	 */
284
+	public function context_templates()
285
+	{
286
+		$mtps_arr = array();
287
+		$mtps = $this->get_many_related('Message_Template');
288
+		if (empty($mtps)) {
289
+			return array();
290
+		}
291
+		// note contexts could have CHECKBOX fields per context. So we return the objects indexed by context AND field.
292
+		foreach ($mtps as $mtp) {
293
+			$mtps_arr[ $mtp->get('MTP_context') ][ $mtp->get('MTP_template_field') ] = $mtp;
294
+		}
295
+		return $mtps_arr;
296
+	}
297
+
298
+
299
+	/**
300
+	 * this returns if the template group this template belongs to is global
301
+	 *
302
+	 * @return bool true if it is, false if it isn't
303
+	 * @throws EE_Error
304
+	 */
305
+	public function is_global()
306
+	{
307
+		return $this->get('MTP_is_global');
308
+	}
309
+
310
+
311
+	/**
312
+	 * this returns if the template group this template belongs to is active (i.e. turned "on" or not)
313
+	 *
314
+	 * @return bool true if it is, false if it isn't
315
+	 * @throws EE_Error
316
+	 */
317
+	public function is_active()
318
+	{
319
+		return $this->get('MTP_is_active');
320
+	}
321
+
322
+
323
+	/**
324
+	 * This will return an array of shortcodes => labels from the messenger and message_type objects associated with
325
+	 * this template.
326
+	 *
327
+	 * @since 4.3.0
328
+	 * @uses  EEH_MSG_Template::get_shortcodes()
329
+	 * @param string $context what context we're going to return shortcodes for
330
+	 * @param array  $fields  what fields we're returning valid shortcodes for.  If empty then we assume all fields are
331
+	 *                        to be returned.
332
+	 * @param bool   $merged  If TRUE then we don't return shortcodes indexed by field but instead an array of the
333
+	 *                        unique shortcodes for all the given (or all) fields.
334
+	 * @return mixed (array|bool) an array of shortcodes in the format array( '[shortcode] => 'label') OR FALSE if no
335
+	 *                        shortcodes found.
336
+	 * @throws EE_Error
337
+	 */
338
+	public function get_shortcodes($context, $fields = array(), $merged = false)
339
+	{
340
+		$messenger = $this->messenger();
341
+		$message_type = $this->message_type();
342
+		return EEH_MSG_Template::get_shortcodes($message_type, $messenger, $fields, $context, $merged);
343
+	}
344
+
345
+
346
+	/**
347
+	 * This just gets the template pack name assigned to this message template group.  If it's not set, then we just
348
+	 * use the default template pack.
349
+	 *
350
+	 * @since 4.5.0
351
+	 * @return string
352
+	 * @throws EE_Error
353
+	 */
354
+	public function get_template_pack_name()
355
+	{
356
+		return $this->get_extra_meta('MTP_template_pack', true, 'default');
357
+	}
358
+
359
+
360
+	/**
361
+	 * This returns the specific template pack object referenced by the template pack name attached to this message
362
+	 * template group.  If no template pack is assigned then the default template pack is retrieved.
363
+	 *
364
+	 * @since 4.5.0
365
+	 * @return EE_Messages_Template_Pack
366
+	 * @throws EE_Error
367
+	 * @throws InvalidArgumentException
368
+	 * @throws ReflectionException
369
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
370
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
371
+	 */
372
+	public function get_template_pack()
373
+	{
374
+		$pack_name = $this->get_template_pack_name();
375
+		EE_Registry::instance()->load_helper('MSG_Template');
376
+		return EEH_MSG_Template::get_template_pack($pack_name);
377
+	}
378
+
379
+
380
+	/**
381
+	 * This retrieves the template variation assigned to this message template group.  If it's not set, then we just
382
+	 * use the default template variation.
383
+	 *
384
+	 * @since 4.5.0
385
+	 * @return string
386
+	 * @throws EE_Error
387
+	 */
388
+	public function get_template_pack_variation()
389
+	{
390
+		return $this->get_extra_meta('MTP_variation', true, 'default');
391
+	}
392
+
393
+
394
+	/**
395
+	 * This just sets the template pack name attached to this message template group.
396
+	 *
397
+	 * @since 4.5.0
398
+	 * @param string $template_pack_name What message template pack is assigned.
399
+	 * @return int
400
+	 * @throws EE_Error
401
+	 */
402
+	public function set_template_pack_name($template_pack_name)
403
+	{
404
+		return $this->update_extra_meta('MTP_template_pack', $template_pack_name);
405
+	}
406
+
407
+
408
+	/**
409
+	 * This just sets the template pack variation attached to this message template group.
410
+	 *
411
+	 * @since 4.5.0
412
+	 * @param string $variation What variation is being set on the message template group.
413
+	 * @return int
414
+	 * @throws EE_Error
415
+	 */
416
+	public function set_template_pack_variation($variation)
417
+	{
418
+		return $this->update_extra_meta('MTP_variation', $variation);
419
+	}
420
+
421
+
422
+	/**
423
+	 * Deactivates the given context.
424
+	 *
425
+	 * @param $context
426
+	 * @return bool|int
427
+	 * @throws EE_Error
428
+	 * @throws InvalidIdentifierException
429
+	 */
430
+	public function deactivate_context($context)
431
+	{
432
+		$this->validate_context($context);
433
+		return $this->update_extra_meta(self::ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX . $context, false);
434
+	}
435
+
436
+
437
+	/**
438
+	 * Activates the given context.
439
+	 *
440
+	 * @param $context
441
+	 * @return bool|int
442
+	 * @throws EE_Error
443
+	 * @throws InvalidIdentifierException
444
+	 */
445
+	public function activate_context($context)
446
+	{
447
+		$this->validate_context($context);
448
+		return $this->update_extra_meta(self::ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX . $context, true);
449
+	}
450
+
451
+
452
+	/**
453
+	 * Returns whether the context is active or not.
454
+	 * Note, this will default to true if the extra meta record doesn't exist.
455
+	 * Also, this does NOT account for whether the "To" field is empty or not. Some messengers may allow the "To" field
456
+	 * to be empty (@see EE_Messenger::allow_empty_to_field()) so an empty "To" field is not always an indicator of
457
+	 * whether a context is "active" or not.
458
+	 *
459
+	 * @param $context
460
+	 * @return bool
461
+	 * @throws EE_Error
462
+	 * @throws InvalidIdentifierException
463
+	 */
464
+	public function is_context_active($context)
465
+	{
466
+		$this->validate_context($context);
467
+		return filter_var(
468
+			$this->get_extra_meta(self::ACTIVE_CONTEXT_RECORD_META_KEY_PREFIX . $context, true, true),
469
+			FILTER_VALIDATE_BOOLEAN
470
+		);
471
+	}
472
+
473
+
474
+	/**
475
+	 * Validates the incoming context to verify it matches a registered context for the related message type.
476
+	 *
477
+	 * @param string $context
478
+	 * @throws EE_Error
479
+	 * @throws InvalidIdentifierException
480
+	 */
481
+	public function validate_context($context)
482
+	{
483
+		$contexts = $this->contexts_config();
484
+		if (! isset($contexts[ $context ])) {
485
+			throw new InvalidIdentifierException(
486
+				'',
487
+				'',
488
+				sprintf(
489
+					esc_html__(
490
+						'An invalid string identifying a context was provided.  "%1$s" was received, and one of "%2$s" was expected.',
491
+						'event_espresso'
492
+					),
493
+					$context,
494
+					implode(',', array_keys($contexts))
495
+				)
496
+			);
497
+		}
498
+	}
499 499
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Transaction.class.php 1 patch
Indentation   +1390 added lines, -1390 removed lines patch added patch discarded remove patch
@@ -13,1394 +13,1394 @@
 block discarded – undo
13 13
 class EE_Transaction extends EE_Base_Class implements EEI_Transaction
14 14
 {
15 15
 
16
-    /**
17
-     * The length of time in seconds that a lock is applied before being considered expired.
18
-     * It is not long because a transaction should only be locked for the duration of the request that locked it
19
-     */
20
-    const LOCK_EXPIRATION = 2;
21
-
22
-    /**
23
-     * txn status upon initial construction.
24
-     *
25
-     * @var string
26
-     */
27
-    protected $_old_txn_status;
28
-
29
-
30
-    /**
31
-     * @param array  $props_n_values          incoming values
32
-     * @param string $timezone                incoming timezone
33
-     *                                        (if not set the timezone set for the website will be used.)
34
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
-     *                                        date_format and the second value is the time format
36
-     * @return EE_Transaction
37
-     * @throws EE_Error
38
-     */
39
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
40
-    {
41
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
42
-        $txn = $has_object
43
-            ? $has_object
44
-            : new self($props_n_values, false, $timezone, $date_formats);
45
-        if (! $has_object) {
46
-            $txn->set_old_txn_status($txn->status_ID());
47
-        }
48
-        return $txn;
49
-    }
50
-
51
-
52
-    /**
53
-     * @param array  $props_n_values  incoming values from the database
54
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
55
-     *                                the website will be used.
56
-     * @return EE_Transaction
57
-     * @throws EE_Error
58
-     */
59
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
60
-    {
61
-        $txn = new self($props_n_values, true, $timezone);
62
-        $txn->set_old_txn_status($txn->status_ID());
63
-        return $txn;
64
-    }
65
-
66
-
67
-    /**
68
-     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
69
-     * If a lock has already been set, then we will attempt to remove it in case it has expired.
70
-     * If that also fails, then an exception is thrown.
71
-     *
72
-     * @throws EE_Error
73
-     */
74
-    public function lock()
75
-    {
76
-        // attempt to set lock, but if that fails...
77
-        if (! $this->add_extra_meta('lock', time(), true)) {
78
-            // then attempt to remove the lock in case it is expired
79
-            if ($this->_remove_expired_lock()) {
80
-                // if removal was successful, then try setting lock again
81
-                $this->lock();
82
-            } else {
83
-                // but if the lock can not be removed, then throw an exception
84
-                throw new EE_Error(
85
-                    sprintf(
86
-                        __(
87
-                            'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
88
-                            'event_espresso'
89
-                        ),
90
-                        $this->ID()
91
-                    )
92
-                );
93
-            }
94
-        }
95
-    }
96
-
97
-
98
-    /**
99
-     * removes transaction lock applied in EE_Transaction::lock()
100
-     *
101
-     * @return int
102
-     * @throws EE_Error
103
-     */
104
-    public function unlock()
105
-    {
106
-        return $this->delete_extra_meta('lock');
107
-    }
108
-
109
-
110
-    /**
111
-     * Decides whether or not now is the right time to update the transaction.
112
-     * This is useful because we don't always know if it is safe to update the transaction
113
-     * and its related data. why?
114
-     * because it's possible that the transaction is being used in another
115
-     * request and could overwrite anything we save.
116
-     * So we want to only update the txn once we know that won't happen.
117
-     * We also check that the lock isn't expired, and remove it if it is
118
-     *
119
-     * @return boolean
120
-     * @throws EE_Error
121
-     */
122
-    public function is_locked()
123
-    {
124
-        // if TXN is not locked, then return false immediately
125
-        if (! $this->_get_lock()) {
126
-            return false;
127
-        }
128
-        // if not, then let's try and remove the lock in case it's expired...
129
-        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
130
-        // and a positive number if the lock was removed (ie: number of locks deleted),
131
-        // so we need to return the opposite
132
-        return ! $this->_remove_expired_lock() ? true : false;
133
-    }
134
-
135
-
136
-    /**
137
-     * Gets the meta field indicating that this TXN is locked
138
-     *
139
-     * @return int
140
-     * @throws EE_Error
141
-     */
142
-    protected function _get_lock()
143
-    {
144
-        return (int) $this->get_extra_meta('lock', true, 0);
145
-    }
146
-
147
-
148
-    /**
149
-     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
150
-     *
151
-     * @return int
152
-     * @throws EE_Error
153
-     */
154
-    protected function _remove_expired_lock()
155
-    {
156
-        $locked = $this->_get_lock();
157
-        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
158
-            return $this->unlock();
159
-        }
160
-        return 0;
161
-    }
162
-
163
-
164
-    /**
165
-     * Set transaction total
166
-     *
167
-     * @param float $total total value of transaction
168
-     * @throws EE_Error
169
-     */
170
-    public function set_total($total = 0.00)
171
-    {
172
-        $this->set('TXN_total', (float) $total);
173
-    }
174
-
175
-
176
-    /**
177
-     * Set Total Amount Paid to Date
178
-     *
179
-     * @param float $total_paid total amount paid to date (sum of all payments)
180
-     * @throws EE_Error
181
-     */
182
-    public function set_paid($total_paid = 0.00)
183
-    {
184
-        $this->set('TXN_paid', (float) $total_paid);
185
-    }
186
-
187
-
188
-    /**
189
-     * Set transaction status
190
-     *
191
-     * @param string $status        whether the transaction is open, declined, accepted,
192
-     *                              or any number of custom values that can be set
193
-     * @throws EE_Error
194
-     */
195
-    public function set_status($status = '')
196
-    {
197
-        $this->set('STS_ID', $status);
198
-    }
199
-
200
-
201
-    /**
202
-     * Set hash salt
203
-     *
204
-     * @param string $hash_salt required for some payment gateways
205
-     * @throws EE_Error
206
-     */
207
-    public function set_hash_salt($hash_salt = '')
208
-    {
209
-        $this->set('TXN_hash_salt', $hash_salt);
210
-    }
211
-
212
-
213
-    /**
214
-     * Sets TXN_reg_steps array
215
-     *
216
-     * @param array $txn_reg_steps
217
-     * @throws EE_Error
218
-     */
219
-    public function set_reg_steps(array $txn_reg_steps)
220
-    {
221
-        $this->set('TXN_reg_steps', $txn_reg_steps);
222
-    }
223
-
224
-
225
-    /**
226
-     * Gets TXN_reg_steps
227
-     *
228
-     * @return array
229
-     * @throws EE_Error
230
-     */
231
-    public function reg_steps()
232
-    {
233
-        $TXN_reg_steps = $this->get('TXN_reg_steps');
234
-        return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
235
-    }
236
-
237
-
238
-    /**
239
-     * @return string of transaction's total cost, with currency symbol and decimal
240
-     * @throws EE_Error
241
-     */
242
-    public function pretty_total()
243
-    {
244
-        return $this->get_pretty('TXN_total');
245
-    }
246
-
247
-
248
-    /**
249
-     * Gets the amount paid in a pretty string (formatted and with currency symbol)
250
-     *
251
-     * @return string
252
-     * @throws EE_Error
253
-     */
254
-    public function pretty_paid()
255
-    {
256
-        return $this->get_pretty('TXN_paid');
257
-    }
258
-
259
-
260
-    /**
261
-     * calculate the amount remaining for this transaction and return;
262
-     *
263
-     * @return float amount remaining
264
-     * @throws EE_Error
265
-     */
266
-    public function remaining()
267
-    {
268
-        return $this->total() - $this->paid();
269
-    }
270
-
271
-
272
-    /**
273
-     * get Transaction Total
274
-     *
275
-     * @return float
276
-     * @throws EE_Error
277
-     */
278
-    public function total()
279
-    {
280
-        return (float) $this->get('TXN_total');
281
-    }
282
-
283
-
284
-    /**
285
-     * get Total Amount Paid to Date
286
-     *
287
-     * @return float
288
-     * @throws EE_Error
289
-     */
290
-    public function paid()
291
-    {
292
-        return (float) $this->get('TXN_paid');
293
-    }
294
-
295
-
296
-    /**
297
-     * @throws EE_Error
298
-     */
299
-    public function get_cart_session()
300
-    {
301
-        $session_data = (array) $this->get('TXN_session_data');
302
-        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
303
-            ? $session_data['cart']
304
-            : null;
305
-    }
306
-
307
-
308
-    /**
309
-     * get Transaction session data
310
-     *
311
-     * @throws EE_Error
312
-     */
313
-    public function session_data()
314
-    {
315
-        $session_data = $this->get('TXN_session_data');
316
-        if (empty($session_data)) {
317
-            $session_data = array(
318
-                'id'            => null,
319
-                'user_id'       => null,
320
-                'ip_address'    => null,
321
-                'user_agent'    => null,
322
-                'init_access'   => null,
323
-                'last_access'   => null,
324
-                'pages_visited' => array(),
325
-            );
326
-        }
327
-        return $session_data;
328
-    }
329
-
330
-
331
-    /**
332
-     * Set session data within the TXN object
333
-     *
334
-     * @param EE_Session|array $session_data
335
-     * @throws EE_Error
336
-     */
337
-    public function set_txn_session_data($session_data)
338
-    {
339
-        if ($session_data instanceof EE_Session) {
340
-            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
341
-        } else {
342
-            $this->set('TXN_session_data', $session_data);
343
-        }
344
-    }
345
-
346
-
347
-    /**
348
-     * get Transaction hash salt
349
-     *
350
-     * @throws EE_Error
351
-     */
352
-    public function hash_salt_()
353
-    {
354
-        return $this->get('TXN_hash_salt');
355
-    }
356
-
357
-
358
-    /**
359
-     * Returns the transaction datetime as either:
360
-     *            - unix timestamp format ($format = false, $gmt = true)
361
-     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
362
-     *              has no affect with this option)), this also may include a timezone abbreviation if the
363
-     *              set timezone in this class differs from what the timezone is on the blog.
364
-     *            - formatted date string including the UTC (timezone) offset (default).
365
-     *
366
-     * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
367
-     * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
368
-     *                          or no UTC offset applied
369
-     * @return string | int
370
-     * @throws EE_Error
371
-     */
372
-    public function datetime($format = false, $gmt = false)
373
-    {
374
-        if ($format) {
375
-            return $this->get_pretty('TXN_timestamp');
376
-        }
377
-        if ($gmt) {
378
-            return $this->get_raw('TXN_timestamp');
379
-        }
380
-        return $this->get('TXN_timestamp');
381
-    }
382
-
383
-
384
-    /**
385
-     * Gets registrations on this transaction
386
-     *
387
-     * @param array   $query_params array of query parameters
388
-     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
389
-     * @return EE_Base_Class[]|EE_Registration[]
390
-     * @throws EE_Error
391
-     */
392
-    public function registrations($query_params = array(), $get_cached = false)
393
-    {
394
-        $query_params = (empty($query_params) || ! is_array($query_params))
395
-            ? array(
396
-                'order_by' => array(
397
-                    'Event.EVT_name'     => 'ASC',
398
-                    'Attendee.ATT_lname' => 'ASC',
399
-                    'Attendee.ATT_fname' => 'ASC',
400
-                ),
401
-            )
402
-            : $query_params;
403
-        $query_params = $get_cached ? array() : $query_params;
404
-        return $this->get_many_related('Registration', $query_params);
405
-    }
406
-
407
-
408
-    /**
409
-     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
410
-     * function for getting attendees and how many registrations they each have for an event)
411
-     *
412
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
413
-     * @throws EE_Error
414
-     */
415
-    public function attendees()
416
-    {
417
-        return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
418
-    }
419
-
420
-
421
-    /**
422
-     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
423
-     *
424
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
425
-     * @return EE_Base_Class[]|EE_Payment[]
426
-     * @throws EE_Error
427
-     */
428
-    public function payments($query_params = array())
429
-    {
430
-        return $this->get_many_related('Payment', $query_params);
431
-    }
432
-
433
-
434
-    /**
435
-     * gets only approved payments for this transaction
436
-     *
437
-     * @return EE_Base_Class[]|EE_Payment[]
438
-     * @throws EE_Error
439
-     * @throws InvalidArgumentException
440
-     * @throws ReflectionException
441
-     * @throws InvalidDataTypeException
442
-     * @throws InvalidInterfaceException
443
-     */
444
-    public function approved_payments()
445
-    {
446
-        EE_Registry::instance()->load_model('Payment');
447
-        return $this->get_many_related(
448
-            'Payment',
449
-            array(
450
-                array('STS_ID' => EEM_Payment::status_id_approved),
451
-                'order_by' => array('PAY_timestamp' => 'DESC'),
452
-            )
453
-        );
454
-    }
455
-
456
-
457
-    /**
458
-     * Gets all payments which have not been approved
459
-     *
460
-     * @return EE_Base_Class[]|EEI_Payment[]
461
-     * @throws EE_Error if a model is misconfigured somehow
462
-     */
463
-    public function pending_payments()
464
-    {
465
-        return $this->get_many_related(
466
-            'Payment',
467
-            array(
468
-                array(
469
-                    'STS_ID' => EEM_Payment::status_id_pending,
470
-                ),
471
-                'order_by' => array(
472
-                    'PAY_timestamp' => 'DESC',
473
-                ),
474
-            )
475
-        );
476
-    }
477
-
478
-
479
-    /**
480
-     * echoes $this->pretty_status()
481
-     *
482
-     * @param bool $show_icons
483
-     * @throws EE_Error
484
-     * @throws InvalidArgumentException
485
-     * @throws InvalidDataTypeException
486
-     * @throws InvalidInterfaceException
487
-     */
488
-    public function e_pretty_status($show_icons = false)
489
-    {
490
-        echo $this->pretty_status($show_icons);
491
-    }
492
-
493
-
494
-    /**
495
-     * returns a pretty version of the status, good for displaying to users
496
-     *
497
-     * @param bool $show_icons
498
-     * @return string
499
-     * @throws EE_Error
500
-     * @throws InvalidArgumentException
501
-     * @throws InvalidDataTypeException
502
-     * @throws InvalidInterfaceException
503
-     */
504
-    public function pretty_status($show_icons = false)
505
-    {
506
-        $status = EEM_Status::instance()->localized_status(
507
-            array($this->status_ID() => __('unknown', 'event_espresso')),
508
-            false,
509
-            'sentence'
510
-        );
511
-        $icon = '';
512
-        switch ($this->status_ID()) {
513
-            case EEM_Transaction::complete_status_code:
514
-                $icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
515
-                break;
516
-            case EEM_Transaction::incomplete_status_code:
517
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
518
-                    : '';
519
-                break;
520
-            case EEM_Transaction::abandoned_status_code:
521
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
522
-                break;
523
-            case EEM_Transaction::failed_status_code:
524
-                $icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
525
-                break;
526
-            case EEM_Transaction::overpaid_status_code:
527
-                $icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
528
-                break;
529
-        }
530
-        return $icon . $status[ $this->status_ID() ];
531
-    }
532
-
533
-
534
-    /**
535
-     * get Transaction Status
536
-     *
537
-     * @throws EE_Error
538
-     */
539
-    public function status_ID()
540
-    {
541
-        return $this->get('STS_ID');
542
-    }
543
-
544
-
545
-    /**
546
-     * Returns TRUE or FALSE for whether or not this transaction cost any money
547
-     *
548
-     * @return boolean
549
-     * @throws EE_Error
550
-     */
551
-    public function is_free()
552
-    {
553
-        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
554
-    }
555
-
556
-
557
-    /**
558
-     * Returns whether this transaction is complete
559
-     * Useful in templates and other logic for deciding if we should ask for another payment...
560
-     *
561
-     * @return boolean
562
-     * @throws EE_Error
563
-     */
564
-    public function is_completed()
565
-    {
566
-        return $this->status_ID() === EEM_Transaction::complete_status_code;
567
-    }
568
-
569
-
570
-    /**
571
-     * Returns whether this transaction is incomplete
572
-     * Useful in templates and other logic for deciding if we should ask for another payment...
573
-     *
574
-     * @return boolean
575
-     * @throws EE_Error
576
-     */
577
-    public function is_incomplete()
578
-    {
579
-        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
580
-    }
581
-
582
-
583
-    /**
584
-     * Returns whether this transaction is overpaid
585
-     * Useful in templates and other logic for deciding if monies need to be refunded
586
-     *
587
-     * @return boolean
588
-     * @throws EE_Error
589
-     */
590
-    public function is_overpaid()
591
-    {
592
-        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
593
-    }
594
-
595
-
596
-    /**
597
-     * Returns whether this transaction was abandoned
598
-     * meaning that the transaction/registration process was somehow interrupted and never completed
599
-     * but that contact information exists for at least one registrant
600
-     *
601
-     * @return boolean
602
-     * @throws EE_Error
603
-     */
604
-    public function is_abandoned()
605
-    {
606
-        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
607
-    }
608
-
609
-
610
-    /**
611
-     * Returns whether this transaction failed
612
-     * meaning that the transaction/registration process was somehow interrupted and never completed
613
-     * and that NO contact information exists for any registrants
614
-     *
615
-     * @return boolean
616
-     * @throws EE_Error
617
-     */
618
-    public function failed()
619
-    {
620
-        return $this->status_ID() === EEM_Transaction::failed_status_code;
621
-    }
622
-
623
-
624
-    /**
625
-     * This returns the url for the invoice of this transaction
626
-     *
627
-     * @param string $type 'html' or 'pdf' (default is pdf)
628
-     * @return string
629
-     * @throws EE_Error
630
-     */
631
-    public function invoice_url($type = 'html')
632
-    {
633
-        $REG = $this->primary_registration();
634
-        if (! $REG instanceof EE_Registration) {
635
-            return '';
636
-        }
637
-        return $REG->invoice_url($type);
638
-    }
639
-
640
-
641
-    /**
642
-     * Gets the primary registration only
643
-     *
644
-     * @return EE_Base_Class|EE_Registration
645
-     * @throws EE_Error
646
-     */
647
-    public function primary_registration()
648
-    {
649
-        $registrations = (array) $this->get_many_related(
650
-            'Registration',
651
-            array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
652
-        );
653
-        foreach ($registrations as $registration) {
654
-            // valid registration that is NOT cancelled or declined ?
655
-            if ($registration instanceof EE_Registration
656
-                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
657
-            ) {
658
-                return $registration;
659
-            }
660
-        }
661
-        // nothing valid found, so just return first thing from array of results
662
-        return reset($registrations);
663
-    }
664
-
665
-
666
-    /**
667
-     * Gets the URL for viewing the receipt
668
-     *
669
-     * @param string $type 'pdf' or 'html' (default is 'html')
670
-     * @return string
671
-     * @throws EE_Error
672
-     */
673
-    public function receipt_url($type = 'html')
674
-    {
675
-        $REG = $this->primary_registration();
676
-        if (! $REG instanceof EE_Registration) {
677
-            return '';
678
-        }
679
-        return $REG->receipt_url($type);
680
-    }
681
-
682
-
683
-    /**
684
-     * Gets the URL of the thank you page with this registration REG_url_link added as
685
-     * a query parameter
686
-     *
687
-     * @return string
688
-     * @throws EE_Error
689
-     */
690
-    public function payment_overview_url()
691
-    {
692
-        $primary_registration = $this->primary_registration();
693
-        return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
694
-    }
695
-
696
-
697
-    /**
698
-     * @return string
699
-     * @throws EE_Error
700
-     */
701
-    public function gateway_response_on_transaction()
702
-    {
703
-        $payment = $this->get_first_related('Payment');
704
-        return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
705
-    }
706
-
707
-
708
-    /**
709
-     * Get the status object of this object
710
-     *
711
-     * @return EE_Base_Class|EE_Status
712
-     * @throws EE_Error
713
-     */
714
-    public function status_obj()
715
-    {
716
-        return $this->get_first_related('Status');
717
-    }
718
-
719
-
720
-    /**
721
-     * Gets all the extra meta info on this payment
722
-     *
723
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
724
-     * @return EE_Base_Class[]|EE_Extra_Meta
725
-     * @throws EE_Error
726
-     */
727
-    public function extra_meta($query_params = array())
728
-    {
729
-        return $this->get_many_related('Extra_Meta', $query_params);
730
-    }
731
-
732
-
733
-    /**
734
-     * Wrapper for _add_relation_to
735
-     *
736
-     * @param EE_Registration $registration
737
-     * @return EE_Base_Class the relation was added to
738
-     * @throws EE_Error
739
-     */
740
-    public function add_registration(EE_Registration $registration)
741
-    {
742
-        return $this->_add_relation_to($registration, 'Registration');
743
-    }
744
-
745
-
746
-    /**
747
-     * Removes the given registration from being related (even before saving this transaction).
748
-     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
749
-     *
750
-     * @param int $registration_or_id
751
-     * @return EE_Base_Class that was removed from being related
752
-     * @throws EE_Error
753
-     */
754
-    public function remove_registration_with_id($registration_or_id)
755
-    {
756
-        return $this->_remove_relation_to($registration_or_id, 'Registration');
757
-    }
758
-
759
-
760
-    /**
761
-     * Gets all the line items which are for ACTUAL items
762
-     *
763
-     * @return EE_Line_Item[]
764
-     * @throws EE_Error
765
-     */
766
-    public function items_purchased()
767
-    {
768
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
769
-    }
770
-
771
-
772
-    /**
773
-     * Wrapper for _add_relation_to
774
-     *
775
-     * @param EE_Line_Item $line_item
776
-     * @return EE_Base_Class the relation was added to
777
-     * @throws EE_Error
778
-     */
779
-    public function add_line_item(EE_Line_Item $line_item)
780
-    {
781
-        return $this->_add_relation_to($line_item, 'Line_Item');
782
-    }
783
-
784
-
785
-    /**
786
-     * Gets ALL the line items related to this transaction (unstructured)
787
-     *
788
-     * @param array $query_params
789
-     * @return EE_Base_Class[]|EE_Line_Item[]
790
-     * @throws EE_Error
791
-     */
792
-    public function line_items($query_params = array())
793
-    {
794
-        return $this->get_many_related('Line_Item', $query_params);
795
-    }
796
-
797
-
798
-    /**
799
-     * Gets all the line items which are taxes on the total
800
-     *
801
-     * @return EE_Line_Item[]
802
-     * @throws EE_Error
803
-     */
804
-    public function tax_items()
805
-    {
806
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
807
-    }
808
-
809
-
810
-    /**
811
-     * Gets the total line item (which is a parent of all other related line items,
812
-     * meaning it takes them all into account on its total)
813
-     *
814
-     * @param bool $create_if_not_found
815
-     * @return \EE_Line_Item
816
-     * @throws EE_Error
817
-     */
818
-    public function total_line_item($create_if_not_found = true)
819
-    {
820
-        $item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
821
-        if (! $item && $create_if_not_found) {
822
-            $item = EEH_Line_Item::create_total_line_item($this);
823
-        }
824
-        return $item;
825
-    }
826
-
827
-
828
-    /**
829
-     * Returns the total amount of tax on this transaction
830
-     * (assumes there's only one tax subtotal line item)
831
-     *
832
-     * @return float
833
-     * @throws EE_Error
834
-     */
835
-    public function tax_total()
836
-    {
837
-        $tax_line_item = $this->tax_total_line_item();
838
-        if ($tax_line_item) {
839
-            return (float) $tax_line_item->total();
840
-        }
841
-        return (float) 0;
842
-    }
843
-
844
-
845
-    /**
846
-     * Gets the tax subtotal line item (assumes there's only one)
847
-     *
848
-     * @return EE_Line_Item
849
-     * @throws EE_Error
850
-     */
851
-    public function tax_total_line_item()
852
-    {
853
-        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
854
-    }
855
-
856
-
857
-    /**
858
-     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
859
-     *
860
-     * @return EE_Form_Section_Proper
861
-     * @throws EE_Error
862
-     */
863
-    public function billing_info()
864
-    {
865
-        $payment_method = $this->payment_method();
866
-        if (! $payment_method) {
867
-            EE_Error::add_error(
868
-                __(
869
-                    'Could not find billing info for transaction because no gateway has been used for it yet',
870
-                    'event_espresso'
871
-                ),
872
-                __FILE__,
873
-                __FUNCTION__,
874
-                __LINE__
875
-            );
876
-            return null;
877
-        }
878
-        $primary_reg = $this->primary_registration();
879
-        if (! $primary_reg) {
880
-            EE_Error::add_error(
881
-                __(
882
-                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
883
-                    'event_espresso'
884
-                ),
885
-                __FILE__,
886
-                __FUNCTION__,
887
-                __LINE__
888
-            );
889
-            return null;
890
-        }
891
-        $attendee = $primary_reg->attendee();
892
-        if (! $attendee) {
893
-            EE_Error::add_error(
894
-                __(
895
-                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
896
-                    'event_espresso'
897
-                ),
898
-                __FILE__,
899
-                __FUNCTION__,
900
-                __LINE__
901
-            );
902
-            return null;
903
-        }
904
-        return $attendee->billing_info_for_payment_method($payment_method);
905
-    }
906
-
907
-
908
-    /**
909
-     * Gets PMD_ID
910
-     *
911
-     * @return int
912
-     * @throws EE_Error
913
-     */
914
-    public function payment_method_ID()
915
-    {
916
-        return $this->get('PMD_ID');
917
-    }
918
-
919
-
920
-    /**
921
-     * Sets PMD_ID
922
-     *
923
-     * @param int $PMD_ID
924
-     * @throws EE_Error
925
-     */
926
-    public function set_payment_method_ID($PMD_ID)
927
-    {
928
-        $this->set('PMD_ID', $PMD_ID);
929
-    }
930
-
931
-
932
-    /**
933
-     * Gets the last-used payment method on this transaction
934
-     * (we COULD just use the last-made payment, but some payment methods, namely
935
-     * offline ones, dont' create payments)
936
-     *
937
-     * @return EE_Payment_Method
938
-     * @throws EE_Error
939
-     */
940
-    public function payment_method()
941
-    {
942
-        $pm = $this->get_first_related('Payment_Method');
943
-        if ($pm instanceof EE_Payment_Method) {
944
-            return $pm;
945
-        }
946
-        $last_payment = $this->last_payment();
947
-        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
948
-            return $last_payment->payment_method();
949
-        }
950
-        return null;
951
-    }
952
-
953
-
954
-    /**
955
-     * Gets the last payment made
956
-     *
957
-     * @return EE_Base_Class|EE_Payment
958
-     * @throws EE_Error
959
-     */
960
-    public function last_payment()
961
-    {
962
-        return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
963
-    }
964
-
965
-
966
-    /**
967
-     * Gets all the line items which are unrelated to tickets on this transaction
968
-     *
969
-     * @return EE_Line_Item[]
970
-     * @throws EE_Error
971
-     * @throws InvalidArgumentException
972
-     * @throws InvalidDataTypeException
973
-     * @throws InvalidInterfaceException
974
-     */
975
-    public function non_ticket_line_items()
976
-    {
977
-        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
978
-    }
979
-
980
-
981
-    /**
982
-     * possibly toggles TXN status
983
-     *
984
-     * @param  boolean $update whether to save the TXN
985
-     * @return bool whether the TXN was saved
986
-     * @throws EE_Error
987
-     * @throws RuntimeException
988
-     */
989
-    public function update_status_based_on_total_paid($update = true)
990
-    {
991
-        // set transaction status based on comparison of TXN_paid vs TXN_total
992
-        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
993
-            $new_txn_status = EEM_Transaction::overpaid_status_code;
994
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
995
-            $new_txn_status = EEM_Transaction::complete_status_code;
996
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
997
-            $new_txn_status = EEM_Transaction::incomplete_status_code;
998
-        } else {
999
-            throw new RuntimeException(
1000
-                __('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1001
-            );
1002
-        }
1003
-        if ($new_txn_status !== $this->status_ID()) {
1004
-            $this->set_status($new_txn_status);
1005
-            if ($update) {
1006
-                return $this->save() ? true : false;
1007
-            }
1008
-        }
1009
-        return false;
1010
-    }
1011
-
1012
-
1013
-    /**
1014
-     * Updates the transaction's status and total_paid based on all the payments
1015
-     * that apply to it
1016
-     *
1017
-     * @deprecated
1018
-     * @return array|bool
1019
-     * @throws EE_Error
1020
-     * @throws InvalidArgumentException
1021
-     * @throws ReflectionException
1022
-     * @throws InvalidDataTypeException
1023
-     * @throws InvalidInterfaceException
1024
-     */
1025
-    public function update_based_on_payments()
1026
-    {
1027
-        EE_Error::doing_it_wrong(
1028
-            __CLASS__ . '::' . __FUNCTION__,
1029
-            sprintf(
1030
-                __('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1031
-                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1032
-            ),
1033
-            '4.6.0'
1034
-        );
1035
-        /** @type EE_Transaction_Processor $transaction_processor */
1036
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1037
-        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1038
-    }
1039
-
1040
-
1041
-    /**
1042
-     * @return string
1043
-     */
1044
-    public function old_txn_status()
1045
-    {
1046
-        return $this->_old_txn_status;
1047
-    }
1048
-
1049
-
1050
-    /**
1051
-     * @param string $old_txn_status
1052
-     */
1053
-    public function set_old_txn_status($old_txn_status)
1054
-    {
1055
-        // only set the first time
1056
-        if ($this->_old_txn_status === null) {
1057
-            $this->_old_txn_status = $old_txn_status;
1058
-        }
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     * reg_status_updated
1064
-     *
1065
-     * @return bool
1066
-     * @throws EE_Error
1067
-     */
1068
-    public function txn_status_updated()
1069
-    {
1070
-        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1071
-    }
1072
-
1073
-
1074
-    /**
1075
-     * _reg_steps_completed
1076
-     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1077
-     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1078
-     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1079
-     *
1080
-     * @param string $reg_step_slug
1081
-     * @param bool   $check_all
1082
-     * @return bool|int
1083
-     * @throws EE_Error
1084
-     */
1085
-    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1086
-    {
1087
-        $reg_steps = $this->reg_steps();
1088
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1089
-            return false;
1090
-        }
1091
-        // loop thru reg steps array)
1092
-        foreach ($reg_steps as $slug => $reg_step_completed) {
1093
-            // if NOT checking ALL steps (only checking one step)
1094
-            if (! $check_all) {
1095
-                // and this is the one
1096
-                if ($slug === $reg_step_slug) {
1097
-                    return $reg_step_completed;
1098
-                }
1099
-                // skip to next reg step in loop
1100
-                continue;
1101
-            }
1102
-            // $check_all must be true, else we would never have gotten to this point
1103
-            if ($slug === $reg_step_slug) {
1104
-                // if we reach this point, then we are testing either:
1105
-                // all_reg_steps_completed_except() or
1106
-                // all_reg_steps_completed_except_final_step(),
1107
-                // and since this is the reg step EXCEPTION being tested
1108
-                // we want to return true (yes true) if this reg step is NOT completed
1109
-                // ie: "is everything completed except the final step?"
1110
-                // "that is correct... the final step is not completed, but all others are."
1111
-                return $reg_step_completed !== true;
1112
-            }
1113
-            if ($reg_step_completed !== true) {
1114
-                // if any reg step is NOT completed, then ALL steps are not completed
1115
-                return false;
1116
-            }
1117
-        }
1118
-        return true;
1119
-    }
1120
-
1121
-
1122
-    /**
1123
-     * all_reg_steps_completed
1124
-     * returns:
1125
-     *    true if ALL reg steps have been marked as completed
1126
-     *        or false if any step is not completed
1127
-     *
1128
-     * @return bool
1129
-     * @throws EE_Error
1130
-     */
1131
-    public function all_reg_steps_completed()
1132
-    {
1133
-        return $this->_reg_steps_completed();
1134
-    }
1135
-
1136
-
1137
-    /**
1138
-     * all_reg_steps_completed_except
1139
-     * returns:
1140
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1141
-     *        or false if any other step is not completed
1142
-     *        or false if ALL steps are completed including the exception you are testing !!!
1143
-     *
1144
-     * @param string $exception
1145
-     * @return bool
1146
-     * @throws EE_Error
1147
-     */
1148
-    public function all_reg_steps_completed_except($exception = '')
1149
-    {
1150
-        return $this->_reg_steps_completed($exception);
1151
-    }
1152
-
1153
-
1154
-    /**
1155
-     * all_reg_steps_completed_except
1156
-     * returns:
1157
-     *        true if ALL reg steps, except the final step, have been marked as completed
1158
-     *        or false if any step is not completed
1159
-     *    or false if ALL steps are completed including the final step !!!
1160
-     *
1161
-     * @return bool
1162
-     * @throws EE_Error
1163
-     */
1164
-    public function all_reg_steps_completed_except_final_step()
1165
-    {
1166
-        return $this->_reg_steps_completed('finalize_registration');
1167
-    }
1168
-
1169
-
1170
-    /**
1171
-     * reg_step_completed
1172
-     * returns:
1173
-     *    true if a specific reg step has been marked as completed
1174
-     *    a Unix timestamp if it has been initialized but not yet completed,
1175
-     *    or false if it has not yet been initialized
1176
-     *
1177
-     * @param string $reg_step_slug
1178
-     * @return bool|int
1179
-     * @throws EE_Error
1180
-     */
1181
-    public function reg_step_completed($reg_step_slug)
1182
-    {
1183
-        return $this->_reg_steps_completed($reg_step_slug, false);
1184
-    }
1185
-
1186
-
1187
-    /**
1188
-     * completed_final_reg_step
1189
-     * returns:
1190
-     *    true if the finalize_registration reg step has been marked as completed
1191
-     *    a Unix timestamp if it has been initialized but not yet completed,
1192
-     *    or false if it has not yet been initialized
1193
-     *
1194
-     * @return bool|int
1195
-     * @throws EE_Error
1196
-     */
1197
-    public function final_reg_step_completed()
1198
-    {
1199
-        return $this->_reg_steps_completed('finalize_registration', false);
1200
-    }
1201
-
1202
-
1203
-    /**
1204
-     * set_reg_step_initiated
1205
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1206
-     *
1207
-     * @param string $reg_step_slug
1208
-     * @return boolean
1209
-     * @throws EE_Error
1210
-     */
1211
-    public function set_reg_step_initiated($reg_step_slug)
1212
-    {
1213
-        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1214
-    }
1215
-
1216
-
1217
-    /**
1218
-     * set_reg_step_completed
1219
-     * given a valid TXN_reg_step, this sets the step as completed
1220
-     *
1221
-     * @param string $reg_step_slug
1222
-     * @return boolean
1223
-     * @throws EE_Error
1224
-     */
1225
-    public function set_reg_step_completed($reg_step_slug)
1226
-    {
1227
-        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1228
-    }
1229
-
1230
-
1231
-    /**
1232
-     * set_reg_step_completed
1233
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1234
-     *
1235
-     * @param string $reg_step_slug
1236
-     * @return boolean
1237
-     * @throws EE_Error
1238
-     */
1239
-    public function set_reg_step_not_completed($reg_step_slug)
1240
-    {
1241
-        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1242
-    }
1243
-
1244
-
1245
-    /**
1246
-     * set_reg_step_completed
1247
-     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1248
-     *
1249
-     * @param  string      $reg_step_slug
1250
-     * @param  boolean|int $status
1251
-     * @return boolean
1252
-     * @throws EE_Error
1253
-     */
1254
-    private function _set_reg_step_completed_status($reg_step_slug, $status)
1255
-    {
1256
-        // validate status
1257
-        $status = is_bool($status) || is_int($status) ? $status : false;
1258
-        // get reg steps array
1259
-        $txn_reg_steps = $this->reg_steps();
1260
-        // if reg step does NOT exist
1261
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1262
-            return false;
1263
-        }
1264
-        // if  we're trying to complete a step that is already completed
1265
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1266
-            return true;
1267
-        }
1268
-        // if  we're trying to complete a step that hasn't even started
1269
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1270
-            return false;
1271
-        }
1272
-        // if current status value matches the incoming value (no change)
1273
-        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1274
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1275
-            // this will happen in cases where multiple AJAX requests occur during the same step
1276
-            return true;
1277
-        }
1278
-        // if we're trying to set a start time, but it has already been set...
1279
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1280
-            // skip the update below, but don't return FALSE so that errors won't be displayed
1281
-            return true;
1282
-        }
1283
-        // update completed status
1284
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1285
-        $this->set_reg_steps($txn_reg_steps);
1286
-        $this->save();
1287
-        return true;
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * remove_reg_step
1293
-     * given a valid TXN_reg_step slug, this will remove (unset)
1294
-     * the reg step from the TXN reg step array
1295
-     *
1296
-     * @param string $reg_step_slug
1297
-     * @return void
1298
-     * @throws EE_Error
1299
-     */
1300
-    public function remove_reg_step($reg_step_slug)
1301
-    {
1302
-        // get reg steps array
1303
-        $txn_reg_steps = $this->reg_steps();
1304
-        unset($txn_reg_steps[ $reg_step_slug ]);
1305
-        $this->set_reg_steps($txn_reg_steps);
1306
-    }
1307
-
1308
-
1309
-    /**
1310
-     * toggle_failed_transaction_status
1311
-     * upgrades a TXNs status from failed to abandoned,
1312
-     * meaning that contact information has been captured for at least one registrant
1313
-     *
1314
-     * @param bool $save
1315
-     * @return bool
1316
-     * @throws EE_Error
1317
-     */
1318
-    public function toggle_failed_transaction_status($save = true)
1319
-    {
1320
-        // if TXN status is still set as "failed"...
1321
-        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1322
-            $this->set_status(EEM_Transaction::abandoned_status_code);
1323
-            if ($save) {
1324
-                $this->save();
1325
-            }
1326
-            return true;
1327
-        }
1328
-        return false;
1329
-    }
1330
-
1331
-
1332
-    /**
1333
-     * toggle_abandoned_transaction_status
1334
-     * upgrades a TXNs status from failed or abandoned to incomplete
1335
-     *
1336
-     * @return bool
1337
-     * @throws EE_Error
1338
-     */
1339
-    public function toggle_abandoned_transaction_status()
1340
-    {
1341
-        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1342
-        $txn_status = $this->status_ID();
1343
-        if ($txn_status === EEM_Transaction::failed_status_code
1344
-            || $txn_status === EEM_Transaction::abandoned_status_code
1345
-        ) {
1346
-            // if a contact record for the primary registrant has been created
1347
-            if ($this->primary_registration() instanceof EE_Registration
1348
-                && $this->primary_registration()->attendee() instanceof EE_Attendee
1349
-            ) {
1350
-                $this->set_status(EEM_Transaction::incomplete_status_code);
1351
-            } else {
1352
-                // no contact record? yer abandoned!
1353
-                $this->set_status(EEM_Transaction::abandoned_status_code);
1354
-            }
1355
-            return true;
1356
-        }
1357
-        return false;
1358
-    }
1359
-
1360
-
1361
-    /**
1362
-     * checks if an Abandoned TXN has any related payments, and if so,
1363
-     * updates the TXN status based on the amount paid
1364
-     *
1365
-     * @throws EE_Error
1366
-     * @throws InvalidDataTypeException
1367
-     * @throws InvalidInterfaceException
1368
-     * @throws InvalidArgumentException
1369
-     * @throws RuntimeException
1370
-     */
1371
-    public function verify_abandoned_transaction_status()
1372
-    {
1373
-        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1374
-            return;
1375
-        }
1376
-        $payments = $this->get_many_related('Payment');
1377
-        if (! empty($payments)) {
1378
-            foreach ($payments as $payment) {
1379
-                if ($payment instanceof EE_Payment) {
1380
-                    // kk this TXN should NOT be abandoned
1381
-                    $this->update_status_based_on_total_paid();
1382
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1383
-                        EE_Error::add_attention(
1384
-                            sprintf(
1385
-                                esc_html__(
1386
-                                    'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1387
-                                    'event_espresso'
1388
-                                ),
1389
-                                $this->ID(),
1390
-                                $this->pretty_status()
1391
-                            )
1392
-                        );
1393
-                    }
1394
-                    // get final reg step status
1395
-                    $finalized = $this->final_reg_step_completed();
1396
-                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1397
-                    // but has not yet been fully completed (TRUE)
1398
-                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1399
-                        $this->set_reg_step_completed('finalize_registration');
1400
-                        $this->save();
1401
-                    }
1402
-                }
1403
-            }
1404
-        }
1405
-    }
16
+	/**
17
+	 * The length of time in seconds that a lock is applied before being considered expired.
18
+	 * It is not long because a transaction should only be locked for the duration of the request that locked it
19
+	 */
20
+	const LOCK_EXPIRATION = 2;
21
+
22
+	/**
23
+	 * txn status upon initial construction.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	protected $_old_txn_status;
28
+
29
+
30
+	/**
31
+	 * @param array  $props_n_values          incoming values
32
+	 * @param string $timezone                incoming timezone
33
+	 *                                        (if not set the timezone set for the website will be used.)
34
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
+	 *                                        date_format and the second value is the time format
36
+	 * @return EE_Transaction
37
+	 * @throws EE_Error
38
+	 */
39
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
40
+	{
41
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
42
+		$txn = $has_object
43
+			? $has_object
44
+			: new self($props_n_values, false, $timezone, $date_formats);
45
+		if (! $has_object) {
46
+			$txn->set_old_txn_status($txn->status_ID());
47
+		}
48
+		return $txn;
49
+	}
50
+
51
+
52
+	/**
53
+	 * @param array  $props_n_values  incoming values from the database
54
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
55
+	 *                                the website will be used.
56
+	 * @return EE_Transaction
57
+	 * @throws EE_Error
58
+	 */
59
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
60
+	{
61
+		$txn = new self($props_n_values, true, $timezone);
62
+		$txn->set_old_txn_status($txn->status_ID());
63
+		return $txn;
64
+	}
65
+
66
+
67
+	/**
68
+	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
69
+	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
70
+	 * If that also fails, then an exception is thrown.
71
+	 *
72
+	 * @throws EE_Error
73
+	 */
74
+	public function lock()
75
+	{
76
+		// attempt to set lock, but if that fails...
77
+		if (! $this->add_extra_meta('lock', time(), true)) {
78
+			// then attempt to remove the lock in case it is expired
79
+			if ($this->_remove_expired_lock()) {
80
+				// if removal was successful, then try setting lock again
81
+				$this->lock();
82
+			} else {
83
+				// but if the lock can not be removed, then throw an exception
84
+				throw new EE_Error(
85
+					sprintf(
86
+						__(
87
+							'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
88
+							'event_espresso'
89
+						),
90
+						$this->ID()
91
+					)
92
+				);
93
+			}
94
+		}
95
+	}
96
+
97
+
98
+	/**
99
+	 * removes transaction lock applied in EE_Transaction::lock()
100
+	 *
101
+	 * @return int
102
+	 * @throws EE_Error
103
+	 */
104
+	public function unlock()
105
+	{
106
+		return $this->delete_extra_meta('lock');
107
+	}
108
+
109
+
110
+	/**
111
+	 * Decides whether or not now is the right time to update the transaction.
112
+	 * This is useful because we don't always know if it is safe to update the transaction
113
+	 * and its related data. why?
114
+	 * because it's possible that the transaction is being used in another
115
+	 * request and could overwrite anything we save.
116
+	 * So we want to only update the txn once we know that won't happen.
117
+	 * We also check that the lock isn't expired, and remove it if it is
118
+	 *
119
+	 * @return boolean
120
+	 * @throws EE_Error
121
+	 */
122
+	public function is_locked()
123
+	{
124
+		// if TXN is not locked, then return false immediately
125
+		if (! $this->_get_lock()) {
126
+			return false;
127
+		}
128
+		// if not, then let's try and remove the lock in case it's expired...
129
+		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
130
+		// and a positive number if the lock was removed (ie: number of locks deleted),
131
+		// so we need to return the opposite
132
+		return ! $this->_remove_expired_lock() ? true : false;
133
+	}
134
+
135
+
136
+	/**
137
+	 * Gets the meta field indicating that this TXN is locked
138
+	 *
139
+	 * @return int
140
+	 * @throws EE_Error
141
+	 */
142
+	protected function _get_lock()
143
+	{
144
+		return (int) $this->get_extra_meta('lock', true, 0);
145
+	}
146
+
147
+
148
+	/**
149
+	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
150
+	 *
151
+	 * @return int
152
+	 * @throws EE_Error
153
+	 */
154
+	protected function _remove_expired_lock()
155
+	{
156
+		$locked = $this->_get_lock();
157
+		if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
158
+			return $this->unlock();
159
+		}
160
+		return 0;
161
+	}
162
+
163
+
164
+	/**
165
+	 * Set transaction total
166
+	 *
167
+	 * @param float $total total value of transaction
168
+	 * @throws EE_Error
169
+	 */
170
+	public function set_total($total = 0.00)
171
+	{
172
+		$this->set('TXN_total', (float) $total);
173
+	}
174
+
175
+
176
+	/**
177
+	 * Set Total Amount Paid to Date
178
+	 *
179
+	 * @param float $total_paid total amount paid to date (sum of all payments)
180
+	 * @throws EE_Error
181
+	 */
182
+	public function set_paid($total_paid = 0.00)
183
+	{
184
+		$this->set('TXN_paid', (float) $total_paid);
185
+	}
186
+
187
+
188
+	/**
189
+	 * Set transaction status
190
+	 *
191
+	 * @param string $status        whether the transaction is open, declined, accepted,
192
+	 *                              or any number of custom values that can be set
193
+	 * @throws EE_Error
194
+	 */
195
+	public function set_status($status = '')
196
+	{
197
+		$this->set('STS_ID', $status);
198
+	}
199
+
200
+
201
+	/**
202
+	 * Set hash salt
203
+	 *
204
+	 * @param string $hash_salt required for some payment gateways
205
+	 * @throws EE_Error
206
+	 */
207
+	public function set_hash_salt($hash_salt = '')
208
+	{
209
+		$this->set('TXN_hash_salt', $hash_salt);
210
+	}
211
+
212
+
213
+	/**
214
+	 * Sets TXN_reg_steps array
215
+	 *
216
+	 * @param array $txn_reg_steps
217
+	 * @throws EE_Error
218
+	 */
219
+	public function set_reg_steps(array $txn_reg_steps)
220
+	{
221
+		$this->set('TXN_reg_steps', $txn_reg_steps);
222
+	}
223
+
224
+
225
+	/**
226
+	 * Gets TXN_reg_steps
227
+	 *
228
+	 * @return array
229
+	 * @throws EE_Error
230
+	 */
231
+	public function reg_steps()
232
+	{
233
+		$TXN_reg_steps = $this->get('TXN_reg_steps');
234
+		return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
235
+	}
236
+
237
+
238
+	/**
239
+	 * @return string of transaction's total cost, with currency symbol and decimal
240
+	 * @throws EE_Error
241
+	 */
242
+	public function pretty_total()
243
+	{
244
+		return $this->get_pretty('TXN_total');
245
+	}
246
+
247
+
248
+	/**
249
+	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
250
+	 *
251
+	 * @return string
252
+	 * @throws EE_Error
253
+	 */
254
+	public function pretty_paid()
255
+	{
256
+		return $this->get_pretty('TXN_paid');
257
+	}
258
+
259
+
260
+	/**
261
+	 * calculate the amount remaining for this transaction and return;
262
+	 *
263
+	 * @return float amount remaining
264
+	 * @throws EE_Error
265
+	 */
266
+	public function remaining()
267
+	{
268
+		return $this->total() - $this->paid();
269
+	}
270
+
271
+
272
+	/**
273
+	 * get Transaction Total
274
+	 *
275
+	 * @return float
276
+	 * @throws EE_Error
277
+	 */
278
+	public function total()
279
+	{
280
+		return (float) $this->get('TXN_total');
281
+	}
282
+
283
+
284
+	/**
285
+	 * get Total Amount Paid to Date
286
+	 *
287
+	 * @return float
288
+	 * @throws EE_Error
289
+	 */
290
+	public function paid()
291
+	{
292
+		return (float) $this->get('TXN_paid');
293
+	}
294
+
295
+
296
+	/**
297
+	 * @throws EE_Error
298
+	 */
299
+	public function get_cart_session()
300
+	{
301
+		$session_data = (array) $this->get('TXN_session_data');
302
+		return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
303
+			? $session_data['cart']
304
+			: null;
305
+	}
306
+
307
+
308
+	/**
309
+	 * get Transaction session data
310
+	 *
311
+	 * @throws EE_Error
312
+	 */
313
+	public function session_data()
314
+	{
315
+		$session_data = $this->get('TXN_session_data');
316
+		if (empty($session_data)) {
317
+			$session_data = array(
318
+				'id'            => null,
319
+				'user_id'       => null,
320
+				'ip_address'    => null,
321
+				'user_agent'    => null,
322
+				'init_access'   => null,
323
+				'last_access'   => null,
324
+				'pages_visited' => array(),
325
+			);
326
+		}
327
+		return $session_data;
328
+	}
329
+
330
+
331
+	/**
332
+	 * Set session data within the TXN object
333
+	 *
334
+	 * @param EE_Session|array $session_data
335
+	 * @throws EE_Error
336
+	 */
337
+	public function set_txn_session_data($session_data)
338
+	{
339
+		if ($session_data instanceof EE_Session) {
340
+			$this->set('TXN_session_data', $session_data->get_session_data(null, true));
341
+		} else {
342
+			$this->set('TXN_session_data', $session_data);
343
+		}
344
+	}
345
+
346
+
347
+	/**
348
+	 * get Transaction hash salt
349
+	 *
350
+	 * @throws EE_Error
351
+	 */
352
+	public function hash_salt_()
353
+	{
354
+		return $this->get('TXN_hash_salt');
355
+	}
356
+
357
+
358
+	/**
359
+	 * Returns the transaction datetime as either:
360
+	 *            - unix timestamp format ($format = false, $gmt = true)
361
+	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
362
+	 *              has no affect with this option)), this also may include a timezone abbreviation if the
363
+	 *              set timezone in this class differs from what the timezone is on the blog.
364
+	 *            - formatted date string including the UTC (timezone) offset (default).
365
+	 *
366
+	 * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
367
+	 * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
368
+	 *                          or no UTC offset applied
369
+	 * @return string | int
370
+	 * @throws EE_Error
371
+	 */
372
+	public function datetime($format = false, $gmt = false)
373
+	{
374
+		if ($format) {
375
+			return $this->get_pretty('TXN_timestamp');
376
+		}
377
+		if ($gmt) {
378
+			return $this->get_raw('TXN_timestamp');
379
+		}
380
+		return $this->get('TXN_timestamp');
381
+	}
382
+
383
+
384
+	/**
385
+	 * Gets registrations on this transaction
386
+	 *
387
+	 * @param array   $query_params array of query parameters
388
+	 * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
389
+	 * @return EE_Base_Class[]|EE_Registration[]
390
+	 * @throws EE_Error
391
+	 */
392
+	public function registrations($query_params = array(), $get_cached = false)
393
+	{
394
+		$query_params = (empty($query_params) || ! is_array($query_params))
395
+			? array(
396
+				'order_by' => array(
397
+					'Event.EVT_name'     => 'ASC',
398
+					'Attendee.ATT_lname' => 'ASC',
399
+					'Attendee.ATT_fname' => 'ASC',
400
+				),
401
+			)
402
+			: $query_params;
403
+		$query_params = $get_cached ? array() : $query_params;
404
+		return $this->get_many_related('Registration', $query_params);
405
+	}
406
+
407
+
408
+	/**
409
+	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
410
+	 * function for getting attendees and how many registrations they each have for an event)
411
+	 *
412
+	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
413
+	 * @throws EE_Error
414
+	 */
415
+	public function attendees()
416
+	{
417
+		return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
418
+	}
419
+
420
+
421
+	/**
422
+	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
423
+	 *
424
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
425
+	 * @return EE_Base_Class[]|EE_Payment[]
426
+	 * @throws EE_Error
427
+	 */
428
+	public function payments($query_params = array())
429
+	{
430
+		return $this->get_many_related('Payment', $query_params);
431
+	}
432
+
433
+
434
+	/**
435
+	 * gets only approved payments for this transaction
436
+	 *
437
+	 * @return EE_Base_Class[]|EE_Payment[]
438
+	 * @throws EE_Error
439
+	 * @throws InvalidArgumentException
440
+	 * @throws ReflectionException
441
+	 * @throws InvalidDataTypeException
442
+	 * @throws InvalidInterfaceException
443
+	 */
444
+	public function approved_payments()
445
+	{
446
+		EE_Registry::instance()->load_model('Payment');
447
+		return $this->get_many_related(
448
+			'Payment',
449
+			array(
450
+				array('STS_ID' => EEM_Payment::status_id_approved),
451
+				'order_by' => array('PAY_timestamp' => 'DESC'),
452
+			)
453
+		);
454
+	}
455
+
456
+
457
+	/**
458
+	 * Gets all payments which have not been approved
459
+	 *
460
+	 * @return EE_Base_Class[]|EEI_Payment[]
461
+	 * @throws EE_Error if a model is misconfigured somehow
462
+	 */
463
+	public function pending_payments()
464
+	{
465
+		return $this->get_many_related(
466
+			'Payment',
467
+			array(
468
+				array(
469
+					'STS_ID' => EEM_Payment::status_id_pending,
470
+				),
471
+				'order_by' => array(
472
+					'PAY_timestamp' => 'DESC',
473
+				),
474
+			)
475
+		);
476
+	}
477
+
478
+
479
+	/**
480
+	 * echoes $this->pretty_status()
481
+	 *
482
+	 * @param bool $show_icons
483
+	 * @throws EE_Error
484
+	 * @throws InvalidArgumentException
485
+	 * @throws InvalidDataTypeException
486
+	 * @throws InvalidInterfaceException
487
+	 */
488
+	public function e_pretty_status($show_icons = false)
489
+	{
490
+		echo $this->pretty_status($show_icons);
491
+	}
492
+
493
+
494
+	/**
495
+	 * returns a pretty version of the status, good for displaying to users
496
+	 *
497
+	 * @param bool $show_icons
498
+	 * @return string
499
+	 * @throws EE_Error
500
+	 * @throws InvalidArgumentException
501
+	 * @throws InvalidDataTypeException
502
+	 * @throws InvalidInterfaceException
503
+	 */
504
+	public function pretty_status($show_icons = false)
505
+	{
506
+		$status = EEM_Status::instance()->localized_status(
507
+			array($this->status_ID() => __('unknown', 'event_espresso')),
508
+			false,
509
+			'sentence'
510
+		);
511
+		$icon = '';
512
+		switch ($this->status_ID()) {
513
+			case EEM_Transaction::complete_status_code:
514
+				$icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
515
+				break;
516
+			case EEM_Transaction::incomplete_status_code:
517
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
518
+					: '';
519
+				break;
520
+			case EEM_Transaction::abandoned_status_code:
521
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
522
+				break;
523
+			case EEM_Transaction::failed_status_code:
524
+				$icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
525
+				break;
526
+			case EEM_Transaction::overpaid_status_code:
527
+				$icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
528
+				break;
529
+		}
530
+		return $icon . $status[ $this->status_ID() ];
531
+	}
532
+
533
+
534
+	/**
535
+	 * get Transaction Status
536
+	 *
537
+	 * @throws EE_Error
538
+	 */
539
+	public function status_ID()
540
+	{
541
+		return $this->get('STS_ID');
542
+	}
543
+
544
+
545
+	/**
546
+	 * Returns TRUE or FALSE for whether or not this transaction cost any money
547
+	 *
548
+	 * @return boolean
549
+	 * @throws EE_Error
550
+	 */
551
+	public function is_free()
552
+	{
553
+		return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
554
+	}
555
+
556
+
557
+	/**
558
+	 * Returns whether this transaction is complete
559
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
560
+	 *
561
+	 * @return boolean
562
+	 * @throws EE_Error
563
+	 */
564
+	public function is_completed()
565
+	{
566
+		return $this->status_ID() === EEM_Transaction::complete_status_code;
567
+	}
568
+
569
+
570
+	/**
571
+	 * Returns whether this transaction is incomplete
572
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
573
+	 *
574
+	 * @return boolean
575
+	 * @throws EE_Error
576
+	 */
577
+	public function is_incomplete()
578
+	{
579
+		return $this->status_ID() === EEM_Transaction::incomplete_status_code;
580
+	}
581
+
582
+
583
+	/**
584
+	 * Returns whether this transaction is overpaid
585
+	 * Useful in templates and other logic for deciding if monies need to be refunded
586
+	 *
587
+	 * @return boolean
588
+	 * @throws EE_Error
589
+	 */
590
+	public function is_overpaid()
591
+	{
592
+		return $this->status_ID() === EEM_Transaction::overpaid_status_code;
593
+	}
594
+
595
+
596
+	/**
597
+	 * Returns whether this transaction was abandoned
598
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
599
+	 * but that contact information exists for at least one registrant
600
+	 *
601
+	 * @return boolean
602
+	 * @throws EE_Error
603
+	 */
604
+	public function is_abandoned()
605
+	{
606
+		return $this->status_ID() === EEM_Transaction::abandoned_status_code;
607
+	}
608
+
609
+
610
+	/**
611
+	 * Returns whether this transaction failed
612
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
613
+	 * and that NO contact information exists for any registrants
614
+	 *
615
+	 * @return boolean
616
+	 * @throws EE_Error
617
+	 */
618
+	public function failed()
619
+	{
620
+		return $this->status_ID() === EEM_Transaction::failed_status_code;
621
+	}
622
+
623
+
624
+	/**
625
+	 * This returns the url for the invoice of this transaction
626
+	 *
627
+	 * @param string $type 'html' or 'pdf' (default is pdf)
628
+	 * @return string
629
+	 * @throws EE_Error
630
+	 */
631
+	public function invoice_url($type = 'html')
632
+	{
633
+		$REG = $this->primary_registration();
634
+		if (! $REG instanceof EE_Registration) {
635
+			return '';
636
+		}
637
+		return $REG->invoice_url($type);
638
+	}
639
+
640
+
641
+	/**
642
+	 * Gets the primary registration only
643
+	 *
644
+	 * @return EE_Base_Class|EE_Registration
645
+	 * @throws EE_Error
646
+	 */
647
+	public function primary_registration()
648
+	{
649
+		$registrations = (array) $this->get_many_related(
650
+			'Registration',
651
+			array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
652
+		);
653
+		foreach ($registrations as $registration) {
654
+			// valid registration that is NOT cancelled or declined ?
655
+			if ($registration instanceof EE_Registration
656
+				&& ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
657
+			) {
658
+				return $registration;
659
+			}
660
+		}
661
+		// nothing valid found, so just return first thing from array of results
662
+		return reset($registrations);
663
+	}
664
+
665
+
666
+	/**
667
+	 * Gets the URL for viewing the receipt
668
+	 *
669
+	 * @param string $type 'pdf' or 'html' (default is 'html')
670
+	 * @return string
671
+	 * @throws EE_Error
672
+	 */
673
+	public function receipt_url($type = 'html')
674
+	{
675
+		$REG = $this->primary_registration();
676
+		if (! $REG instanceof EE_Registration) {
677
+			return '';
678
+		}
679
+		return $REG->receipt_url($type);
680
+	}
681
+
682
+
683
+	/**
684
+	 * Gets the URL of the thank you page with this registration REG_url_link added as
685
+	 * a query parameter
686
+	 *
687
+	 * @return string
688
+	 * @throws EE_Error
689
+	 */
690
+	public function payment_overview_url()
691
+	{
692
+		$primary_registration = $this->primary_registration();
693
+		return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
694
+	}
695
+
696
+
697
+	/**
698
+	 * @return string
699
+	 * @throws EE_Error
700
+	 */
701
+	public function gateway_response_on_transaction()
702
+	{
703
+		$payment = $this->get_first_related('Payment');
704
+		return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
705
+	}
706
+
707
+
708
+	/**
709
+	 * Get the status object of this object
710
+	 *
711
+	 * @return EE_Base_Class|EE_Status
712
+	 * @throws EE_Error
713
+	 */
714
+	public function status_obj()
715
+	{
716
+		return $this->get_first_related('Status');
717
+	}
718
+
719
+
720
+	/**
721
+	 * Gets all the extra meta info on this payment
722
+	 *
723
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
724
+	 * @return EE_Base_Class[]|EE_Extra_Meta
725
+	 * @throws EE_Error
726
+	 */
727
+	public function extra_meta($query_params = array())
728
+	{
729
+		return $this->get_many_related('Extra_Meta', $query_params);
730
+	}
731
+
732
+
733
+	/**
734
+	 * Wrapper for _add_relation_to
735
+	 *
736
+	 * @param EE_Registration $registration
737
+	 * @return EE_Base_Class the relation was added to
738
+	 * @throws EE_Error
739
+	 */
740
+	public function add_registration(EE_Registration $registration)
741
+	{
742
+		return $this->_add_relation_to($registration, 'Registration');
743
+	}
744
+
745
+
746
+	/**
747
+	 * Removes the given registration from being related (even before saving this transaction).
748
+	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
749
+	 *
750
+	 * @param int $registration_or_id
751
+	 * @return EE_Base_Class that was removed from being related
752
+	 * @throws EE_Error
753
+	 */
754
+	public function remove_registration_with_id($registration_or_id)
755
+	{
756
+		return $this->_remove_relation_to($registration_or_id, 'Registration');
757
+	}
758
+
759
+
760
+	/**
761
+	 * Gets all the line items which are for ACTUAL items
762
+	 *
763
+	 * @return EE_Line_Item[]
764
+	 * @throws EE_Error
765
+	 */
766
+	public function items_purchased()
767
+	{
768
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
769
+	}
770
+
771
+
772
+	/**
773
+	 * Wrapper for _add_relation_to
774
+	 *
775
+	 * @param EE_Line_Item $line_item
776
+	 * @return EE_Base_Class the relation was added to
777
+	 * @throws EE_Error
778
+	 */
779
+	public function add_line_item(EE_Line_Item $line_item)
780
+	{
781
+		return $this->_add_relation_to($line_item, 'Line_Item');
782
+	}
783
+
784
+
785
+	/**
786
+	 * Gets ALL the line items related to this transaction (unstructured)
787
+	 *
788
+	 * @param array $query_params
789
+	 * @return EE_Base_Class[]|EE_Line_Item[]
790
+	 * @throws EE_Error
791
+	 */
792
+	public function line_items($query_params = array())
793
+	{
794
+		return $this->get_many_related('Line_Item', $query_params);
795
+	}
796
+
797
+
798
+	/**
799
+	 * Gets all the line items which are taxes on the total
800
+	 *
801
+	 * @return EE_Line_Item[]
802
+	 * @throws EE_Error
803
+	 */
804
+	public function tax_items()
805
+	{
806
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
807
+	}
808
+
809
+
810
+	/**
811
+	 * Gets the total line item (which is a parent of all other related line items,
812
+	 * meaning it takes them all into account on its total)
813
+	 *
814
+	 * @param bool $create_if_not_found
815
+	 * @return \EE_Line_Item
816
+	 * @throws EE_Error
817
+	 */
818
+	public function total_line_item($create_if_not_found = true)
819
+	{
820
+		$item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
821
+		if (! $item && $create_if_not_found) {
822
+			$item = EEH_Line_Item::create_total_line_item($this);
823
+		}
824
+		return $item;
825
+	}
826
+
827
+
828
+	/**
829
+	 * Returns the total amount of tax on this transaction
830
+	 * (assumes there's only one tax subtotal line item)
831
+	 *
832
+	 * @return float
833
+	 * @throws EE_Error
834
+	 */
835
+	public function tax_total()
836
+	{
837
+		$tax_line_item = $this->tax_total_line_item();
838
+		if ($tax_line_item) {
839
+			return (float) $tax_line_item->total();
840
+		}
841
+		return (float) 0;
842
+	}
843
+
844
+
845
+	/**
846
+	 * Gets the tax subtotal line item (assumes there's only one)
847
+	 *
848
+	 * @return EE_Line_Item
849
+	 * @throws EE_Error
850
+	 */
851
+	public function tax_total_line_item()
852
+	{
853
+		return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
854
+	}
855
+
856
+
857
+	/**
858
+	 * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
859
+	 *
860
+	 * @return EE_Form_Section_Proper
861
+	 * @throws EE_Error
862
+	 */
863
+	public function billing_info()
864
+	{
865
+		$payment_method = $this->payment_method();
866
+		if (! $payment_method) {
867
+			EE_Error::add_error(
868
+				__(
869
+					'Could not find billing info for transaction because no gateway has been used for it yet',
870
+					'event_espresso'
871
+				),
872
+				__FILE__,
873
+				__FUNCTION__,
874
+				__LINE__
875
+			);
876
+			return null;
877
+		}
878
+		$primary_reg = $this->primary_registration();
879
+		if (! $primary_reg) {
880
+			EE_Error::add_error(
881
+				__(
882
+					'Cannot get billing info for gateway %s on transaction because no primary registration exists',
883
+					'event_espresso'
884
+				),
885
+				__FILE__,
886
+				__FUNCTION__,
887
+				__LINE__
888
+			);
889
+			return null;
890
+		}
891
+		$attendee = $primary_reg->attendee();
892
+		if (! $attendee) {
893
+			EE_Error::add_error(
894
+				__(
895
+					'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
896
+					'event_espresso'
897
+				),
898
+				__FILE__,
899
+				__FUNCTION__,
900
+				__LINE__
901
+			);
902
+			return null;
903
+		}
904
+		return $attendee->billing_info_for_payment_method($payment_method);
905
+	}
906
+
907
+
908
+	/**
909
+	 * Gets PMD_ID
910
+	 *
911
+	 * @return int
912
+	 * @throws EE_Error
913
+	 */
914
+	public function payment_method_ID()
915
+	{
916
+		return $this->get('PMD_ID');
917
+	}
918
+
919
+
920
+	/**
921
+	 * Sets PMD_ID
922
+	 *
923
+	 * @param int $PMD_ID
924
+	 * @throws EE_Error
925
+	 */
926
+	public function set_payment_method_ID($PMD_ID)
927
+	{
928
+		$this->set('PMD_ID', $PMD_ID);
929
+	}
930
+
931
+
932
+	/**
933
+	 * Gets the last-used payment method on this transaction
934
+	 * (we COULD just use the last-made payment, but some payment methods, namely
935
+	 * offline ones, dont' create payments)
936
+	 *
937
+	 * @return EE_Payment_Method
938
+	 * @throws EE_Error
939
+	 */
940
+	public function payment_method()
941
+	{
942
+		$pm = $this->get_first_related('Payment_Method');
943
+		if ($pm instanceof EE_Payment_Method) {
944
+			return $pm;
945
+		}
946
+		$last_payment = $this->last_payment();
947
+		if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
948
+			return $last_payment->payment_method();
949
+		}
950
+		return null;
951
+	}
952
+
953
+
954
+	/**
955
+	 * Gets the last payment made
956
+	 *
957
+	 * @return EE_Base_Class|EE_Payment
958
+	 * @throws EE_Error
959
+	 */
960
+	public function last_payment()
961
+	{
962
+		return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
963
+	}
964
+
965
+
966
+	/**
967
+	 * Gets all the line items which are unrelated to tickets on this transaction
968
+	 *
969
+	 * @return EE_Line_Item[]
970
+	 * @throws EE_Error
971
+	 * @throws InvalidArgumentException
972
+	 * @throws InvalidDataTypeException
973
+	 * @throws InvalidInterfaceException
974
+	 */
975
+	public function non_ticket_line_items()
976
+	{
977
+		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
978
+	}
979
+
980
+
981
+	/**
982
+	 * possibly toggles TXN status
983
+	 *
984
+	 * @param  boolean $update whether to save the TXN
985
+	 * @return bool whether the TXN was saved
986
+	 * @throws EE_Error
987
+	 * @throws RuntimeException
988
+	 */
989
+	public function update_status_based_on_total_paid($update = true)
990
+	{
991
+		// set transaction status based on comparison of TXN_paid vs TXN_total
992
+		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
993
+			$new_txn_status = EEM_Transaction::overpaid_status_code;
994
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
995
+			$new_txn_status = EEM_Transaction::complete_status_code;
996
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
997
+			$new_txn_status = EEM_Transaction::incomplete_status_code;
998
+		} else {
999
+			throw new RuntimeException(
1000
+				__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1001
+			);
1002
+		}
1003
+		if ($new_txn_status !== $this->status_ID()) {
1004
+			$this->set_status($new_txn_status);
1005
+			if ($update) {
1006
+				return $this->save() ? true : false;
1007
+			}
1008
+		}
1009
+		return false;
1010
+	}
1011
+
1012
+
1013
+	/**
1014
+	 * Updates the transaction's status and total_paid based on all the payments
1015
+	 * that apply to it
1016
+	 *
1017
+	 * @deprecated
1018
+	 * @return array|bool
1019
+	 * @throws EE_Error
1020
+	 * @throws InvalidArgumentException
1021
+	 * @throws ReflectionException
1022
+	 * @throws InvalidDataTypeException
1023
+	 * @throws InvalidInterfaceException
1024
+	 */
1025
+	public function update_based_on_payments()
1026
+	{
1027
+		EE_Error::doing_it_wrong(
1028
+			__CLASS__ . '::' . __FUNCTION__,
1029
+			sprintf(
1030
+				__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1031
+				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1032
+			),
1033
+			'4.6.0'
1034
+		);
1035
+		/** @type EE_Transaction_Processor $transaction_processor */
1036
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1037
+		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1038
+	}
1039
+
1040
+
1041
+	/**
1042
+	 * @return string
1043
+	 */
1044
+	public function old_txn_status()
1045
+	{
1046
+		return $this->_old_txn_status;
1047
+	}
1048
+
1049
+
1050
+	/**
1051
+	 * @param string $old_txn_status
1052
+	 */
1053
+	public function set_old_txn_status($old_txn_status)
1054
+	{
1055
+		// only set the first time
1056
+		if ($this->_old_txn_status === null) {
1057
+			$this->_old_txn_status = $old_txn_status;
1058
+		}
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 * reg_status_updated
1064
+	 *
1065
+	 * @return bool
1066
+	 * @throws EE_Error
1067
+	 */
1068
+	public function txn_status_updated()
1069
+	{
1070
+		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1071
+	}
1072
+
1073
+
1074
+	/**
1075
+	 * _reg_steps_completed
1076
+	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1077
+	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1078
+	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1079
+	 *
1080
+	 * @param string $reg_step_slug
1081
+	 * @param bool   $check_all
1082
+	 * @return bool|int
1083
+	 * @throws EE_Error
1084
+	 */
1085
+	private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1086
+	{
1087
+		$reg_steps = $this->reg_steps();
1088
+		if (! is_array($reg_steps) || empty($reg_steps)) {
1089
+			return false;
1090
+		}
1091
+		// loop thru reg steps array)
1092
+		foreach ($reg_steps as $slug => $reg_step_completed) {
1093
+			// if NOT checking ALL steps (only checking one step)
1094
+			if (! $check_all) {
1095
+				// and this is the one
1096
+				if ($slug === $reg_step_slug) {
1097
+					return $reg_step_completed;
1098
+				}
1099
+				// skip to next reg step in loop
1100
+				continue;
1101
+			}
1102
+			// $check_all must be true, else we would never have gotten to this point
1103
+			if ($slug === $reg_step_slug) {
1104
+				// if we reach this point, then we are testing either:
1105
+				// all_reg_steps_completed_except() or
1106
+				// all_reg_steps_completed_except_final_step(),
1107
+				// and since this is the reg step EXCEPTION being tested
1108
+				// we want to return true (yes true) if this reg step is NOT completed
1109
+				// ie: "is everything completed except the final step?"
1110
+				// "that is correct... the final step is not completed, but all others are."
1111
+				return $reg_step_completed !== true;
1112
+			}
1113
+			if ($reg_step_completed !== true) {
1114
+				// if any reg step is NOT completed, then ALL steps are not completed
1115
+				return false;
1116
+			}
1117
+		}
1118
+		return true;
1119
+	}
1120
+
1121
+
1122
+	/**
1123
+	 * all_reg_steps_completed
1124
+	 * returns:
1125
+	 *    true if ALL reg steps have been marked as completed
1126
+	 *        or false if any step is not completed
1127
+	 *
1128
+	 * @return bool
1129
+	 * @throws EE_Error
1130
+	 */
1131
+	public function all_reg_steps_completed()
1132
+	{
1133
+		return $this->_reg_steps_completed();
1134
+	}
1135
+
1136
+
1137
+	/**
1138
+	 * all_reg_steps_completed_except
1139
+	 * returns:
1140
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1141
+	 *        or false if any other step is not completed
1142
+	 *        or false if ALL steps are completed including the exception you are testing !!!
1143
+	 *
1144
+	 * @param string $exception
1145
+	 * @return bool
1146
+	 * @throws EE_Error
1147
+	 */
1148
+	public function all_reg_steps_completed_except($exception = '')
1149
+	{
1150
+		return $this->_reg_steps_completed($exception);
1151
+	}
1152
+
1153
+
1154
+	/**
1155
+	 * all_reg_steps_completed_except
1156
+	 * returns:
1157
+	 *        true if ALL reg steps, except the final step, have been marked as completed
1158
+	 *        or false if any step is not completed
1159
+	 *    or false if ALL steps are completed including the final step !!!
1160
+	 *
1161
+	 * @return bool
1162
+	 * @throws EE_Error
1163
+	 */
1164
+	public function all_reg_steps_completed_except_final_step()
1165
+	{
1166
+		return $this->_reg_steps_completed('finalize_registration');
1167
+	}
1168
+
1169
+
1170
+	/**
1171
+	 * reg_step_completed
1172
+	 * returns:
1173
+	 *    true if a specific reg step has been marked as completed
1174
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1175
+	 *    or false if it has not yet been initialized
1176
+	 *
1177
+	 * @param string $reg_step_slug
1178
+	 * @return bool|int
1179
+	 * @throws EE_Error
1180
+	 */
1181
+	public function reg_step_completed($reg_step_slug)
1182
+	{
1183
+		return $this->_reg_steps_completed($reg_step_slug, false);
1184
+	}
1185
+
1186
+
1187
+	/**
1188
+	 * completed_final_reg_step
1189
+	 * returns:
1190
+	 *    true if the finalize_registration reg step has been marked as completed
1191
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1192
+	 *    or false if it has not yet been initialized
1193
+	 *
1194
+	 * @return bool|int
1195
+	 * @throws EE_Error
1196
+	 */
1197
+	public function final_reg_step_completed()
1198
+	{
1199
+		return $this->_reg_steps_completed('finalize_registration', false);
1200
+	}
1201
+
1202
+
1203
+	/**
1204
+	 * set_reg_step_initiated
1205
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1206
+	 *
1207
+	 * @param string $reg_step_slug
1208
+	 * @return boolean
1209
+	 * @throws EE_Error
1210
+	 */
1211
+	public function set_reg_step_initiated($reg_step_slug)
1212
+	{
1213
+		return $this->_set_reg_step_completed_status($reg_step_slug, time());
1214
+	}
1215
+
1216
+
1217
+	/**
1218
+	 * set_reg_step_completed
1219
+	 * given a valid TXN_reg_step, this sets the step as completed
1220
+	 *
1221
+	 * @param string $reg_step_slug
1222
+	 * @return boolean
1223
+	 * @throws EE_Error
1224
+	 */
1225
+	public function set_reg_step_completed($reg_step_slug)
1226
+	{
1227
+		return $this->_set_reg_step_completed_status($reg_step_slug, true);
1228
+	}
1229
+
1230
+
1231
+	/**
1232
+	 * set_reg_step_completed
1233
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1234
+	 *
1235
+	 * @param string $reg_step_slug
1236
+	 * @return boolean
1237
+	 * @throws EE_Error
1238
+	 */
1239
+	public function set_reg_step_not_completed($reg_step_slug)
1240
+	{
1241
+		return $this->_set_reg_step_completed_status($reg_step_slug, false);
1242
+	}
1243
+
1244
+
1245
+	/**
1246
+	 * set_reg_step_completed
1247
+	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1248
+	 *
1249
+	 * @param  string      $reg_step_slug
1250
+	 * @param  boolean|int $status
1251
+	 * @return boolean
1252
+	 * @throws EE_Error
1253
+	 */
1254
+	private function _set_reg_step_completed_status($reg_step_slug, $status)
1255
+	{
1256
+		// validate status
1257
+		$status = is_bool($status) || is_int($status) ? $status : false;
1258
+		// get reg steps array
1259
+		$txn_reg_steps = $this->reg_steps();
1260
+		// if reg step does NOT exist
1261
+		if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1262
+			return false;
1263
+		}
1264
+		// if  we're trying to complete a step that is already completed
1265
+		if ($txn_reg_steps[ $reg_step_slug ] === true) {
1266
+			return true;
1267
+		}
1268
+		// if  we're trying to complete a step that hasn't even started
1269
+		if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1270
+			return false;
1271
+		}
1272
+		// if current status value matches the incoming value (no change)
1273
+		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1274
+		if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1275
+			// this will happen in cases where multiple AJAX requests occur during the same step
1276
+			return true;
1277
+		}
1278
+		// if we're trying to set a start time, but it has already been set...
1279
+		if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1280
+			// skip the update below, but don't return FALSE so that errors won't be displayed
1281
+			return true;
1282
+		}
1283
+		// update completed status
1284
+		$txn_reg_steps[ $reg_step_slug ] = $status;
1285
+		$this->set_reg_steps($txn_reg_steps);
1286
+		$this->save();
1287
+		return true;
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * remove_reg_step
1293
+	 * given a valid TXN_reg_step slug, this will remove (unset)
1294
+	 * the reg step from the TXN reg step array
1295
+	 *
1296
+	 * @param string $reg_step_slug
1297
+	 * @return void
1298
+	 * @throws EE_Error
1299
+	 */
1300
+	public function remove_reg_step($reg_step_slug)
1301
+	{
1302
+		// get reg steps array
1303
+		$txn_reg_steps = $this->reg_steps();
1304
+		unset($txn_reg_steps[ $reg_step_slug ]);
1305
+		$this->set_reg_steps($txn_reg_steps);
1306
+	}
1307
+
1308
+
1309
+	/**
1310
+	 * toggle_failed_transaction_status
1311
+	 * upgrades a TXNs status from failed to abandoned,
1312
+	 * meaning that contact information has been captured for at least one registrant
1313
+	 *
1314
+	 * @param bool $save
1315
+	 * @return bool
1316
+	 * @throws EE_Error
1317
+	 */
1318
+	public function toggle_failed_transaction_status($save = true)
1319
+	{
1320
+		// if TXN status is still set as "failed"...
1321
+		if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1322
+			$this->set_status(EEM_Transaction::abandoned_status_code);
1323
+			if ($save) {
1324
+				$this->save();
1325
+			}
1326
+			return true;
1327
+		}
1328
+		return false;
1329
+	}
1330
+
1331
+
1332
+	/**
1333
+	 * toggle_abandoned_transaction_status
1334
+	 * upgrades a TXNs status from failed or abandoned to incomplete
1335
+	 *
1336
+	 * @return bool
1337
+	 * @throws EE_Error
1338
+	 */
1339
+	public function toggle_abandoned_transaction_status()
1340
+	{
1341
+		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1342
+		$txn_status = $this->status_ID();
1343
+		if ($txn_status === EEM_Transaction::failed_status_code
1344
+			|| $txn_status === EEM_Transaction::abandoned_status_code
1345
+		) {
1346
+			// if a contact record for the primary registrant has been created
1347
+			if ($this->primary_registration() instanceof EE_Registration
1348
+				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1349
+			) {
1350
+				$this->set_status(EEM_Transaction::incomplete_status_code);
1351
+			} else {
1352
+				// no contact record? yer abandoned!
1353
+				$this->set_status(EEM_Transaction::abandoned_status_code);
1354
+			}
1355
+			return true;
1356
+		}
1357
+		return false;
1358
+	}
1359
+
1360
+
1361
+	/**
1362
+	 * checks if an Abandoned TXN has any related payments, and if so,
1363
+	 * updates the TXN status based on the amount paid
1364
+	 *
1365
+	 * @throws EE_Error
1366
+	 * @throws InvalidDataTypeException
1367
+	 * @throws InvalidInterfaceException
1368
+	 * @throws InvalidArgumentException
1369
+	 * @throws RuntimeException
1370
+	 */
1371
+	public function verify_abandoned_transaction_status()
1372
+	{
1373
+		if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1374
+			return;
1375
+		}
1376
+		$payments = $this->get_many_related('Payment');
1377
+		if (! empty($payments)) {
1378
+			foreach ($payments as $payment) {
1379
+				if ($payment instanceof EE_Payment) {
1380
+					// kk this TXN should NOT be abandoned
1381
+					$this->update_status_based_on_total_paid();
1382
+					if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1383
+						EE_Error::add_attention(
1384
+							sprintf(
1385
+								esc_html__(
1386
+									'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1387
+									'event_espresso'
1388
+								),
1389
+								$this->ID(),
1390
+								$this->pretty_status()
1391
+							)
1392
+						);
1393
+					}
1394
+					// get final reg step status
1395
+					$finalized = $this->final_reg_step_completed();
1396
+					// if the 'finalize_registration' step has been initiated (has a timestamp)
1397
+					// but has not yet been fully completed (TRUE)
1398
+					if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1399
+						$this->set_reg_step_completed('finalize_registration');
1400
+						$this->save();
1401
+					}
1402
+				}
1403
+			}
1404
+		}
1405
+	}
1406 1406
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Venue.class.php 1 patch
Indentation   +560 added lines, -560 removed lines patch added patch discarded remove patch
@@ -10,564 +10,564 @@
 block discarded – undo
10 10
 class EE_Venue extends EE_CPT_Base implements EEI_Address
11 11
 {
12 12
 
13
-    /**
14
-     *
15
-     * @param array  $props_n_values          incoming values
16
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
17
-     *                                        used.)
18
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
19
-     *                                        date_format and the second value is the time format
20
-     * @return EE_Attendee
21
-     */
22
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
23
-    {
24
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
25
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
26
-    }
27
-
28
-
29
-    /**
30
-     * @param array  $props_n_values  incoming values from the database
31
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
32
-     *                                the website will be used.
33
-     * @return EE_Attendee
34
-     */
35
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
36
-    {
37
-        return new self($props_n_values, true, $timezone);
38
-    }
39
-
40
-
41
-    /**
42
-     * Gets name
43
-     *
44
-     * @return string
45
-     */
46
-    public function name()
47
-    {
48
-        return $this->get('VNU_name');
49
-    }
50
-
51
-
52
-    /**
53
-     * Gets phone
54
-     *
55
-     * @return string
56
-     */
57
-    public function phone()
58
-    {
59
-        return $this->get('VNU_phone');
60
-    }
61
-
62
-
63
-    /**
64
-     * venue_url
65
-     *
66
-     * @return string
67
-     */
68
-    public function venue_url()
69
-    {
70
-        return $this->get('VNU_url');
71
-    }
72
-
73
-
74
-    /**
75
-     * Gets desc
76
-     *
77
-     * @return string
78
-     */
79
-    public function description()
80
-    {
81
-        return $this->get('VNU_desc');
82
-    }
83
-
84
-
85
-    /**
86
-     * Gets short description (AKA: the excerpt)
87
-     *
88
-     * @return string
89
-     */
90
-    public function excerpt()
91
-    {
92
-        return $this->get('VNU_short_desc');
93
-    }
94
-
95
-
96
-    /**
97
-     * Gets identifier
98
-     *
99
-     * @return string
100
-     */
101
-    public function identifier()
102
-    {
103
-        return $this->get('VNU_identifier');
104
-    }
105
-
106
-
107
-    /**
108
-     * Gets address
109
-     *
110
-     * @return string
111
-     */
112
-    public function address()
113
-    {
114
-        return $this->get('VNU_address');
115
-    }
116
-
117
-
118
-    /**
119
-     * Gets address2
120
-     *
121
-     * @return string
122
-     */
123
-    public function address2()
124
-    {
125
-        return $this->get('VNU_address2');
126
-    }
127
-
128
-
129
-    /**
130
-     * Gets city
131
-     *
132
-     * @return string
133
-     */
134
-    public function city()
135
-    {
136
-        return $this->get('VNU_city');
137
-    }
138
-
139
-    /**
140
-     * Gets state
141
-     *
142
-     * @return int
143
-     */
144
-    public function state_ID()
145
-    {
146
-        return $this->get('STA_ID');
147
-    }
148
-
149
-
150
-    /**
151
-     * @return string
152
-     */
153
-    public function state_abbrev()
154
-    {
155
-        return $this->state_obj() instanceof EE_State ? $this->state_obj()->abbrev() : '';
156
-    }
157
-
158
-
159
-    /**
160
-     * @return string
161
-     */
162
-    public function state_name()
163
-    {
164
-        return $this->state_obj() instanceof EE_State ? $this->state_obj()->name() : '';
165
-    }
166
-
167
-
168
-    /**
169
-     * Gets the state for this venue
170
-     *
171
-     * @return EE_State
172
-     */
173
-    public function state_obj()
174
-    {
175
-        return $this->get_first_related('State');
176
-    }
177
-
178
-
179
-    /**
180
-     * either displays the state abbreviation or the state name, as determined
181
-     * by the "FHEE__EEI_Address__state__use_abbreviation" filter.
182
-     * defaults to abbreviation
183
-     *
184
-     * @return string
185
-     */
186
-    public function state()
187
-    {
188
-        if (apply_filters('FHEE__EEI_Address__state__use_abbreviation', true, $this->state_obj())) {
189
-            return $this->state_abbrev();
190
-        } else {
191
-            return $this->state_name();
192
-        }
193
-    }
194
-
195
-
196
-    /**
197
-     * country_ID
198
-     *
199
-     * @return string
200
-     */
201
-    public function country_ID()
202
-    {
203
-        return $this->get('CNT_ISO');
204
-    }
205
-
206
-
207
-    /**
208
-     * @return string
209
-     */
210
-    public function country_name()
211
-    {
212
-        return $this->country_obj() instanceof EE_Country ? $this->country_obj()->name() : '';
213
-    }
214
-
215
-
216
-    /**
217
-     * Gets the country of this venue
218
-     *
219
-     * @return EE_Country
220
-     */
221
-    public function country_obj()
222
-    {
223
-        return $this->get_first_related('Country');
224
-    }
225
-
226
-
227
-    /**
228
-     * either displays the country ISO2 code or the country name, as determined
229
-     * by the "FHEE__EEI_Address__country__use_abbreviation" filter.
230
-     * defaults to abbreviation
231
-     *
232
-     * @return string
233
-     */
234
-    public function country()
235
-    {
236
-        if (apply_filters('FHEE__EEI_Address__country__use_abbreviation', true, $this->country_obj())) {
237
-            return $this->country_ID();
238
-        } else {
239
-            return $this->country_name();
240
-        }
241
-    }
242
-
243
-
244
-    /**
245
-     * Gets zip
246
-     *
247
-     * @return string
248
-     */
249
-    public function zip()
250
-    {
251
-        return $this->get('VNU_zip');
252
-    }
253
-
254
-
255
-    /**
256
-     * Gets capacity
257
-     *
258
-     * @return int
259
-     */
260
-    public function capacity()
261
-    {
262
-        return $this->get_pretty('VNU_capacity', 'symbol');
263
-    }
264
-
265
-
266
-    /**
267
-     * Gets created
268
-     *
269
-     * @return string
270
-     */
271
-    public function created()
272
-    {
273
-        return $this->get('VNU_created');
274
-    }
275
-
276
-
277
-    /**
278
-     * Gets modified
279
-     *
280
-     * @return string
281
-     */
282
-    public function modified()
283
-    {
284
-        return $this->get('VNU_modified');
285
-    }
286
-
287
-
288
-    /**
289
-     * Gets order
290
-     *
291
-     * @return int
292
-     */
293
-    public function order()
294
-    {
295
-        return $this->get('VNU_order');
296
-    }
297
-
298
-
299
-    /**
300
-     * Gets wp_user
301
-     *
302
-     * @return int
303
-     */
304
-    public function wp_user()
305
-    {
306
-        return $this->get('VNU_wp_user');
307
-    }
308
-
309
-
310
-    /**
311
-     * @return string
312
-     */
313
-    public function virtual_phone()
314
-    {
315
-        return $this->get('VNU_virtual_phone');
316
-    }
317
-
318
-
319
-    /**
320
-     * @return string
321
-     */
322
-    public function virtual_url()
323
-    {
324
-        return $this->get('VNU_virtual_url');
325
-    }
326
-
327
-
328
-    /**
329
-     * @return bool
330
-     */
331
-    public function enable_for_gmap()
332
-    {
333
-        return $this->get('VNU_enable_for_gmap');
334
-    }
335
-
336
-
337
-    /**
338
-     * @return string
339
-     */
340
-    public function google_map_link()
341
-    {
342
-        return $this->get('VNU_google_map_link');
343
-    }
344
-
345
-
346
-    /**
347
-     * Gets all events happening at this venue. Query parameters can be added to
348
-     * fetch a subset of those events.
349
-     *
350
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
351
-     * @param bool  $upcoming
352
-     * @return EE_Event[]
353
-     */
354
-    public function events($query_params = array(), $upcoming = false)
355
-    {
356
-        if ($upcoming) {
357
-            $query_params = array(
358
-                array(
359
-                    'status'                 => 'publish',
360
-                    'Datetime.DTT_EVT_start' => array(
361
-                        '>',
362
-                        EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
363
-                    ),
364
-                ),
365
-            );
366
-        }
367
-        return $this->get_many_related('Event', $query_params);
368
-    }
369
-
370
-
371
-    /**
372
-     * Sets address
373
-     */
374
-    public function set_address($address = '')
375
-    {
376
-        $this->set('VNU_address', $address);
377
-    }
378
-
379
-
380
-    /**
381
-     * @param string $address2
382
-     */
383
-    public function set_address2($address2 = '')
384
-    {
385
-        $this->set('VNU_address2', $address2);
386
-    }
387
-
388
-
389
-    /**
390
-     * @param string $city
391
-     */
392
-    public function set_city($city = '')
393
-    {
394
-        $this->set('VNU_city', $city);
395
-    }
396
-
397
-
398
-    /**
399
-     * @param int $state
400
-     */
401
-    public function set_state_ID($state = 0)
402
-    {
403
-        $this->set('STA_ID', $state);
404
-    }
405
-
406
-
407
-    /**
408
-     * Sets the state, given either a state id or state object
409
-     *
410
-     * @param EE_State /int $state_id_or_obj
411
-     * @return EE_State
412
-     */
413
-    public function set_state_obj($state_id_or_obj)
414
-    {
415
-        return $this->_add_relation_to($state_id_or_obj, 'State');
416
-    }
417
-
418
-
419
-    /**
420
-     * @param int $country_ID
421
-     */
422
-    public function set_country_ID($country_ID = 0)
423
-    {
424
-        $this->set('CNT_ISO', $country_ID);
425
-    }
426
-
427
-
428
-    /**
429
-     * Sets the country on the venue
430
-     *
431
-     * @param EE_Country /string $country_id_or_obj
432
-     * @return EE_Country
433
-     */
434
-    public function set_country_obj($country_id_or_obj)
435
-    {
436
-        return $this->_add_relation_to($country_id_or_obj, 'Country');
437
-    }
438
-
439
-
440
-    /**
441
-     * @param string $zip
442
-     */
443
-    public function set_zip($zip = '')
444
-    {
445
-        $this->set('VNU_zip', $zip);
446
-    }
447
-
448
-
449
-    /**
450
-     * @param int $capacity
451
-     */
452
-    public function set_capacity($capacity = 0)
453
-    {
454
-        $this->set('VNU_capacity', $capacity);
455
-    }
456
-
457
-
458
-    /**
459
-     * @param string $created
460
-     */
461
-    public function set_created($created = '')
462
-    {
463
-        $this->set('VNU_created', $created);
464
-    }
465
-
466
-
467
-    /**
468
-     * @param string $desc
469
-     */
470
-    public function set_description($desc = '')
471
-    {
472
-        $this->set('VNU_desc', $desc);
473
-    }
474
-
475
-
476
-    /**
477
-     * @param string $identifier
478
-     */
479
-    public function set_identifier($identifier = '')
480
-    {
481
-        $this->set('VNU_identifier', $identifier);
482
-    }
483
-
484
-
485
-    /**
486
-     * @param string $modified
487
-     */
488
-    public function set_modified($modified = '')
489
-    {
490
-        $this->set('VNU_modified', $modified);
491
-    }
492
-
493
-
494
-    /**
495
-     * @param string $name
496
-     */
497
-    public function set_name($name = '')
498
-    {
499
-        $this->set('VNU_name', $name);
500
-    }
501
-
502
-
503
-    /**
504
-     * @param int $order
505
-     */
506
-    public function set_order($order = 0)
507
-    {
508
-        $this->set('VNU_order', $order);
509
-    }
510
-
511
-
512
-    /**
513
-     * @param string $phone
514
-     */
515
-    public function set_phone($phone = '')
516
-    {
517
-        $this->set('VNU_phone', $phone);
518
-    }
519
-
520
-
521
-    /**
522
-     * @param int $wp_user
523
-     */
524
-    public function set_wp_user($wp_user = 1)
525
-    {
526
-        $this->set('VNU_wp_user', $wp_user);
527
-    }
528
-
529
-
530
-    /**
531
-     * @param string $url
532
-     */
533
-    public function set_venue_url($url = '')
534
-    {
535
-        $this->set('VNU_url', $url);
536
-    }
537
-
538
-
539
-    /**
540
-     * @param string $phone
541
-     */
542
-    public function set_virtual_phone($phone = '')
543
-    {
544
-        $this->set('VNU_virtual_phone', $phone);
545
-    }
546
-
547
-
548
-    /**
549
-     * @param string $url
550
-     */
551
-    public function set_virtual_url($url = '')
552
-    {
553
-        $this->set('VNU_virtual_url', $url);
554
-    }
555
-
556
-
557
-    /**
558
-     * @param string $enable
559
-     */
560
-    public function set_enable_for_gmap($enable = '')
561
-    {
562
-        $this->set('VNU_enable_for_gmap', $enable);
563
-    }
564
-
565
-
566
-    /**
567
-     * @param string $google_map_link
568
-     */
569
-    public function set_google_map_link($google_map_link = '')
570
-    {
571
-        $this->set('VNU_google_map_link', $google_map_link);
572
-    }
13
+	/**
14
+	 *
15
+	 * @param array  $props_n_values          incoming values
16
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
17
+	 *                                        used.)
18
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
19
+	 *                                        date_format and the second value is the time format
20
+	 * @return EE_Attendee
21
+	 */
22
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
23
+	{
24
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
25
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
26
+	}
27
+
28
+
29
+	/**
30
+	 * @param array  $props_n_values  incoming values from the database
31
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
32
+	 *                                the website will be used.
33
+	 * @return EE_Attendee
34
+	 */
35
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
36
+	{
37
+		return new self($props_n_values, true, $timezone);
38
+	}
39
+
40
+
41
+	/**
42
+	 * Gets name
43
+	 *
44
+	 * @return string
45
+	 */
46
+	public function name()
47
+	{
48
+		return $this->get('VNU_name');
49
+	}
50
+
51
+
52
+	/**
53
+	 * Gets phone
54
+	 *
55
+	 * @return string
56
+	 */
57
+	public function phone()
58
+	{
59
+		return $this->get('VNU_phone');
60
+	}
61
+
62
+
63
+	/**
64
+	 * venue_url
65
+	 *
66
+	 * @return string
67
+	 */
68
+	public function venue_url()
69
+	{
70
+		return $this->get('VNU_url');
71
+	}
72
+
73
+
74
+	/**
75
+	 * Gets desc
76
+	 *
77
+	 * @return string
78
+	 */
79
+	public function description()
80
+	{
81
+		return $this->get('VNU_desc');
82
+	}
83
+
84
+
85
+	/**
86
+	 * Gets short description (AKA: the excerpt)
87
+	 *
88
+	 * @return string
89
+	 */
90
+	public function excerpt()
91
+	{
92
+		return $this->get('VNU_short_desc');
93
+	}
94
+
95
+
96
+	/**
97
+	 * Gets identifier
98
+	 *
99
+	 * @return string
100
+	 */
101
+	public function identifier()
102
+	{
103
+		return $this->get('VNU_identifier');
104
+	}
105
+
106
+
107
+	/**
108
+	 * Gets address
109
+	 *
110
+	 * @return string
111
+	 */
112
+	public function address()
113
+	{
114
+		return $this->get('VNU_address');
115
+	}
116
+
117
+
118
+	/**
119
+	 * Gets address2
120
+	 *
121
+	 * @return string
122
+	 */
123
+	public function address2()
124
+	{
125
+		return $this->get('VNU_address2');
126
+	}
127
+
128
+
129
+	/**
130
+	 * Gets city
131
+	 *
132
+	 * @return string
133
+	 */
134
+	public function city()
135
+	{
136
+		return $this->get('VNU_city');
137
+	}
138
+
139
+	/**
140
+	 * Gets state
141
+	 *
142
+	 * @return int
143
+	 */
144
+	public function state_ID()
145
+	{
146
+		return $this->get('STA_ID');
147
+	}
148
+
149
+
150
+	/**
151
+	 * @return string
152
+	 */
153
+	public function state_abbrev()
154
+	{
155
+		return $this->state_obj() instanceof EE_State ? $this->state_obj()->abbrev() : '';
156
+	}
157
+
158
+
159
+	/**
160
+	 * @return string
161
+	 */
162
+	public function state_name()
163
+	{
164
+		return $this->state_obj() instanceof EE_State ? $this->state_obj()->name() : '';
165
+	}
166
+
167
+
168
+	/**
169
+	 * Gets the state for this venue
170
+	 *
171
+	 * @return EE_State
172
+	 */
173
+	public function state_obj()
174
+	{
175
+		return $this->get_first_related('State');
176
+	}
177
+
178
+
179
+	/**
180
+	 * either displays the state abbreviation or the state name, as determined
181
+	 * by the "FHEE__EEI_Address__state__use_abbreviation" filter.
182
+	 * defaults to abbreviation
183
+	 *
184
+	 * @return string
185
+	 */
186
+	public function state()
187
+	{
188
+		if (apply_filters('FHEE__EEI_Address__state__use_abbreviation', true, $this->state_obj())) {
189
+			return $this->state_abbrev();
190
+		} else {
191
+			return $this->state_name();
192
+		}
193
+	}
194
+
195
+
196
+	/**
197
+	 * country_ID
198
+	 *
199
+	 * @return string
200
+	 */
201
+	public function country_ID()
202
+	{
203
+		return $this->get('CNT_ISO');
204
+	}
205
+
206
+
207
+	/**
208
+	 * @return string
209
+	 */
210
+	public function country_name()
211
+	{
212
+		return $this->country_obj() instanceof EE_Country ? $this->country_obj()->name() : '';
213
+	}
214
+
215
+
216
+	/**
217
+	 * Gets the country of this venue
218
+	 *
219
+	 * @return EE_Country
220
+	 */
221
+	public function country_obj()
222
+	{
223
+		return $this->get_first_related('Country');
224
+	}
225
+
226
+
227
+	/**
228
+	 * either displays the country ISO2 code or the country name, as determined
229
+	 * by the "FHEE__EEI_Address__country__use_abbreviation" filter.
230
+	 * defaults to abbreviation
231
+	 *
232
+	 * @return string
233
+	 */
234
+	public function country()
235
+	{
236
+		if (apply_filters('FHEE__EEI_Address__country__use_abbreviation', true, $this->country_obj())) {
237
+			return $this->country_ID();
238
+		} else {
239
+			return $this->country_name();
240
+		}
241
+	}
242
+
243
+
244
+	/**
245
+	 * Gets zip
246
+	 *
247
+	 * @return string
248
+	 */
249
+	public function zip()
250
+	{
251
+		return $this->get('VNU_zip');
252
+	}
253
+
254
+
255
+	/**
256
+	 * Gets capacity
257
+	 *
258
+	 * @return int
259
+	 */
260
+	public function capacity()
261
+	{
262
+		return $this->get_pretty('VNU_capacity', 'symbol');
263
+	}
264
+
265
+
266
+	/**
267
+	 * Gets created
268
+	 *
269
+	 * @return string
270
+	 */
271
+	public function created()
272
+	{
273
+		return $this->get('VNU_created');
274
+	}
275
+
276
+
277
+	/**
278
+	 * Gets modified
279
+	 *
280
+	 * @return string
281
+	 */
282
+	public function modified()
283
+	{
284
+		return $this->get('VNU_modified');
285
+	}
286
+
287
+
288
+	/**
289
+	 * Gets order
290
+	 *
291
+	 * @return int
292
+	 */
293
+	public function order()
294
+	{
295
+		return $this->get('VNU_order');
296
+	}
297
+
298
+
299
+	/**
300
+	 * Gets wp_user
301
+	 *
302
+	 * @return int
303
+	 */
304
+	public function wp_user()
305
+	{
306
+		return $this->get('VNU_wp_user');
307
+	}
308
+
309
+
310
+	/**
311
+	 * @return string
312
+	 */
313
+	public function virtual_phone()
314
+	{
315
+		return $this->get('VNU_virtual_phone');
316
+	}
317
+
318
+
319
+	/**
320
+	 * @return string
321
+	 */
322
+	public function virtual_url()
323
+	{
324
+		return $this->get('VNU_virtual_url');
325
+	}
326
+
327
+
328
+	/**
329
+	 * @return bool
330
+	 */
331
+	public function enable_for_gmap()
332
+	{
333
+		return $this->get('VNU_enable_for_gmap');
334
+	}
335
+
336
+
337
+	/**
338
+	 * @return string
339
+	 */
340
+	public function google_map_link()
341
+	{
342
+		return $this->get('VNU_google_map_link');
343
+	}
344
+
345
+
346
+	/**
347
+	 * Gets all events happening at this venue. Query parameters can be added to
348
+	 * fetch a subset of those events.
349
+	 *
350
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
351
+	 * @param bool  $upcoming
352
+	 * @return EE_Event[]
353
+	 */
354
+	public function events($query_params = array(), $upcoming = false)
355
+	{
356
+		if ($upcoming) {
357
+			$query_params = array(
358
+				array(
359
+					'status'                 => 'publish',
360
+					'Datetime.DTT_EVT_start' => array(
361
+						'>',
362
+						EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
363
+					),
364
+				),
365
+			);
366
+		}
367
+		return $this->get_many_related('Event', $query_params);
368
+	}
369
+
370
+
371
+	/**
372
+	 * Sets address
373
+	 */
374
+	public function set_address($address = '')
375
+	{
376
+		$this->set('VNU_address', $address);
377
+	}
378
+
379
+
380
+	/**
381
+	 * @param string $address2
382
+	 */
383
+	public function set_address2($address2 = '')
384
+	{
385
+		$this->set('VNU_address2', $address2);
386
+	}
387
+
388
+
389
+	/**
390
+	 * @param string $city
391
+	 */
392
+	public function set_city($city = '')
393
+	{
394
+		$this->set('VNU_city', $city);
395
+	}
396
+
397
+
398
+	/**
399
+	 * @param int $state
400
+	 */
401
+	public function set_state_ID($state = 0)
402
+	{
403
+		$this->set('STA_ID', $state);
404
+	}
405
+
406
+
407
+	/**
408
+	 * Sets the state, given either a state id or state object
409
+	 *
410
+	 * @param EE_State /int $state_id_or_obj
411
+	 * @return EE_State
412
+	 */
413
+	public function set_state_obj($state_id_or_obj)
414
+	{
415
+		return $this->_add_relation_to($state_id_or_obj, 'State');
416
+	}
417
+
418
+
419
+	/**
420
+	 * @param int $country_ID
421
+	 */
422
+	public function set_country_ID($country_ID = 0)
423
+	{
424
+		$this->set('CNT_ISO', $country_ID);
425
+	}
426
+
427
+
428
+	/**
429
+	 * Sets the country on the venue
430
+	 *
431
+	 * @param EE_Country /string $country_id_or_obj
432
+	 * @return EE_Country
433
+	 */
434
+	public function set_country_obj($country_id_or_obj)
435
+	{
436
+		return $this->_add_relation_to($country_id_or_obj, 'Country');
437
+	}
438
+
439
+
440
+	/**
441
+	 * @param string $zip
442
+	 */
443
+	public function set_zip($zip = '')
444
+	{
445
+		$this->set('VNU_zip', $zip);
446
+	}
447
+
448
+
449
+	/**
450
+	 * @param int $capacity
451
+	 */
452
+	public function set_capacity($capacity = 0)
453
+	{
454
+		$this->set('VNU_capacity', $capacity);
455
+	}
456
+
457
+
458
+	/**
459
+	 * @param string $created
460
+	 */
461
+	public function set_created($created = '')
462
+	{
463
+		$this->set('VNU_created', $created);
464
+	}
465
+
466
+
467
+	/**
468
+	 * @param string $desc
469
+	 */
470
+	public function set_description($desc = '')
471
+	{
472
+		$this->set('VNU_desc', $desc);
473
+	}
474
+
475
+
476
+	/**
477
+	 * @param string $identifier
478
+	 */
479
+	public function set_identifier($identifier = '')
480
+	{
481
+		$this->set('VNU_identifier', $identifier);
482
+	}
483
+
484
+
485
+	/**
486
+	 * @param string $modified
487
+	 */
488
+	public function set_modified($modified = '')
489
+	{
490
+		$this->set('VNU_modified', $modified);
491
+	}
492
+
493
+
494
+	/**
495
+	 * @param string $name
496
+	 */
497
+	public function set_name($name = '')
498
+	{
499
+		$this->set('VNU_name', $name);
500
+	}
501
+
502
+
503
+	/**
504
+	 * @param int $order
505
+	 */
506
+	public function set_order($order = 0)
507
+	{
508
+		$this->set('VNU_order', $order);
509
+	}
510
+
511
+
512
+	/**
513
+	 * @param string $phone
514
+	 */
515
+	public function set_phone($phone = '')
516
+	{
517
+		$this->set('VNU_phone', $phone);
518
+	}
519
+
520
+
521
+	/**
522
+	 * @param int $wp_user
523
+	 */
524
+	public function set_wp_user($wp_user = 1)
525
+	{
526
+		$this->set('VNU_wp_user', $wp_user);
527
+	}
528
+
529
+
530
+	/**
531
+	 * @param string $url
532
+	 */
533
+	public function set_venue_url($url = '')
534
+	{
535
+		$this->set('VNU_url', $url);
536
+	}
537
+
538
+
539
+	/**
540
+	 * @param string $phone
541
+	 */
542
+	public function set_virtual_phone($phone = '')
543
+	{
544
+		$this->set('VNU_virtual_phone', $phone);
545
+	}
546
+
547
+
548
+	/**
549
+	 * @param string $url
550
+	 */
551
+	public function set_virtual_url($url = '')
552
+	{
553
+		$this->set('VNU_virtual_url', $url);
554
+	}
555
+
556
+
557
+	/**
558
+	 * @param string $enable
559
+	 */
560
+	public function set_enable_for_gmap($enable = '')
561
+	{
562
+		$this->set('VNU_enable_for_gmap', $enable);
563
+	}
564
+
565
+
566
+	/**
567
+	 * @param string $google_map_link
568
+	 */
569
+	public function set_google_map_link($google_map_link = '')
570
+	{
571
+		$this->set('VNU_google_map_link', $google_map_link);
572
+	}
573 573
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Registration.class.php 1 patch
Indentation   +2067 added lines, -2067 removed lines patch added patch discarded remove patch
@@ -17,2071 +17,2071 @@
 block discarded – undo
17 17
 {
18 18
 
19 19
 
20
-    /**
21
-     * Used to reference when a registration has never been checked in.
22
-     *
23
-     * @deprecated use \EE_Checkin::status_checked_never instead
24
-     * @type int
25
-     */
26
-    const checkin_status_never = 2;
27
-
28
-    /**
29
-     * Used to reference when a registration has been checked in.
30
-     *
31
-     * @deprecated use \EE_Checkin::status_checked_in instead
32
-     * @type int
33
-     */
34
-    const checkin_status_in = 1;
35
-
36
-
37
-    /**
38
-     * Used to reference when a registration has been checked out.
39
-     *
40
-     * @deprecated use \EE_Checkin::status_checked_out instead
41
-     * @type int
42
-     */
43
-    const checkin_status_out = 0;
44
-
45
-
46
-    /**
47
-     * extra meta key for tracking reg status os trashed registrations
48
-     *
49
-     * @type string
50
-     */
51
-    const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
52
-
53
-
54
-    /**
55
-     * extra meta key for tracking if registration has reserved ticket
56
-     *
57
-     * @type string
58
-     */
59
-    const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
60
-
61
-
62
-    /**
63
-     * @param array  $props_n_values          incoming values
64
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
65
-     *                                        used.)
66
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
67
-     *                                        date_format and the second value is the time format
68
-     * @return EE_Registration
69
-     * @throws EE_Error
70
-     */
71
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
72
-    {
73
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
74
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
75
-    }
76
-
77
-
78
-    /**
79
-     * @param array  $props_n_values  incoming values from the database
80
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
81
-     *                                the website will be used.
82
-     * @return EE_Registration
83
-     */
84
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
85
-    {
86
-        return new self($props_n_values, true, $timezone);
87
-    }
88
-
89
-
90
-    /**
91
-     *        Set Event ID
92
-     *
93
-     * @param        int $EVT_ID Event ID
94
-     * @throws EE_Error
95
-     * @throws RuntimeException
96
-     */
97
-    public function set_event($EVT_ID = 0)
98
-    {
99
-        $this->set('EVT_ID', $EVT_ID);
100
-    }
101
-
102
-
103
-    /**
104
-     * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
105
-     * be routed to internal methods
106
-     *
107
-     * @param string $field_name
108
-     * @param mixed  $field_value
109
-     * @param bool   $use_default
110
-     * @throws EE_Error
111
-     * @throws EntityNotFoundException
112
-     * @throws InvalidArgumentException
113
-     * @throws InvalidDataTypeException
114
-     * @throws InvalidInterfaceException
115
-     * @throws ReflectionException
116
-     * @throws RuntimeException
117
-     */
118
-    public function set($field_name, $field_value, $use_default = false)
119
-    {
120
-        switch ($field_name) {
121
-            case 'REG_code':
122
-                if (! empty($field_value) && $this->reg_code() === null) {
123
-                    $this->set_reg_code($field_value, $use_default);
124
-                }
125
-                break;
126
-            case 'STS_ID':
127
-                $this->set_status($field_value, $use_default);
128
-                break;
129
-            default:
130
-                parent::set($field_name, $field_value, $use_default);
131
-        }
132
-    }
133
-
134
-
135
-    /**
136
-     * Set Status ID
137
-     * updates the registration status and ALSO...
138
-     * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
139
-     * calls release_registration_space() if the reg status changes FROM approved to any other reg status
140
-     *
141
-     * @param string                $new_STS_ID
142
-     * @param boolean               $use_default
143
-     * @param ContextInterface|null $context
144
-     * @return bool
145
-     * @throws EE_Error
146
-     * @throws EntityNotFoundException
147
-     * @throws InvalidArgumentException
148
-     * @throws ReflectionException
149
-     * @throws RuntimeException
150
-     * @throws InvalidDataTypeException
151
-     * @throws InvalidInterfaceException
152
-     */
153
-    public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
154
-    {
155
-        // get current REG_Status
156
-        $old_STS_ID = $this->status_ID();
157
-        // if status has changed
158
-        if ($old_STS_ID !== $new_STS_ID // and that status has actually changed
159
-            && ! empty($old_STS_ID) // and that old status is actually set
160
-            && ! empty($new_STS_ID) // as well as the new status
161
-            && $this->ID() // ensure registration is in the db
162
-        ) {
163
-            // TO approved
164
-            if ($new_STS_ID === EEM_Registration::status_id_approved) {
165
-                // reserve a space by incrementing ticket and datetime sold values
166
-                $this->_reserve_registration_space();
167
-                do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
168
-                // OR FROM  approved
169
-            } elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
170
-                // release a space by decrementing ticket and datetime sold values
171
-                $this->_release_registration_space();
172
-                do_action(
173
-                    'AHEE__EE_Registration__set_status__from_approved',
174
-                    $this,
175
-                    $old_STS_ID,
176
-                    $new_STS_ID,
177
-                    $context
178
-                );
179
-            }
180
-            // update status
181
-            parent::set('STS_ID', $new_STS_ID, $use_default);
182
-            $this->_update_if_canceled_or_declined($new_STS_ID, $old_STS_ID, $context);
183
-            if ($this->statusChangeUpdatesTransaction($context)) {
184
-                $this->updateTransactionAfterStatusChange();
185
-            }
186
-            do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
187
-            return true;
188
-        }
189
-        // even though the old value matches the new value, it's still good to
190
-        // allow the parent set method to have a say
191
-        parent::set('STS_ID', $new_STS_ID, $use_default);
192
-        return true;
193
-    }
194
-
195
-
196
-    /**
197
-     * update REGs and TXN when cancelled or declined registrations involved
198
-     *
199
-     * @param string                $new_STS_ID
200
-     * @param string                $old_STS_ID
201
-     * @param ContextInterface|null $context
202
-     * @throws EE_Error
203
-     * @throws InvalidArgumentException
204
-     * @throws InvalidDataTypeException
205
-     * @throws InvalidInterfaceException
206
-     * @throws ReflectionException
207
-     */
208
-    private function _update_if_canceled_or_declined($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
209
-    {
210
-        // these reg statuses should not be considered in any calculations involving monies owing
211
-        $closed_reg_statuses = EEM_Registration::closed_reg_statuses();
212
-        // true if registration has been cancelled or declined
213
-        $this->updateIfCanceled(
214
-            $closed_reg_statuses,
215
-            $new_STS_ID,
216
-            $old_STS_ID,
217
-            $context
218
-        );
219
-        $this->updateIfDeclined(
220
-            $closed_reg_statuses,
221
-            $new_STS_ID,
222
-            $old_STS_ID,
223
-            $context
224
-        );
225
-    }
226
-
227
-
228
-    /**
229
-     * update REGs and TXN when cancelled or declined registrations involved
230
-     *
231
-     * @param array                 $closed_reg_statuses
232
-     * @param string                $new_STS_ID
233
-     * @param string                $old_STS_ID
234
-     * @param ContextInterface|null $context
235
-     * @throws EE_Error
236
-     * @throws InvalidArgumentException
237
-     * @throws InvalidDataTypeException
238
-     * @throws InvalidInterfaceException
239
-     * @throws ReflectionException
240
-     */
241
-    private function updateIfCanceled(
242
-        array $closed_reg_statuses,
243
-        $new_STS_ID,
244
-        $old_STS_ID,
245
-        ContextInterface $context = null
246
-    ) {
247
-        // true if registration has been cancelled or declined
248
-        if (in_array($new_STS_ID, $closed_reg_statuses, true)
249
-            && ! in_array($old_STS_ID, $closed_reg_statuses, true)
250
-        ) {
251
-            /** @type EE_Registration_Processor $registration_processor */
252
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
253
-            /** @type EE_Transaction_Processor $transaction_processor */
254
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
255
-            // cancelled or declined registration
256
-            $registration_processor->update_registration_after_being_canceled_or_declined(
257
-                $this,
258
-                $closed_reg_statuses
259
-            );
260
-            $transaction_processor->update_transaction_after_canceled_or_declined_registration(
261
-                $this,
262
-                $closed_reg_statuses,
263
-                false
264
-            );
265
-            do_action(
266
-                'AHEE__EE_Registration__set_status__canceled_or_declined',
267
-                $this,
268
-                $old_STS_ID,
269
-                $new_STS_ID,
270
-                $context
271
-            );
272
-            return;
273
-        }
274
-    }
275
-
276
-
277
-    /**
278
-     * update REGs and TXN when cancelled or declined registrations involved
279
-     *
280
-     * @param array                 $closed_reg_statuses
281
-     * @param string                $new_STS_ID
282
-     * @param string                $old_STS_ID
283
-     * @param ContextInterface|null $context
284
-     * @throws EE_Error
285
-     * @throws InvalidArgumentException
286
-     * @throws InvalidDataTypeException
287
-     * @throws InvalidInterfaceException
288
-     * @throws ReflectionException
289
-     */
290
-    private function updateIfDeclined(
291
-        array $closed_reg_statuses,
292
-        $new_STS_ID,
293
-        $old_STS_ID,
294
-        ContextInterface $context = null
295
-    ) {
296
-        // true if reinstating cancelled or declined registration
297
-        if (in_array($old_STS_ID, $closed_reg_statuses, true)
298
-            && ! in_array($new_STS_ID, $closed_reg_statuses, true)
299
-        ) {
300
-            /** @type EE_Registration_Processor $registration_processor */
301
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
302
-            /** @type EE_Transaction_Processor $transaction_processor */
303
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
304
-            // reinstating cancelled or declined registration
305
-            $registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
306
-                $this,
307
-                $closed_reg_statuses
308
-            );
309
-            $transaction_processor->update_transaction_after_reinstating_canceled_registration(
310
-                $this,
311
-                $closed_reg_statuses,
312
-                false
313
-            );
314
-            do_action(
315
-                'AHEE__EE_Registration__set_status__after_reinstated',
316
-                $this,
317
-                $old_STS_ID,
318
-                $new_STS_ID,
319
-                $context
320
-            );
321
-        }
322
-    }
323
-
324
-
325
-    /**
326
-     * @param ContextInterface|null $context
327
-     * @return bool
328
-     */
329
-    private function statusChangeUpdatesTransaction(ContextInterface $context = null)
330
-    {
331
-        $contexts_that_do_not_update_transaction = (array) apply_filters(
332
-            'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
333
-            array('spco_reg_step_attendee_information_process_registrations'),
334
-            $context,
335
-            $this
336
-        );
337
-        return ! (
338
-            $context instanceof ContextInterface
339
-            && in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
340
-        );
341
-    }
342
-
343
-
344
-    /**
345
-     * @throws EE_Error
346
-     * @throws EntityNotFoundException
347
-     * @throws InvalidArgumentException
348
-     * @throws InvalidDataTypeException
349
-     * @throws InvalidInterfaceException
350
-     * @throws ReflectionException
351
-     * @throws RuntimeException
352
-     */
353
-    private function updateTransactionAfterStatusChange()
354
-    {
355
-        /** @type EE_Transaction_Payments $transaction_payments */
356
-        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
357
-        $transaction_payments->recalculate_transaction_total($this->transaction(), false);
358
-        $this->transaction()->update_status_based_on_total_paid(true);
359
-    }
360
-
361
-
362
-    /**
363
-     *        get Status ID
364
-     */
365
-    public function status_ID()
366
-    {
367
-        return $this->get('STS_ID');
368
-    }
369
-
370
-
371
-    /**
372
-     * Gets the ticket this registration is for
373
-     *
374
-     * @param boolean $include_archived whether to include archived tickets or not.
375
-     *
376
-     * @return EE_Ticket|EE_Base_Class
377
-     * @throws EE_Error
378
-     */
379
-    public function ticket($include_archived = true)
380
-    {
381
-        $query_params = array();
382
-        if ($include_archived) {
383
-            $query_params['default_where_conditions'] = 'none';
384
-        }
385
-        return $this->get_first_related('Ticket', $query_params);
386
-    }
387
-
388
-
389
-    /**
390
-     * Gets the event this registration is for
391
-     *
392
-     * @return EE_Event
393
-     * @throws EE_Error
394
-     * @throws EntityNotFoundException
395
-     */
396
-    public function event()
397
-    {
398
-        $event = $this->get_first_related('Event');
399
-        if (! $event instanceof \EE_Event) {
400
-            throw new EntityNotFoundException('Event ID', $this->event_ID());
401
-        }
402
-        return $event;
403
-    }
404
-
405
-
406
-    /**
407
-     * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
408
-     * with the author of the event this registration is for.
409
-     *
410
-     * @since 4.5.0
411
-     * @return int
412
-     * @throws EE_Error
413
-     * @throws EntityNotFoundException
414
-     */
415
-    public function wp_user()
416
-    {
417
-        $event = $this->event();
418
-        if ($event instanceof EE_Event) {
419
-            return $event->wp_user();
420
-        }
421
-        return 0;
422
-    }
423
-
424
-
425
-    /**
426
-     * increments this registration's related ticket sold and corresponding datetime sold values
427
-     *
428
-     * @return void
429
-     * @throws DomainException
430
-     * @throws EE_Error
431
-     * @throws EntityNotFoundException
432
-     * @throws InvalidArgumentException
433
-     * @throws InvalidDataTypeException
434
-     * @throws InvalidInterfaceException
435
-     * @throws ReflectionException
436
-     * @throws UnexpectedEntityException
437
-     */
438
-    private function _reserve_registration_space()
439
-    {
440
-        // reserved ticket and datetime counts will be decremented as sold counts are incremented
441
-        // so stop tracking that this reg has a ticket reserved
442
-        $this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
443
-        $ticket = $this->ticket();
444
-        $ticket->increase_sold();
445
-        $ticket->save();
446
-        // possibly set event status to sold out
447
-        $this->event()->perform_sold_out_status_check();
448
-    }
449
-
450
-
451
-    /**
452
-     * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
453
-     *
454
-     * @return void
455
-     * @throws DomainException
456
-     * @throws EE_Error
457
-     * @throws EntityNotFoundException
458
-     * @throws InvalidArgumentException
459
-     * @throws InvalidDataTypeException
460
-     * @throws InvalidInterfaceException
461
-     * @throws ReflectionException
462
-     * @throws UnexpectedEntityException
463
-     */
464
-    private function _release_registration_space()
465
-    {
466
-        $ticket = $this->ticket();
467
-        $ticket->decrease_sold();
468
-        $ticket->save();
469
-        // possibly change event status from sold out back to previous status
470
-        $this->event()->perform_sold_out_status_check();
471
-    }
472
-
473
-
474
-    /**
475
-     * tracks this registration's ticket reservation in extra meta
476
-     * and can increment related ticket reserved and corresponding datetime reserved values
477
-     *
478
-     * @param bool $update_ticket if true, will increment ticket and datetime reserved count
479
-     * @return void
480
-     * @throws EE_Error
481
-     * @throws InvalidArgumentException
482
-     * @throws InvalidDataTypeException
483
-     * @throws InvalidInterfaceException
484
-     * @throws ReflectionException
485
-     */
486
-    public function reserve_ticket($update_ticket = false, $source = 'unknown')
487
-    {
488
-        // only reserve ticket if space is not currently reserved
489
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
490
-            $this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
491
-            // IMPORTANT !!!
492
-            // although checking $update_ticket first would be more efficient,
493
-            // we NEED to ALWAYS call update_extra_meta(), which is why that is done first
494
-            if ($this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true)
495
-                && $update_ticket
496
-            ) {
497
-                $ticket = $this->ticket();
498
-                $ticket->increase_reserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
499
-                $ticket->save();
500
-            }
501
-        }
502
-    }
503
-
504
-
505
-    /**
506
-     * stops tracking this registration's ticket reservation in extra meta
507
-     * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
508
-     *
509
-     * @param bool $update_ticket if true, will decrement ticket and datetime reserved count
510
-     * @return void
511
-     * @throws EE_Error
512
-     * @throws InvalidArgumentException
513
-     * @throws InvalidDataTypeException
514
-     * @throws InvalidInterfaceException
515
-     * @throws ReflectionException
516
-     */
517
-    public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
518
-    {
519
-        // only release ticket if space is currently reserved
520
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
521
-            $this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
522
-            // IMPORTANT !!!
523
-            // although checking $update_ticket first would be more efficient,
524
-            // we NEED to ALWAYS call update_extra_meta(), which is why that is done first
525
-            if ($this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false)
526
-                && $update_ticket
527
-            ) {
528
-                $ticket = $this->ticket();
529
-                $ticket->decrease_reserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
530
-                $ticket->save();
531
-            }
532
-        }
533
-    }
534
-
535
-
536
-    /**
537
-     * Set Attendee ID
538
-     *
539
-     * @param        int $ATT_ID Attendee ID
540
-     * @throws EE_Error
541
-     * @throws RuntimeException
542
-     */
543
-    public function set_attendee_id($ATT_ID = 0)
544
-    {
545
-        $this->set('ATT_ID', $ATT_ID);
546
-    }
547
-
548
-
549
-    /**
550
-     *        Set Transaction ID
551
-     *
552
-     * @param        int $TXN_ID Transaction ID
553
-     * @throws EE_Error
554
-     * @throws RuntimeException
555
-     */
556
-    public function set_transaction_id($TXN_ID = 0)
557
-    {
558
-        $this->set('TXN_ID', $TXN_ID);
559
-    }
560
-
561
-
562
-    /**
563
-     *        Set Session
564
-     *
565
-     * @param    string $REG_session PHP Session ID
566
-     * @throws EE_Error
567
-     * @throws RuntimeException
568
-     */
569
-    public function set_session($REG_session = '')
570
-    {
571
-        $this->set('REG_session', $REG_session);
572
-    }
573
-
574
-
575
-    /**
576
-     *        Set Registration URL Link
577
-     *
578
-     * @param    string $REG_url_link Registration URL Link
579
-     * @throws EE_Error
580
-     * @throws RuntimeException
581
-     */
582
-    public function set_reg_url_link($REG_url_link = '')
583
-    {
584
-        $this->set('REG_url_link', $REG_url_link);
585
-    }
586
-
587
-
588
-    /**
589
-     *        Set Attendee Counter
590
-     *
591
-     * @param        int $REG_count Primary Attendee
592
-     * @throws EE_Error
593
-     * @throws RuntimeException
594
-     */
595
-    public function set_count($REG_count = 1)
596
-    {
597
-        $this->set('REG_count', $REG_count);
598
-    }
599
-
600
-
601
-    /**
602
-     *        Set Group Size
603
-     *
604
-     * @param        boolean $REG_group_size Group Registration
605
-     * @throws EE_Error
606
-     * @throws RuntimeException
607
-     */
608
-    public function set_group_size($REG_group_size = false)
609
-    {
610
-        $this->set('REG_group_size', $REG_group_size);
611
-    }
612
-
613
-
614
-    /**
615
-     *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
616
-     *    EEM_Registration::status_id_not_approved
617
-     *
618
-     * @return        boolean
619
-     */
620
-    public function is_not_approved()
621
-    {
622
-        return $this->status_ID() == EEM_Registration::status_id_not_approved ? true : false;
623
-    }
624
-
625
-
626
-    /**
627
-     *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
628
-     *    EEM_Registration::status_id_pending_payment
629
-     *
630
-     * @return        boolean
631
-     */
632
-    public function is_pending_payment()
633
-    {
634
-        return $this->status_ID() == EEM_Registration::status_id_pending_payment ? true : false;
635
-    }
636
-
637
-
638
-    /**
639
-     *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
640
-     *
641
-     * @return        boolean
642
-     */
643
-    public function is_approved()
644
-    {
645
-        return $this->status_ID() == EEM_Registration::status_id_approved ? true : false;
646
-    }
647
-
648
-
649
-    /**
650
-     *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
651
-     *
652
-     * @return        boolean
653
-     */
654
-    public function is_cancelled()
655
-    {
656
-        return $this->status_ID() == EEM_Registration::status_id_cancelled ? true : false;
657
-    }
658
-
659
-
660
-    /**
661
-     *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
662
-     *
663
-     * @return        boolean
664
-     */
665
-    public function is_declined()
666
-    {
667
-        return $this->status_ID() == EEM_Registration::status_id_declined ? true : false;
668
-    }
669
-
670
-
671
-    /**
672
-     *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
673
-     *    EEM_Registration::status_id_incomplete
674
-     *
675
-     * @return        boolean
676
-     */
677
-    public function is_incomplete()
678
-    {
679
-        return $this->status_ID() == EEM_Registration::status_id_incomplete ? true : false;
680
-    }
681
-
682
-
683
-    /**
684
-     *        Set Registration Date
685
-     *
686
-     * @param        mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
687
-     *                                                 Date
688
-     * @throws EE_Error
689
-     * @throws RuntimeException
690
-     */
691
-    public function set_reg_date($REG_date = false)
692
-    {
693
-        $this->set('REG_date', $REG_date);
694
-    }
695
-
696
-
697
-    /**
698
-     *    Set final price owing for this registration after all ticket/price modifications
699
-     *
700
-     * @access    public
701
-     * @param    float $REG_final_price
702
-     * @throws EE_Error
703
-     * @throws RuntimeException
704
-     */
705
-    public function set_final_price($REG_final_price = 0.00)
706
-    {
707
-        $this->set('REG_final_price', $REG_final_price);
708
-    }
709
-
710
-
711
-    /**
712
-     *    Set amount paid towards this registration's final price
713
-     *
714
-     * @access    public
715
-     * @param    float $REG_paid
716
-     * @throws EE_Error
717
-     * @throws RuntimeException
718
-     */
719
-    public function set_paid($REG_paid = 0.00)
720
-    {
721
-        $this->set('REG_paid', $REG_paid);
722
-    }
723
-
724
-
725
-    /**
726
-     *        Attendee Is Going
727
-     *
728
-     * @param        boolean $REG_att_is_going Attendee Is Going
729
-     * @throws EE_Error
730
-     * @throws RuntimeException
731
-     */
732
-    public function set_att_is_going($REG_att_is_going = false)
733
-    {
734
-        $this->set('REG_att_is_going', $REG_att_is_going);
735
-    }
736
-
737
-
738
-    /**
739
-     * Gets the related attendee
740
-     *
741
-     * @return EE_Attendee
742
-     * @throws EE_Error
743
-     */
744
-    public function attendee()
745
-    {
746
-        return $this->get_first_related('Attendee');
747
-    }
748
-
749
-
750
-    /**
751
-     *        get Event ID
752
-     */
753
-    public function event_ID()
754
-    {
755
-        return $this->get('EVT_ID');
756
-    }
757
-
758
-
759
-    /**
760
-     *        get Event ID
761
-     */
762
-    public function event_name()
763
-    {
764
-        $event = $this->event_obj();
765
-        if ($event) {
766
-            return $event->name();
767
-        } else {
768
-            return null;
769
-        }
770
-    }
771
-
772
-
773
-    /**
774
-     * Fetches the event this registration is for
775
-     *
776
-     * @return EE_Event
777
-     * @throws EE_Error
778
-     */
779
-    public function event_obj()
780
-    {
781
-        return $this->get_first_related('Event');
782
-    }
783
-
784
-
785
-    /**
786
-     *        get Attendee ID
787
-     */
788
-    public function attendee_ID()
789
-    {
790
-        return $this->get('ATT_ID');
791
-    }
792
-
793
-
794
-    /**
795
-     *        get PHP Session ID
796
-     */
797
-    public function session_ID()
798
-    {
799
-        return $this->get('REG_session');
800
-    }
801
-
802
-
803
-    /**
804
-     * Gets the string which represents the URL trigger for the receipt template in the message template system.
805
-     *
806
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
807
-     * @return string
808
-     */
809
-    public function receipt_url($messenger = 'html')
810
-    {
811
-
812
-        /**
813
-         * The below will be deprecated one version after this.  We check first if there is a custom receipt template
814
-         * already in use on old system.  If there is then we just return the standard url for it.
815
-         *
816
-         * @since 4.5.0
817
-         */
818
-        $template_relative_path = 'modules/gateways/Invoice/lib/templates/receipt_body.template.php';
819
-        $has_custom = EEH_Template::locate_template(
820
-            $template_relative_path,
821
-            array(),
822
-            true,
823
-            true,
824
-            true
825
-        );
826
-
827
-        if ($has_custom) {
828
-            return add_query_arg(array('receipt' => 'true'), $this->invoice_url('launch'));
829
-        }
830
-        return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets the string which represents the URL trigger for the invoice template in the message template system.
836
-     *
837
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
838
-     * @return string
839
-     * @throws EE_Error
840
-     */
841
-    public function invoice_url($messenger = 'html')
842
-    {
843
-        /**
844
-         * The below will be deprecated one version after this.  We check first if there is a custom invoice template
845
-         * already in use on old system.  If there is then we just return the standard url for it.
846
-         *
847
-         * @since 4.5.0
848
-         */
849
-        $template_relative_path = 'modules/gateways/Invoice/lib/templates/invoice_body.template.php';
850
-        $has_custom = EEH_Template::locate_template(
851
-            $template_relative_path,
852
-            array(),
853
-            true,
854
-            true,
855
-            true
856
-        );
857
-
858
-        if ($has_custom) {
859
-            if ($messenger == 'html') {
860
-                return $this->invoice_url('launch');
861
-            }
862
-            $route = $messenger == 'download' || $messenger == 'pdf' ? 'download_invoice' : 'launch_invoice';
863
-
864
-            $query_args = array('ee' => $route, 'id' => $this->reg_url_link());
865
-            if ($messenger == 'html') {
866
-                $query_args['html'] = true;
867
-            }
868
-            return add_query_arg($query_args, get_permalink(EE_Registry::instance()->CFG->core->thank_you_page_id));
869
-        }
870
-        return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
871
-    }
872
-
873
-
874
-    /**
875
-     * get Registration URL Link
876
-     *
877
-     * @access public
878
-     * @return string
879
-     * @throws EE_Error
880
-     */
881
-    public function reg_url_link()
882
-    {
883
-        return (string) $this->get('REG_url_link');
884
-    }
885
-
886
-
887
-    /**
888
-     * Echoes out invoice_url()
889
-     *
890
-     * @param string $type 'download','launch', or 'html' (default is 'launch')
891
-     * @return void
892
-     * @throws EE_Error
893
-     */
894
-    public function e_invoice_url($type = 'launch')
895
-    {
896
-        echo $this->invoice_url($type);
897
-    }
898
-
899
-
900
-    /**
901
-     * Echoes out payment_overview_url
902
-     */
903
-    public function e_payment_overview_url()
904
-    {
905
-        echo $this->payment_overview_url();
906
-    }
907
-
908
-
909
-    /**
910
-     * Gets the URL for the checkout payment options reg step
911
-     * with this registration's REG_url_link added as a query parameter
912
-     *
913
-     * @param bool $clear_session Set to true when you want to clear the session on revisiting the
914
-     *                            payment overview url.
915
-     * @return string
916
-     * @throws InvalidInterfaceException
917
-     * @throws InvalidDataTypeException
918
-     * @throws EE_Error
919
-     * @throws InvalidArgumentException
920
-     */
921
-    public function payment_overview_url($clear_session = false)
922
-    {
923
-        return add_query_arg(
924
-            (array) apply_filters(
925
-                'FHEE__EE_Registration__payment_overview_url__query_args',
926
-                array(
927
-                    'e_reg_url_link' => $this->reg_url_link(),
928
-                    'step'           => 'payment_options',
929
-                    'revisit'        => true,
930
-                    'clear_session'  => (bool) $clear_session,
931
-                ),
932
-                $this
933
-            ),
934
-            EE_Registry::instance()->CFG->core->reg_page_url()
935
-        );
936
-    }
937
-
938
-
939
-    /**
940
-     * Gets the URL for the checkout attendee information reg step
941
-     * with this registration's REG_url_link added as a query parameter
942
-     *
943
-     * @return string
944
-     * @throws InvalidInterfaceException
945
-     * @throws InvalidDataTypeException
946
-     * @throws EE_Error
947
-     * @throws InvalidArgumentException
948
-     */
949
-    public function edit_attendee_information_url()
950
-    {
951
-        return add_query_arg(
952
-            (array) apply_filters(
953
-                'FHEE__EE_Registration__edit_attendee_information_url__query_args',
954
-                array(
955
-                    'e_reg_url_link' => $this->reg_url_link(),
956
-                    'step'           => 'attendee_information',
957
-                    'revisit'        => true,
958
-                ),
959
-                $this
960
-            ),
961
-            EE_Registry::instance()->CFG->core->reg_page_url()
962
-        );
963
-    }
964
-
965
-
966
-    /**
967
-     * Simply generates and returns the appropriate admin_url link to edit this registration
968
-     *
969
-     * @return string
970
-     * @throws EE_Error
971
-     */
972
-    public function get_admin_edit_url()
973
-    {
974
-        return EEH_URL::add_query_args_and_nonce(
975
-            array(
976
-                'page'    => 'espresso_registrations',
977
-                'action'  => 'view_registration',
978
-                '_REG_ID' => $this->ID(),
979
-            ),
980
-            admin_url('admin.php')
981
-        );
982
-    }
983
-
984
-
985
-    /**
986
-     *    is_primary_registrant?
987
-     */
988
-    public function is_primary_registrant()
989
-    {
990
-        return $this->get('REG_count') == 1 ? true : false;
991
-    }
992
-
993
-
994
-    /**
995
-     * This returns the primary registration object for this registration group (which may be this object).
996
-     *
997
-     * @return EE_Registration
998
-     * @throws EE_Error
999
-     */
1000
-    public function get_primary_registration()
1001
-    {
1002
-        if ($this->is_primary_registrant()) {
1003
-            return $this;
1004
-        }
1005
-
1006
-        // k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1007
-        /** @var EE_Registration $primary_registrant */
1008
-        $primary_registrant = EEM_Registration::instance()->get_one(
1009
-            array(
1010
-                array(
1011
-                    'TXN_ID'    => $this->transaction_ID(),
1012
-                    'REG_count' => 1,
1013
-                ),
1014
-            )
1015
-        );
1016
-        return $primary_registrant;
1017
-    }
1018
-
1019
-
1020
-    /**
1021
-     *        get  Attendee Number
1022
-     *
1023
-     * @access        public
1024
-     */
1025
-    public function count()
1026
-    {
1027
-        return $this->get('REG_count');
1028
-    }
1029
-
1030
-
1031
-    /**
1032
-     *        get Group Size
1033
-     */
1034
-    public function group_size()
1035
-    {
1036
-        return $this->get('REG_group_size');
1037
-    }
1038
-
1039
-
1040
-    /**
1041
-     *        get Registration Date
1042
-     */
1043
-    public function date()
1044
-    {
1045
-        return $this->get('REG_date');
1046
-    }
1047
-
1048
-
1049
-    /**
1050
-     * gets a pretty date
1051
-     *
1052
-     * @param string $date_format
1053
-     * @param string $time_format
1054
-     * @return string
1055
-     * @throws EE_Error
1056
-     */
1057
-    public function pretty_date($date_format = null, $time_format = null)
1058
-    {
1059
-        return $this->get_datetime('REG_date', $date_format, $time_format);
1060
-    }
1061
-
1062
-
1063
-    /**
1064
-     * final_price
1065
-     * the registration's share of the transaction total, so that the
1066
-     * sum of all the transaction's REG_final_prices equal the transaction's total
1067
-     *
1068
-     * @return float
1069
-     * @throws EE_Error
1070
-     */
1071
-    public function final_price()
1072
-    {
1073
-        return $this->get('REG_final_price');
1074
-    }
1075
-
1076
-
1077
-    /**
1078
-     * pretty_final_price
1079
-     *  final price as formatted string, with correct decimal places and currency symbol
1080
-     *
1081
-     * @return string
1082
-     * @throws EE_Error
1083
-     */
1084
-    public function pretty_final_price()
1085
-    {
1086
-        return $this->get_pretty('REG_final_price');
1087
-    }
1088
-
1089
-
1090
-    /**
1091
-     * get paid (yeah)
1092
-     *
1093
-     * @return float
1094
-     * @throws EE_Error
1095
-     */
1096
-    public function paid()
1097
-    {
1098
-        return $this->get('REG_paid');
1099
-    }
1100
-
1101
-
1102
-    /**
1103
-     * pretty_paid
1104
-     *
1105
-     * @return float
1106
-     * @throws EE_Error
1107
-     */
1108
-    public function pretty_paid()
1109
-    {
1110
-        return $this->get_pretty('REG_paid');
1111
-    }
1112
-
1113
-
1114
-    /**
1115
-     * owes_monies_and_can_pay
1116
-     * whether or not this registration has monies owing and it's' status allows payment
1117
-     *
1118
-     * @param array $requires_payment
1119
-     * @return bool
1120
-     * @throws EE_Error
1121
-     */
1122
-    public function owes_monies_and_can_pay($requires_payment = array())
1123
-    {
1124
-        // these reg statuses require payment (if event is not free)
1125
-        $requires_payment = ! empty($requires_payment)
1126
-            ? $requires_payment
1127
-            : EEM_Registration::reg_statuses_that_allow_payment();
1128
-        if (in_array($this->status_ID(), $requires_payment) &&
1129
-            $this->final_price() != 0 &&
1130
-            $this->final_price() != $this->paid()
1131
-        ) {
1132
-            return true;
1133
-        } else {
1134
-            return false;
1135
-        }
1136
-    }
1137
-
1138
-
1139
-    /**
1140
-     * Prints out the return value of $this->pretty_status()
1141
-     *
1142
-     * @param bool $show_icons
1143
-     * @return void
1144
-     * @throws EE_Error
1145
-     */
1146
-    public function e_pretty_status($show_icons = false)
1147
-    {
1148
-        echo $this->pretty_status($show_icons);
1149
-    }
1150
-
1151
-
1152
-    /**
1153
-     * Returns a nice version of the status for displaying to customers
1154
-     *
1155
-     * @param bool $show_icons
1156
-     * @return string
1157
-     * @throws EE_Error
1158
-     */
1159
-    public function pretty_status($show_icons = false)
1160
-    {
1161
-        $status = EEM_Status::instance()->localized_status(
1162
-            array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
1163
-            false,
1164
-            'sentence'
1165
-        );
1166
-        $icon = '';
1167
-        switch ($this->status_ID()) {
1168
-            case EEM_Registration::status_id_approved:
1169
-                $icon = $show_icons
1170
-                    ? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1171
-                    : '';
1172
-                break;
1173
-            case EEM_Registration::status_id_pending_payment:
1174
-                $icon = $show_icons
1175
-                    ? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1176
-                    : '';
1177
-                break;
1178
-            case EEM_Registration::status_id_not_approved:
1179
-                $icon = $show_icons
1180
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1181
-                    : '';
1182
-                break;
1183
-            case EEM_Registration::status_id_cancelled:
1184
-                $icon = $show_icons
1185
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1186
-                    : '';
1187
-                break;
1188
-            case EEM_Registration::status_id_incomplete:
1189
-                $icon = $show_icons
1190
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1191
-                    : '';
1192
-                break;
1193
-            case EEM_Registration::status_id_declined:
1194
-                $icon = $show_icons
1195
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1196
-                    : '';
1197
-                break;
1198
-            case EEM_Registration::status_id_wait_list:
1199
-                $icon = $show_icons
1200
-                    ? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1201
-                    : '';
1202
-                break;
1203
-        }
1204
-        return $icon . $status[ $this->status_ID() ];
1205
-    }
1206
-
1207
-
1208
-    /**
1209
-     *        get Attendee Is Going
1210
-     */
1211
-    public function att_is_going()
1212
-    {
1213
-        return $this->get('REG_att_is_going');
1214
-    }
1215
-
1216
-
1217
-    /**
1218
-     * Gets related answers
1219
-     *
1220
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1221
-     * @return EE_Answer[]
1222
-     * @throws EE_Error
1223
-     */
1224
-    public function answers($query_params = null)
1225
-    {
1226
-        return $this->get_many_related('Answer', $query_params);
1227
-    }
1228
-
1229
-
1230
-    /**
1231
-     * Gets the registration's answer value to the specified question
1232
-     * (either the question's ID or a question object)
1233
-     *
1234
-     * @param EE_Question|int $question
1235
-     * @param bool            $pretty_value
1236
-     * @return array|string if pretty_value= true, the result will always be a string
1237
-     * (because the answer might be an array of answer values, so passing pretty_value=true
1238
-     * will convert it into some kind of string)
1239
-     * @throws EE_Error
1240
-     */
1241
-    public function answer_value_to_question($question, $pretty_value = true)
1242
-    {
1243
-        $question_id = EEM_Question::instance()->ensure_is_ID($question);
1244
-        return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1245
-    }
1246
-
1247
-
1248
-    /**
1249
-     * question_groups
1250
-     * returns an array of EE_Question_Group objects for this registration
1251
-     *
1252
-     * @return EE_Question_Group[]
1253
-     * @throws EE_Error
1254
-     * @throws EntityNotFoundException
1255
-     */
1256
-    public function question_groups()
1257
-    {
1258
-        $question_groups = array();
1259
-        if ($this->event() instanceof EE_Event) {
1260
-            $question_groups = $this->event()->question_groups(
1261
-                array(
1262
-                    array(
1263
-                        'Event_Question_Group.EQG_primary' => $this->count() == 1 ? true : false,
1264
-                    ),
1265
-                    'order_by' => array('QSG_order' => 'ASC'),
1266
-                )
1267
-            );
1268
-        }
1269
-        return $question_groups;
1270
-    }
1271
-
1272
-
1273
-    /**
1274
-     * count_question_groups
1275
-     * returns a count of the number of EE_Question_Group objects for this registration
1276
-     *
1277
-     * @return int
1278
-     * @throws EE_Error
1279
-     * @throws EntityNotFoundException
1280
-     */
1281
-    public function count_question_groups()
1282
-    {
1283
-        $qg_count = 0;
1284
-        if ($this->event() instanceof EE_Event) {
1285
-            $qg_count = $this->event()->count_related(
1286
-                'Question_Group',
1287
-                array(
1288
-                    array(
1289
-                        'Event_Question_Group.EQG_primary' => $this->count() == 1 ? true : false,
1290
-                    ),
1291
-                )
1292
-            );
1293
-        }
1294
-        return $qg_count;
1295
-    }
1296
-
1297
-
1298
-    /**
1299
-     * Returns the registration date in the 'standard' string format
1300
-     * (function may be improved in the future to allow for different formats and timezones)
1301
-     *
1302
-     * @return string
1303
-     * @throws EE_Error
1304
-     */
1305
-    public function reg_date()
1306
-    {
1307
-        return $this->get_datetime('REG_date');
1308
-    }
1309
-
1310
-
1311
-    /**
1312
-     * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1313
-     * the ticket this registration purchased, or the datetime they have registered
1314
-     * to attend)
1315
-     *
1316
-     * @return EE_Datetime_Ticket
1317
-     * @throws EE_Error
1318
-     */
1319
-    public function datetime_ticket()
1320
-    {
1321
-        return $this->get_first_related('Datetime_Ticket');
1322
-    }
1323
-
1324
-
1325
-    /**
1326
-     * Sets the registration's datetime_ticket.
1327
-     *
1328
-     * @param EE_Datetime_Ticket $datetime_ticket
1329
-     * @return EE_Datetime_Ticket
1330
-     * @throws EE_Error
1331
-     */
1332
-    public function set_datetime_ticket($datetime_ticket)
1333
-    {
1334
-        return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1335
-    }
1336
-
1337
-    /**
1338
-     * Gets deleted
1339
-     *
1340
-     * @return bool
1341
-     * @throws EE_Error
1342
-     */
1343
-    public function deleted()
1344
-    {
1345
-        return $this->get('REG_deleted');
1346
-    }
1347
-
1348
-    /**
1349
-     * Sets deleted
1350
-     *
1351
-     * @param boolean $deleted
1352
-     * @return bool
1353
-     * @throws EE_Error
1354
-     * @throws RuntimeException
1355
-     */
1356
-    public function set_deleted($deleted)
1357
-    {
1358
-        if ($deleted) {
1359
-            $this->delete();
1360
-        } else {
1361
-            $this->restore();
1362
-        }
1363
-    }
1364
-
1365
-
1366
-    /**
1367
-     * Get the status object of this object
1368
-     *
1369
-     * @return EE_Status
1370
-     * @throws EE_Error
1371
-     */
1372
-    public function status_obj()
1373
-    {
1374
-        return $this->get_first_related('Status');
1375
-    }
1376
-
1377
-
1378
-    /**
1379
-     * Returns the number of times this registration has checked into any of the datetimes
1380
-     * its available for
1381
-     *
1382
-     * @return int
1383
-     * @throws EE_Error
1384
-     */
1385
-    public function count_checkins()
1386
-    {
1387
-        return $this->get_model()->count_related($this, 'Checkin');
1388
-    }
1389
-
1390
-
1391
-    /**
1392
-     * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1393
-     * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1394
-     *
1395
-     * @return int
1396
-     * @throws EE_Error
1397
-     */
1398
-    public function count_checkins_not_checkedout()
1399
-    {
1400
-        return $this->get_model()->count_related($this, 'Checkin', array(array('CHK_in' => 1)));
1401
-    }
1402
-
1403
-
1404
-    /**
1405
-     * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1406
-     *
1407
-     * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1408
-     * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1409
-     *                                          consider registration status as well as datetime access.
1410
-     * @return bool
1411
-     * @throws EE_Error
1412
-     */
1413
-    public function can_checkin($DTT_OR_ID, $check_approved = true)
1414
-    {
1415
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1416
-
1417
-        // first check registration status
1418
-        if (($check_approved && ! $this->is_approved()) || ! $DTT_ID) {
1419
-            return false;
1420
-        }
1421
-        // is there a datetime ticket that matches this dtt_ID?
1422
-        if (! (EEM_Datetime_Ticket::instance()->exists(
1423
-            array(
1424
-                array(
1425
-                    'TKT_ID' => $this->get('TKT_ID'),
1426
-                    'DTT_ID' => $DTT_ID,
1427
-                ),
1428
-            )
1429
-        ))
1430
-        ) {
1431
-            return false;
1432
-        }
1433
-
1434
-        // final check is against TKT_uses
1435
-        return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1436
-    }
1437
-
1438
-
1439
-    /**
1440
-     * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1441
-     * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1442
-     * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1443
-     * then return false.  Otherwise return true.
1444
-     *
1445
-     * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1446
-     * @return bool true means can checkin.  false means cannot checkin.
1447
-     * @throws EE_Error
1448
-     */
1449
-    public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1450
-    {
1451
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1452
-
1453
-        if (! $DTT_ID) {
1454
-            return false;
1455
-        }
1456
-
1457
-        $max_uses = $this->ticket() instanceof EE_Ticket ? $this->ticket()->uses() : EE_INF;
1458
-
1459
-        // if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1460
-        // check-in or not.
1461
-        if (! $max_uses || $max_uses === EE_INF) {
1462
-            return true;
1463
-        }
1464
-
1465
-        // does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1466
-        // go ahead and toggle.
1467
-        if (EEM_Checkin::instance()->exists(array(array('REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID)))) {
1468
-            return true;
1469
-        }
1470
-
1471
-        // made it here so the last check is whether the number of checkins per unique datetime on this registration
1472
-        // disallows further check-ins.
1473
-        $count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1474
-            array(
1475
-                array(
1476
-                    'REG_ID' => $this->ID(),
1477
-                    'CHK_in' => true,
1478
-                ),
1479
-            ),
1480
-            'DTT_ID',
1481
-            true
1482
-        );
1483
-        // checkins have already reached their max number of uses
1484
-        // so registrant can NOT checkin
1485
-        if ($count_unique_dtt_checkins >= $max_uses) {
1486
-            EE_Error::add_error(
1487
-                esc_html__(
1488
-                    'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1489
-                    'event_espresso'
1490
-                ),
1491
-                __FILE__,
1492
-                __FUNCTION__,
1493
-                __LINE__
1494
-            );
1495
-            return false;
1496
-        }
1497
-        return true;
1498
-    }
1499
-
1500
-
1501
-    /**
1502
-     * toggle Check-in status for this registration
1503
-     * Check-ins are toggled in the following order:
1504
-     * never checked in -> checked in
1505
-     * checked in -> checked out
1506
-     * checked out -> checked in
1507
-     *
1508
-     * @param  int $DTT_ID  include specific datetime to toggle Check-in for.
1509
-     *                      If not included or null, then it is assumed latest datetime is being toggled.
1510
-     * @param bool $verify  If true then can_checkin() is used to verify whether the person
1511
-     *                      can be checked in or not.  Otherwise this forces change in checkin status.
1512
-     * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1513
-     * @throws EE_Error
1514
-     */
1515
-    public function toggle_checkin_status($DTT_ID = null, $verify = false)
1516
-    {
1517
-        if (empty($DTT_ID)) {
1518
-            $datetime = $this->get_latest_related_datetime();
1519
-            $DTT_ID = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1520
-            // verify the registration can checkin for the given DTT_ID
1521
-        } elseif (! $this->can_checkin($DTT_ID, $verify)) {
1522
-            EE_Error::add_error(
1523
-                sprintf(
1524
-                    esc_html__(
1525
-                        'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1526
-                        'event_espresso'
1527
-                    ),
1528
-                    $this->ID(),
1529
-                    $DTT_ID
1530
-                ),
1531
-                __FILE__,
1532
-                __FUNCTION__,
1533
-                __LINE__
1534
-            );
1535
-            return false;
1536
-        }
1537
-        $status_paths = array(
1538
-            EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1539
-            EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1540
-            EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1541
-        );
1542
-        // start by getting the current status so we know what status we'll be changing to.
1543
-        $cur_status = $this->check_in_status_for_datetime($DTT_ID, null);
1544
-        $status_to = $status_paths[ $cur_status ];
1545
-        // database only records true for checked IN or false for checked OUT
1546
-        // no record ( null ) means checked in NEVER, but we obviously don't save that
1547
-        $new_status = $status_to === EE_Checkin::status_checked_in ? true : false;
1548
-        // add relation - note Check-ins are always creating new rows
1549
-        // because we are keeping track of Check-ins over time.
1550
-        // Eventually we'll probably want to show a list table
1551
-        // for the individual Check-ins so that they can be managed.
1552
-        $checkin = EE_Checkin::new_instance(
1553
-            array(
1554
-                'REG_ID' => $this->ID(),
1555
-                'DTT_ID' => $DTT_ID,
1556
-                'CHK_in' => $new_status,
1557
-            )
1558
-        );
1559
-        // if the record could not be saved then return false
1560
-        if ($checkin->save() === 0) {
1561
-            if (WP_DEBUG) {
1562
-                global $wpdb;
1563
-                $error = sprintf(
1564
-                    esc_html__(
1565
-                        'Registration check in update failed because of the following database error: %1$s%2$s',
1566
-                        'event_espresso'
1567
-                    ),
1568
-                    '<br />',
1569
-                    $wpdb->last_error
1570
-                );
1571
-            } else {
1572
-                $error = esc_html__(
1573
-                    'Registration check in update failed because of an unknown database error',
1574
-                    'event_espresso'
1575
-                );
1576
-            }
1577
-            EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1578
-            return false;
1579
-        }
1580
-        return $status_to;
1581
-    }
1582
-
1583
-
1584
-    /**
1585
-     * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1586
-     * "Latest" is defined by the `DTT_EVT_start` column.
1587
-     *
1588
-     * @return EE_Datetime|null
1589
-     * @throws EE_Error
1590
-     */
1591
-    public function get_latest_related_datetime()
1592
-    {
1593
-        return EEM_Datetime::instance()->get_one(
1594
-            array(
1595
-                array(
1596
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1597
-                ),
1598
-                'order_by' => array('DTT_EVT_start' => 'DESC'),
1599
-            )
1600
-        );
1601
-    }
1602
-
1603
-
1604
-    /**
1605
-     * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1606
-     * "Earliest" is defined by the `DTT_EVT_start` column.
1607
-     *
1608
-     * @throws EE_Error
1609
-     */
1610
-    public function get_earliest_related_datetime()
1611
-    {
1612
-        return EEM_Datetime::instance()->get_one(
1613
-            array(
1614
-                array(
1615
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1616
-                ),
1617
-                'order_by' => array('DTT_EVT_start' => 'ASC'),
1618
-            )
1619
-        );
1620
-    }
1621
-
1622
-
1623
-    /**
1624
-     * This method simply returns the check-in status for this registration and the given datetime.
1625
-     * If neither the datetime nor the checkin values are provided as arguments,
1626
-     * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1627
-     *
1628
-     * @param  int       $DTT_ID  The ID of the datetime we're checking against
1629
-     *                            (if empty we'll get the primary datetime for
1630
-     *                            this registration (via event) and use it's ID);
1631
-     * @param EE_Checkin $checkin If present, we use the given checkin object rather than the dtt_id.
1632
-     *
1633
-     * @return int                Integer representing Check-in status.
1634
-     * @throws EE_Error
1635
-     */
1636
-    public function check_in_status_for_datetime($DTT_ID = 0, $checkin = null)
1637
-    {
1638
-        $checkin_query_params = array(
1639
-            'order_by' => array('CHK_timestamp' => 'DESC'),
1640
-        );
1641
-
1642
-        if ($DTT_ID > 0) {
1643
-            $checkin_query_params[0] = array('DTT_ID' => $DTT_ID);
1644
-        }
1645
-
1646
-        // get checkin object (if exists)
1647
-        $checkin = $checkin instanceof EE_Checkin
1648
-            ? $checkin
1649
-            : $this->get_first_related('Checkin', $checkin_query_params);
1650
-        if ($checkin instanceof EE_Checkin) {
1651
-            if ($checkin->get('CHK_in')) {
1652
-                return EE_Checkin::status_checked_in; // checked in
1653
-            }
1654
-            return EE_Checkin::status_checked_out; // had checked in but is now checked out.
1655
-        }
1656
-        return EE_Checkin::status_checked_never; // never been checked in
1657
-    }
1658
-
1659
-
1660
-    /**
1661
-     * This method returns a localized message for the toggled Check-in message.
1662
-     *
1663
-     * @param  int $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1664
-     *                     then it is assumed Check-in for primary datetime was toggled.
1665
-     * @param bool $error  This just flags that you want an error message returned. This is put in so that the error
1666
-     *                     message can be customized with the attendee name.
1667
-     * @return string internationalized message
1668
-     * @throws EE_Error
1669
-     */
1670
-    public function get_checkin_msg($DTT_ID, $error = false)
1671
-    {
1672
-        // let's get the attendee first so we can include the name of the attendee
1673
-        $attendee = $this->get_first_related('Attendee');
1674
-        if ($attendee instanceof EE_Attendee) {
1675
-            if ($error) {
1676
-                return sprintf(__("%s's check-in status was not changed.", "event_espresso"), $attendee->full_name());
1677
-            }
1678
-            $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1679
-            // what is the status message going to be?
1680
-            switch ($cur_status) {
1681
-                case EE_Checkin::status_checked_never:
1682
-                    return sprintf(
1683
-                        __("%s has been removed from Check-in records", "event_espresso"),
1684
-                        $attendee->full_name()
1685
-                    );
1686
-                    break;
1687
-                case EE_Checkin::status_checked_in:
1688
-                    return sprintf(__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1689
-                    break;
1690
-                case EE_Checkin::status_checked_out:
1691
-                    return sprintf(__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1692
-                    break;
1693
-            }
1694
-        }
1695
-        return esc_html__("The check-in status could not be determined.", "event_espresso");
1696
-    }
1697
-
1698
-
1699
-    /**
1700
-     * Returns the related EE_Transaction to this registration
1701
-     *
1702
-     * @return EE_Transaction
1703
-     * @throws EE_Error
1704
-     * @throws EntityNotFoundException
1705
-     */
1706
-    public function transaction()
1707
-    {
1708
-        $transaction = $this->get_first_related('Transaction');
1709
-        if (! $transaction instanceof \EE_Transaction) {
1710
-            throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1711
-        }
1712
-        return $transaction;
1713
-    }
1714
-
1715
-
1716
-    /**
1717
-     *        get Registration Code
1718
-     */
1719
-    public function reg_code()
1720
-    {
1721
-        return $this->get('REG_code');
1722
-    }
1723
-
1724
-
1725
-    /**
1726
-     *        get Transaction ID
1727
-     */
1728
-    public function transaction_ID()
1729
-    {
1730
-        return $this->get('TXN_ID');
1731
-    }
1732
-
1733
-
1734
-    /**
1735
-     * @return int
1736
-     * @throws EE_Error
1737
-     */
1738
-    public function ticket_ID()
1739
-    {
1740
-        return $this->get('TKT_ID');
1741
-    }
1742
-
1743
-
1744
-    /**
1745
-     *        Set Registration Code
1746
-     *
1747
-     * @access    public
1748
-     * @param    string  $REG_code Registration Code
1749
-     * @param    boolean $use_default
1750
-     * @throws EE_Error
1751
-     */
1752
-    public function set_reg_code($REG_code, $use_default = false)
1753
-    {
1754
-        if (empty($REG_code)) {
1755
-            EE_Error::add_error(
1756
-                esc_html__('REG_code can not be empty.', 'event_espresso'),
1757
-                __FILE__,
1758
-                __FUNCTION__,
1759
-                __LINE__
1760
-            );
1761
-            return;
1762
-        }
1763
-        if (! $this->reg_code()) {
1764
-            parent::set('REG_code', $REG_code, $use_default);
1765
-        } else {
1766
-            EE_Error::doing_it_wrong(
1767
-                __CLASS__ . '::' . __FUNCTION__,
1768
-                esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
1769
-                '4.6.0'
1770
-            );
1771
-        }
1772
-    }
1773
-
1774
-
1775
-    /**
1776
-     * Returns all other registrations in the same group as this registrant who have the same ticket option.
1777
-     * Note, if you want to just get all registrations in the same transaction (group), use:
1778
-     *    $registration->transaction()->registrations();
1779
-     *
1780
-     * @since 4.5.0
1781
-     * @return EE_Registration[] or empty array if this isn't a group registration.
1782
-     * @throws EE_Error
1783
-     */
1784
-    public function get_all_other_registrations_in_group()
1785
-    {
1786
-        if ($this->group_size() < 2) {
1787
-            return array();
1788
-        }
1789
-
1790
-        $query[0] = array(
1791
-            'TXN_ID' => $this->transaction_ID(),
1792
-            'REG_ID' => array('!=', $this->ID()),
1793
-            'TKT_ID' => $this->ticket_ID(),
1794
-        );
1795
-        /** @var EE_Registration[] $registrations */
1796
-        $registrations = $this->get_model()->get_all($query);
1797
-        return $registrations;
1798
-    }
1799
-
1800
-    /**
1801
-     * Return the link to the admin details for the object.
1802
-     *
1803
-     * @return string
1804
-     * @throws EE_Error
1805
-     */
1806
-    public function get_admin_details_link()
1807
-    {
1808
-        EE_Registry::instance()->load_helper('URL');
1809
-        return EEH_URL::add_query_args_and_nonce(
1810
-            array(
1811
-                'page'    => 'espresso_registrations',
1812
-                'action'  => 'view_registration',
1813
-                '_REG_ID' => $this->ID(),
1814
-            ),
1815
-            admin_url('admin.php')
1816
-        );
1817
-    }
1818
-
1819
-    /**
1820
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
1821
-     *
1822
-     * @return string
1823
-     * @throws EE_Error
1824
-     */
1825
-    public function get_admin_edit_link()
1826
-    {
1827
-        return $this->get_admin_details_link();
1828
-    }
1829
-
1830
-    /**
1831
-     * Returns the link to a settings page for the object.
1832
-     *
1833
-     * @return string
1834
-     * @throws EE_Error
1835
-     */
1836
-    public function get_admin_settings_link()
1837
-    {
1838
-        return $this->get_admin_details_link();
1839
-    }
1840
-
1841
-    /**
1842
-     * Returns the link to the "overview" for the object (typically the "list table" view).
1843
-     *
1844
-     * @return string
1845
-     */
1846
-    public function get_admin_overview_link()
1847
-    {
1848
-        EE_Registry::instance()->load_helper('URL');
1849
-        return EEH_URL::add_query_args_and_nonce(
1850
-            array(
1851
-                'page' => 'espresso_registrations',
1852
-            ),
1853
-            admin_url('admin.php')
1854
-        );
1855
-    }
1856
-
1857
-
1858
-    /**
1859
-     * @param array $query_params
1860
-     *
1861
-     * @return \EE_Registration[]
1862
-     * @throws EE_Error
1863
-     */
1864
-    public function payments($query_params = array())
1865
-    {
1866
-        return $this->get_many_related('Payment', $query_params);
1867
-    }
1868
-
1869
-
1870
-    /**
1871
-     * @param array $query_params
1872
-     *
1873
-     * @return \EE_Registration_Payment[]
1874
-     * @throws EE_Error
1875
-     */
1876
-    public function registration_payments($query_params = array())
1877
-    {
1878
-        return $this->get_many_related('Registration_Payment', $query_params);
1879
-    }
1880
-
1881
-
1882
-    /**
1883
-     * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
1884
-     * Note: if there are no payments on the registration there will be no payment method returned.
1885
-     *
1886
-     * @return EE_Payment_Method|null
1887
-     */
1888
-    public function payment_method()
1889
-    {
1890
-        return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
1891
-    }
1892
-
1893
-
1894
-    /**
1895
-     * @return \EE_Line_Item
1896
-     * @throws EntityNotFoundException
1897
-     * @throws EE_Error
1898
-     */
1899
-    public function ticket_line_item()
1900
-    {
1901
-        $ticket = $this->ticket();
1902
-        $transaction = $this->transaction();
1903
-        $line_item = null;
1904
-        $ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
1905
-            $transaction->total_line_item(),
1906
-            'Ticket',
1907
-            array($ticket->ID())
1908
-        );
1909
-        foreach ($ticket_line_items as $ticket_line_item) {
1910
-            if ($ticket_line_item instanceof \EE_Line_Item
1911
-                && $ticket_line_item->OBJ_type() === 'Ticket'
1912
-                && $ticket_line_item->OBJ_ID() === $ticket->ID()
1913
-            ) {
1914
-                $line_item = $ticket_line_item;
1915
-                break;
1916
-            }
1917
-        }
1918
-        if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
1919
-            throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
1920
-        }
1921
-        return $line_item;
1922
-    }
1923
-
1924
-
1925
-    /**
1926
-     * Soft Deletes this model object.
1927
-     *
1928
-     * @return boolean | int
1929
-     * @throws RuntimeException
1930
-     * @throws EE_Error
1931
-     */
1932
-    public function delete()
1933
-    {
1934
-        if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
1935
-            $this->set_status(EEM_Registration::status_id_cancelled);
1936
-        }
1937
-        return parent::delete();
1938
-    }
1939
-
1940
-
1941
-    /**
1942
-     * Restores whatever the previous status was on a registration before it was trashed (if possible)
1943
-     *
1944
-     * @throws EE_Error
1945
-     * @throws RuntimeException
1946
-     */
1947
-    public function restore()
1948
-    {
1949
-        $previous_status = $this->get_extra_meta(
1950
-            EE_Registration::PRE_TRASH_REG_STATUS_KEY,
1951
-            true,
1952
-            EEM_Registration::status_id_cancelled
1953
-        );
1954
-        if ($previous_status) {
1955
-            $this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
1956
-            $this->set_status($previous_status);
1957
-        }
1958
-        return parent::restore();
1959
-    }
1960
-
1961
-
1962
-    /**
1963
-     * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
1964
-     *
1965
-     * @param  boolean $trigger_set_status_logic EE_Registration::set_status() can trigger additional logic
1966
-     *                                           depending on whether the reg status changes to or from "Approved"
1967
-     * @return boolean whether the Registration status was updated
1968
-     * @throws EE_Error
1969
-     * @throws RuntimeException
1970
-     */
1971
-    public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
1972
-    {
1973
-        $paid = $this->paid();
1974
-        $price = $this->final_price();
1975
-        switch (true) {
1976
-            // overpaid or paid
1977
-            case EEH_Money::compare_floats($paid, $price, '>'):
1978
-            case EEH_Money::compare_floats($paid, $price):
1979
-                $new_status = EEM_Registration::status_id_approved;
1980
-                break;
1981
-            //  underpaid
1982
-            case EEH_Money::compare_floats($paid, $price, '<'):
1983
-                $new_status = EEM_Registration::status_id_pending_payment;
1984
-                break;
1985
-            // uhhh Houston...
1986
-            default:
1987
-                throw new RuntimeException(
1988
-                    esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
1989
-                );
1990
-        }
1991
-        if ($new_status !== $this->status_ID()) {
1992
-            if ($trigger_set_status_logic) {
1993
-                return $this->set_status($new_status);
1994
-            }
1995
-            parent::set('STS_ID', $new_status);
1996
-            return true;
1997
-        }
1998
-        return false;
1999
-    }
2000
-
2001
-
2002
-    /*************************** DEPRECATED ***************************/
2003
-
2004
-
2005
-    /**
2006
-     * @deprecated
2007
-     * @since     4.7.0
2008
-     * @access    public
2009
-     */
2010
-    public function price_paid()
2011
-    {
2012
-        EE_Error::doing_it_wrong(
2013
-            'EE_Registration::price_paid()',
2014
-            esc_html__(
2015
-                'This method is deprecated, please use EE_Registration::final_price() instead.',
2016
-                'event_espresso'
2017
-            ),
2018
-            '4.7.0'
2019
-        );
2020
-        return $this->final_price();
2021
-    }
2022
-
2023
-
2024
-    /**
2025
-     * @deprecated
2026
-     * @since     4.7.0
2027
-     * @access    public
2028
-     * @param    float $REG_final_price
2029
-     * @throws EE_Error
2030
-     * @throws RuntimeException
2031
-     */
2032
-    public function set_price_paid($REG_final_price = 0.00)
2033
-    {
2034
-        EE_Error::doing_it_wrong(
2035
-            'EE_Registration::set_price_paid()',
2036
-            esc_html__(
2037
-                'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2038
-                'event_espresso'
2039
-            ),
2040
-            '4.7.0'
2041
-        );
2042
-        $this->set_final_price($REG_final_price);
2043
-    }
2044
-
2045
-
2046
-    /**
2047
-     * @deprecated
2048
-     * @since 4.7.0
2049
-     * @return string
2050
-     * @throws EE_Error
2051
-     */
2052
-    public function pretty_price_paid()
2053
-    {
2054
-        EE_Error::doing_it_wrong(
2055
-            'EE_Registration::pretty_price_paid()',
2056
-            esc_html__(
2057
-                'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2058
-                'event_espresso'
2059
-            ),
2060
-            '4.7.0'
2061
-        );
2062
-        return $this->pretty_final_price();
2063
-    }
2064
-
2065
-
2066
-    /**
2067
-     * Gets the primary datetime related to this registration via the related Event to this registration
2068
-     *
2069
-     * @deprecated 4.9.17
2070
-     * @return EE_Datetime
2071
-     * @throws EE_Error
2072
-     * @throws EntityNotFoundException
2073
-     */
2074
-    public function get_related_primary_datetime()
2075
-    {
2076
-        EE_Error::doing_it_wrong(
2077
-            __METHOD__,
2078
-            esc_html__(
2079
-                'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2080
-                'event_espresso'
2081
-            ),
2082
-            '4.9.17',
2083
-            '5.0.0'
2084
-        );
2085
-        return $this->event()->primary_datetime();
2086
-    }
20
+	/**
21
+	 * Used to reference when a registration has never been checked in.
22
+	 *
23
+	 * @deprecated use \EE_Checkin::status_checked_never instead
24
+	 * @type int
25
+	 */
26
+	const checkin_status_never = 2;
27
+
28
+	/**
29
+	 * Used to reference when a registration has been checked in.
30
+	 *
31
+	 * @deprecated use \EE_Checkin::status_checked_in instead
32
+	 * @type int
33
+	 */
34
+	const checkin_status_in = 1;
35
+
36
+
37
+	/**
38
+	 * Used to reference when a registration has been checked out.
39
+	 *
40
+	 * @deprecated use \EE_Checkin::status_checked_out instead
41
+	 * @type int
42
+	 */
43
+	const checkin_status_out = 0;
44
+
45
+
46
+	/**
47
+	 * extra meta key for tracking reg status os trashed registrations
48
+	 *
49
+	 * @type string
50
+	 */
51
+	const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
52
+
53
+
54
+	/**
55
+	 * extra meta key for tracking if registration has reserved ticket
56
+	 *
57
+	 * @type string
58
+	 */
59
+	const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
60
+
61
+
62
+	/**
63
+	 * @param array  $props_n_values          incoming values
64
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
65
+	 *                                        used.)
66
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
67
+	 *                                        date_format and the second value is the time format
68
+	 * @return EE_Registration
69
+	 * @throws EE_Error
70
+	 */
71
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
72
+	{
73
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
74
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
75
+	}
76
+
77
+
78
+	/**
79
+	 * @param array  $props_n_values  incoming values from the database
80
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
81
+	 *                                the website will be used.
82
+	 * @return EE_Registration
83
+	 */
84
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
85
+	{
86
+		return new self($props_n_values, true, $timezone);
87
+	}
88
+
89
+
90
+	/**
91
+	 *        Set Event ID
92
+	 *
93
+	 * @param        int $EVT_ID Event ID
94
+	 * @throws EE_Error
95
+	 * @throws RuntimeException
96
+	 */
97
+	public function set_event($EVT_ID = 0)
98
+	{
99
+		$this->set('EVT_ID', $EVT_ID);
100
+	}
101
+
102
+
103
+	/**
104
+	 * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
105
+	 * be routed to internal methods
106
+	 *
107
+	 * @param string $field_name
108
+	 * @param mixed  $field_value
109
+	 * @param bool   $use_default
110
+	 * @throws EE_Error
111
+	 * @throws EntityNotFoundException
112
+	 * @throws InvalidArgumentException
113
+	 * @throws InvalidDataTypeException
114
+	 * @throws InvalidInterfaceException
115
+	 * @throws ReflectionException
116
+	 * @throws RuntimeException
117
+	 */
118
+	public function set($field_name, $field_value, $use_default = false)
119
+	{
120
+		switch ($field_name) {
121
+			case 'REG_code':
122
+				if (! empty($field_value) && $this->reg_code() === null) {
123
+					$this->set_reg_code($field_value, $use_default);
124
+				}
125
+				break;
126
+			case 'STS_ID':
127
+				$this->set_status($field_value, $use_default);
128
+				break;
129
+			default:
130
+				parent::set($field_name, $field_value, $use_default);
131
+		}
132
+	}
133
+
134
+
135
+	/**
136
+	 * Set Status ID
137
+	 * updates the registration status and ALSO...
138
+	 * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
139
+	 * calls release_registration_space() if the reg status changes FROM approved to any other reg status
140
+	 *
141
+	 * @param string                $new_STS_ID
142
+	 * @param boolean               $use_default
143
+	 * @param ContextInterface|null $context
144
+	 * @return bool
145
+	 * @throws EE_Error
146
+	 * @throws EntityNotFoundException
147
+	 * @throws InvalidArgumentException
148
+	 * @throws ReflectionException
149
+	 * @throws RuntimeException
150
+	 * @throws InvalidDataTypeException
151
+	 * @throws InvalidInterfaceException
152
+	 */
153
+	public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
154
+	{
155
+		// get current REG_Status
156
+		$old_STS_ID = $this->status_ID();
157
+		// if status has changed
158
+		if ($old_STS_ID !== $new_STS_ID // and that status has actually changed
159
+			&& ! empty($old_STS_ID) // and that old status is actually set
160
+			&& ! empty($new_STS_ID) // as well as the new status
161
+			&& $this->ID() // ensure registration is in the db
162
+		) {
163
+			// TO approved
164
+			if ($new_STS_ID === EEM_Registration::status_id_approved) {
165
+				// reserve a space by incrementing ticket and datetime sold values
166
+				$this->_reserve_registration_space();
167
+				do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
168
+				// OR FROM  approved
169
+			} elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
170
+				// release a space by decrementing ticket and datetime sold values
171
+				$this->_release_registration_space();
172
+				do_action(
173
+					'AHEE__EE_Registration__set_status__from_approved',
174
+					$this,
175
+					$old_STS_ID,
176
+					$new_STS_ID,
177
+					$context
178
+				);
179
+			}
180
+			// update status
181
+			parent::set('STS_ID', $new_STS_ID, $use_default);
182
+			$this->_update_if_canceled_or_declined($new_STS_ID, $old_STS_ID, $context);
183
+			if ($this->statusChangeUpdatesTransaction($context)) {
184
+				$this->updateTransactionAfterStatusChange();
185
+			}
186
+			do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
187
+			return true;
188
+		}
189
+		// even though the old value matches the new value, it's still good to
190
+		// allow the parent set method to have a say
191
+		parent::set('STS_ID', $new_STS_ID, $use_default);
192
+		return true;
193
+	}
194
+
195
+
196
+	/**
197
+	 * update REGs and TXN when cancelled or declined registrations involved
198
+	 *
199
+	 * @param string                $new_STS_ID
200
+	 * @param string                $old_STS_ID
201
+	 * @param ContextInterface|null $context
202
+	 * @throws EE_Error
203
+	 * @throws InvalidArgumentException
204
+	 * @throws InvalidDataTypeException
205
+	 * @throws InvalidInterfaceException
206
+	 * @throws ReflectionException
207
+	 */
208
+	private function _update_if_canceled_or_declined($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
209
+	{
210
+		// these reg statuses should not be considered in any calculations involving monies owing
211
+		$closed_reg_statuses = EEM_Registration::closed_reg_statuses();
212
+		// true if registration has been cancelled or declined
213
+		$this->updateIfCanceled(
214
+			$closed_reg_statuses,
215
+			$new_STS_ID,
216
+			$old_STS_ID,
217
+			$context
218
+		);
219
+		$this->updateIfDeclined(
220
+			$closed_reg_statuses,
221
+			$new_STS_ID,
222
+			$old_STS_ID,
223
+			$context
224
+		);
225
+	}
226
+
227
+
228
+	/**
229
+	 * update REGs and TXN when cancelled or declined registrations involved
230
+	 *
231
+	 * @param array                 $closed_reg_statuses
232
+	 * @param string                $new_STS_ID
233
+	 * @param string                $old_STS_ID
234
+	 * @param ContextInterface|null $context
235
+	 * @throws EE_Error
236
+	 * @throws InvalidArgumentException
237
+	 * @throws InvalidDataTypeException
238
+	 * @throws InvalidInterfaceException
239
+	 * @throws ReflectionException
240
+	 */
241
+	private function updateIfCanceled(
242
+		array $closed_reg_statuses,
243
+		$new_STS_ID,
244
+		$old_STS_ID,
245
+		ContextInterface $context = null
246
+	) {
247
+		// true if registration has been cancelled or declined
248
+		if (in_array($new_STS_ID, $closed_reg_statuses, true)
249
+			&& ! in_array($old_STS_ID, $closed_reg_statuses, true)
250
+		) {
251
+			/** @type EE_Registration_Processor $registration_processor */
252
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
253
+			/** @type EE_Transaction_Processor $transaction_processor */
254
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
255
+			// cancelled or declined registration
256
+			$registration_processor->update_registration_after_being_canceled_or_declined(
257
+				$this,
258
+				$closed_reg_statuses
259
+			);
260
+			$transaction_processor->update_transaction_after_canceled_or_declined_registration(
261
+				$this,
262
+				$closed_reg_statuses,
263
+				false
264
+			);
265
+			do_action(
266
+				'AHEE__EE_Registration__set_status__canceled_or_declined',
267
+				$this,
268
+				$old_STS_ID,
269
+				$new_STS_ID,
270
+				$context
271
+			);
272
+			return;
273
+		}
274
+	}
275
+
276
+
277
+	/**
278
+	 * update REGs and TXN when cancelled or declined registrations involved
279
+	 *
280
+	 * @param array                 $closed_reg_statuses
281
+	 * @param string                $new_STS_ID
282
+	 * @param string                $old_STS_ID
283
+	 * @param ContextInterface|null $context
284
+	 * @throws EE_Error
285
+	 * @throws InvalidArgumentException
286
+	 * @throws InvalidDataTypeException
287
+	 * @throws InvalidInterfaceException
288
+	 * @throws ReflectionException
289
+	 */
290
+	private function updateIfDeclined(
291
+		array $closed_reg_statuses,
292
+		$new_STS_ID,
293
+		$old_STS_ID,
294
+		ContextInterface $context = null
295
+	) {
296
+		// true if reinstating cancelled or declined registration
297
+		if (in_array($old_STS_ID, $closed_reg_statuses, true)
298
+			&& ! in_array($new_STS_ID, $closed_reg_statuses, true)
299
+		) {
300
+			/** @type EE_Registration_Processor $registration_processor */
301
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
302
+			/** @type EE_Transaction_Processor $transaction_processor */
303
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
304
+			// reinstating cancelled or declined registration
305
+			$registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
306
+				$this,
307
+				$closed_reg_statuses
308
+			);
309
+			$transaction_processor->update_transaction_after_reinstating_canceled_registration(
310
+				$this,
311
+				$closed_reg_statuses,
312
+				false
313
+			);
314
+			do_action(
315
+				'AHEE__EE_Registration__set_status__after_reinstated',
316
+				$this,
317
+				$old_STS_ID,
318
+				$new_STS_ID,
319
+				$context
320
+			);
321
+		}
322
+	}
323
+
324
+
325
+	/**
326
+	 * @param ContextInterface|null $context
327
+	 * @return bool
328
+	 */
329
+	private function statusChangeUpdatesTransaction(ContextInterface $context = null)
330
+	{
331
+		$contexts_that_do_not_update_transaction = (array) apply_filters(
332
+			'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
333
+			array('spco_reg_step_attendee_information_process_registrations'),
334
+			$context,
335
+			$this
336
+		);
337
+		return ! (
338
+			$context instanceof ContextInterface
339
+			&& in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
340
+		);
341
+	}
342
+
343
+
344
+	/**
345
+	 * @throws EE_Error
346
+	 * @throws EntityNotFoundException
347
+	 * @throws InvalidArgumentException
348
+	 * @throws InvalidDataTypeException
349
+	 * @throws InvalidInterfaceException
350
+	 * @throws ReflectionException
351
+	 * @throws RuntimeException
352
+	 */
353
+	private function updateTransactionAfterStatusChange()
354
+	{
355
+		/** @type EE_Transaction_Payments $transaction_payments */
356
+		$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
357
+		$transaction_payments->recalculate_transaction_total($this->transaction(), false);
358
+		$this->transaction()->update_status_based_on_total_paid(true);
359
+	}
360
+
361
+
362
+	/**
363
+	 *        get Status ID
364
+	 */
365
+	public function status_ID()
366
+	{
367
+		return $this->get('STS_ID');
368
+	}
369
+
370
+
371
+	/**
372
+	 * Gets the ticket this registration is for
373
+	 *
374
+	 * @param boolean $include_archived whether to include archived tickets or not.
375
+	 *
376
+	 * @return EE_Ticket|EE_Base_Class
377
+	 * @throws EE_Error
378
+	 */
379
+	public function ticket($include_archived = true)
380
+	{
381
+		$query_params = array();
382
+		if ($include_archived) {
383
+			$query_params['default_where_conditions'] = 'none';
384
+		}
385
+		return $this->get_first_related('Ticket', $query_params);
386
+	}
387
+
388
+
389
+	/**
390
+	 * Gets the event this registration is for
391
+	 *
392
+	 * @return EE_Event
393
+	 * @throws EE_Error
394
+	 * @throws EntityNotFoundException
395
+	 */
396
+	public function event()
397
+	{
398
+		$event = $this->get_first_related('Event');
399
+		if (! $event instanceof \EE_Event) {
400
+			throw new EntityNotFoundException('Event ID', $this->event_ID());
401
+		}
402
+		return $event;
403
+	}
404
+
405
+
406
+	/**
407
+	 * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
408
+	 * with the author of the event this registration is for.
409
+	 *
410
+	 * @since 4.5.0
411
+	 * @return int
412
+	 * @throws EE_Error
413
+	 * @throws EntityNotFoundException
414
+	 */
415
+	public function wp_user()
416
+	{
417
+		$event = $this->event();
418
+		if ($event instanceof EE_Event) {
419
+			return $event->wp_user();
420
+		}
421
+		return 0;
422
+	}
423
+
424
+
425
+	/**
426
+	 * increments this registration's related ticket sold and corresponding datetime sold values
427
+	 *
428
+	 * @return void
429
+	 * @throws DomainException
430
+	 * @throws EE_Error
431
+	 * @throws EntityNotFoundException
432
+	 * @throws InvalidArgumentException
433
+	 * @throws InvalidDataTypeException
434
+	 * @throws InvalidInterfaceException
435
+	 * @throws ReflectionException
436
+	 * @throws UnexpectedEntityException
437
+	 */
438
+	private function _reserve_registration_space()
439
+	{
440
+		// reserved ticket and datetime counts will be decremented as sold counts are incremented
441
+		// so stop tracking that this reg has a ticket reserved
442
+		$this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
443
+		$ticket = $this->ticket();
444
+		$ticket->increase_sold();
445
+		$ticket->save();
446
+		// possibly set event status to sold out
447
+		$this->event()->perform_sold_out_status_check();
448
+	}
449
+
450
+
451
+	/**
452
+	 * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
453
+	 *
454
+	 * @return void
455
+	 * @throws DomainException
456
+	 * @throws EE_Error
457
+	 * @throws EntityNotFoundException
458
+	 * @throws InvalidArgumentException
459
+	 * @throws InvalidDataTypeException
460
+	 * @throws InvalidInterfaceException
461
+	 * @throws ReflectionException
462
+	 * @throws UnexpectedEntityException
463
+	 */
464
+	private function _release_registration_space()
465
+	{
466
+		$ticket = $this->ticket();
467
+		$ticket->decrease_sold();
468
+		$ticket->save();
469
+		// possibly change event status from sold out back to previous status
470
+		$this->event()->perform_sold_out_status_check();
471
+	}
472
+
473
+
474
+	/**
475
+	 * tracks this registration's ticket reservation in extra meta
476
+	 * and can increment related ticket reserved and corresponding datetime reserved values
477
+	 *
478
+	 * @param bool $update_ticket if true, will increment ticket and datetime reserved count
479
+	 * @return void
480
+	 * @throws EE_Error
481
+	 * @throws InvalidArgumentException
482
+	 * @throws InvalidDataTypeException
483
+	 * @throws InvalidInterfaceException
484
+	 * @throws ReflectionException
485
+	 */
486
+	public function reserve_ticket($update_ticket = false, $source = 'unknown')
487
+	{
488
+		// only reserve ticket if space is not currently reserved
489
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
490
+			$this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
491
+			// IMPORTANT !!!
492
+			// although checking $update_ticket first would be more efficient,
493
+			// we NEED to ALWAYS call update_extra_meta(), which is why that is done first
494
+			if ($this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true)
495
+				&& $update_ticket
496
+			) {
497
+				$ticket = $this->ticket();
498
+				$ticket->increase_reserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
499
+				$ticket->save();
500
+			}
501
+		}
502
+	}
503
+
504
+
505
+	/**
506
+	 * stops tracking this registration's ticket reservation in extra meta
507
+	 * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
508
+	 *
509
+	 * @param bool $update_ticket if true, will decrement ticket and datetime reserved count
510
+	 * @return void
511
+	 * @throws EE_Error
512
+	 * @throws InvalidArgumentException
513
+	 * @throws InvalidDataTypeException
514
+	 * @throws InvalidInterfaceException
515
+	 * @throws ReflectionException
516
+	 */
517
+	public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
518
+	{
519
+		// only release ticket if space is currently reserved
520
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
521
+			$this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
522
+			// IMPORTANT !!!
523
+			// although checking $update_ticket first would be more efficient,
524
+			// we NEED to ALWAYS call update_extra_meta(), which is why that is done first
525
+			if ($this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false)
526
+				&& $update_ticket
527
+			) {
528
+				$ticket = $this->ticket();
529
+				$ticket->decrease_reserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
530
+				$ticket->save();
531
+			}
532
+		}
533
+	}
534
+
535
+
536
+	/**
537
+	 * Set Attendee ID
538
+	 *
539
+	 * @param        int $ATT_ID Attendee ID
540
+	 * @throws EE_Error
541
+	 * @throws RuntimeException
542
+	 */
543
+	public function set_attendee_id($ATT_ID = 0)
544
+	{
545
+		$this->set('ATT_ID', $ATT_ID);
546
+	}
547
+
548
+
549
+	/**
550
+	 *        Set Transaction ID
551
+	 *
552
+	 * @param        int $TXN_ID Transaction ID
553
+	 * @throws EE_Error
554
+	 * @throws RuntimeException
555
+	 */
556
+	public function set_transaction_id($TXN_ID = 0)
557
+	{
558
+		$this->set('TXN_ID', $TXN_ID);
559
+	}
560
+
561
+
562
+	/**
563
+	 *        Set Session
564
+	 *
565
+	 * @param    string $REG_session PHP Session ID
566
+	 * @throws EE_Error
567
+	 * @throws RuntimeException
568
+	 */
569
+	public function set_session($REG_session = '')
570
+	{
571
+		$this->set('REG_session', $REG_session);
572
+	}
573
+
574
+
575
+	/**
576
+	 *        Set Registration URL Link
577
+	 *
578
+	 * @param    string $REG_url_link Registration URL Link
579
+	 * @throws EE_Error
580
+	 * @throws RuntimeException
581
+	 */
582
+	public function set_reg_url_link($REG_url_link = '')
583
+	{
584
+		$this->set('REG_url_link', $REG_url_link);
585
+	}
586
+
587
+
588
+	/**
589
+	 *        Set Attendee Counter
590
+	 *
591
+	 * @param        int $REG_count Primary Attendee
592
+	 * @throws EE_Error
593
+	 * @throws RuntimeException
594
+	 */
595
+	public function set_count($REG_count = 1)
596
+	{
597
+		$this->set('REG_count', $REG_count);
598
+	}
599
+
600
+
601
+	/**
602
+	 *        Set Group Size
603
+	 *
604
+	 * @param        boolean $REG_group_size Group Registration
605
+	 * @throws EE_Error
606
+	 * @throws RuntimeException
607
+	 */
608
+	public function set_group_size($REG_group_size = false)
609
+	{
610
+		$this->set('REG_group_size', $REG_group_size);
611
+	}
612
+
613
+
614
+	/**
615
+	 *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
616
+	 *    EEM_Registration::status_id_not_approved
617
+	 *
618
+	 * @return        boolean
619
+	 */
620
+	public function is_not_approved()
621
+	{
622
+		return $this->status_ID() == EEM_Registration::status_id_not_approved ? true : false;
623
+	}
624
+
625
+
626
+	/**
627
+	 *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
628
+	 *    EEM_Registration::status_id_pending_payment
629
+	 *
630
+	 * @return        boolean
631
+	 */
632
+	public function is_pending_payment()
633
+	{
634
+		return $this->status_ID() == EEM_Registration::status_id_pending_payment ? true : false;
635
+	}
636
+
637
+
638
+	/**
639
+	 *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
640
+	 *
641
+	 * @return        boolean
642
+	 */
643
+	public function is_approved()
644
+	{
645
+		return $this->status_ID() == EEM_Registration::status_id_approved ? true : false;
646
+	}
647
+
648
+
649
+	/**
650
+	 *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
651
+	 *
652
+	 * @return        boolean
653
+	 */
654
+	public function is_cancelled()
655
+	{
656
+		return $this->status_ID() == EEM_Registration::status_id_cancelled ? true : false;
657
+	}
658
+
659
+
660
+	/**
661
+	 *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
662
+	 *
663
+	 * @return        boolean
664
+	 */
665
+	public function is_declined()
666
+	{
667
+		return $this->status_ID() == EEM_Registration::status_id_declined ? true : false;
668
+	}
669
+
670
+
671
+	/**
672
+	 *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
673
+	 *    EEM_Registration::status_id_incomplete
674
+	 *
675
+	 * @return        boolean
676
+	 */
677
+	public function is_incomplete()
678
+	{
679
+		return $this->status_ID() == EEM_Registration::status_id_incomplete ? true : false;
680
+	}
681
+
682
+
683
+	/**
684
+	 *        Set Registration Date
685
+	 *
686
+	 * @param        mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
687
+	 *                                                 Date
688
+	 * @throws EE_Error
689
+	 * @throws RuntimeException
690
+	 */
691
+	public function set_reg_date($REG_date = false)
692
+	{
693
+		$this->set('REG_date', $REG_date);
694
+	}
695
+
696
+
697
+	/**
698
+	 *    Set final price owing for this registration after all ticket/price modifications
699
+	 *
700
+	 * @access    public
701
+	 * @param    float $REG_final_price
702
+	 * @throws EE_Error
703
+	 * @throws RuntimeException
704
+	 */
705
+	public function set_final_price($REG_final_price = 0.00)
706
+	{
707
+		$this->set('REG_final_price', $REG_final_price);
708
+	}
709
+
710
+
711
+	/**
712
+	 *    Set amount paid towards this registration's final price
713
+	 *
714
+	 * @access    public
715
+	 * @param    float $REG_paid
716
+	 * @throws EE_Error
717
+	 * @throws RuntimeException
718
+	 */
719
+	public function set_paid($REG_paid = 0.00)
720
+	{
721
+		$this->set('REG_paid', $REG_paid);
722
+	}
723
+
724
+
725
+	/**
726
+	 *        Attendee Is Going
727
+	 *
728
+	 * @param        boolean $REG_att_is_going Attendee Is Going
729
+	 * @throws EE_Error
730
+	 * @throws RuntimeException
731
+	 */
732
+	public function set_att_is_going($REG_att_is_going = false)
733
+	{
734
+		$this->set('REG_att_is_going', $REG_att_is_going);
735
+	}
736
+
737
+
738
+	/**
739
+	 * Gets the related attendee
740
+	 *
741
+	 * @return EE_Attendee
742
+	 * @throws EE_Error
743
+	 */
744
+	public function attendee()
745
+	{
746
+		return $this->get_first_related('Attendee');
747
+	}
748
+
749
+
750
+	/**
751
+	 *        get Event ID
752
+	 */
753
+	public function event_ID()
754
+	{
755
+		return $this->get('EVT_ID');
756
+	}
757
+
758
+
759
+	/**
760
+	 *        get Event ID
761
+	 */
762
+	public function event_name()
763
+	{
764
+		$event = $this->event_obj();
765
+		if ($event) {
766
+			return $event->name();
767
+		} else {
768
+			return null;
769
+		}
770
+	}
771
+
772
+
773
+	/**
774
+	 * Fetches the event this registration is for
775
+	 *
776
+	 * @return EE_Event
777
+	 * @throws EE_Error
778
+	 */
779
+	public function event_obj()
780
+	{
781
+		return $this->get_first_related('Event');
782
+	}
783
+
784
+
785
+	/**
786
+	 *        get Attendee ID
787
+	 */
788
+	public function attendee_ID()
789
+	{
790
+		return $this->get('ATT_ID');
791
+	}
792
+
793
+
794
+	/**
795
+	 *        get PHP Session ID
796
+	 */
797
+	public function session_ID()
798
+	{
799
+		return $this->get('REG_session');
800
+	}
801
+
802
+
803
+	/**
804
+	 * Gets the string which represents the URL trigger for the receipt template in the message template system.
805
+	 *
806
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
807
+	 * @return string
808
+	 */
809
+	public function receipt_url($messenger = 'html')
810
+	{
811
+
812
+		/**
813
+		 * The below will be deprecated one version after this.  We check first if there is a custom receipt template
814
+		 * already in use on old system.  If there is then we just return the standard url for it.
815
+		 *
816
+		 * @since 4.5.0
817
+		 */
818
+		$template_relative_path = 'modules/gateways/Invoice/lib/templates/receipt_body.template.php';
819
+		$has_custom = EEH_Template::locate_template(
820
+			$template_relative_path,
821
+			array(),
822
+			true,
823
+			true,
824
+			true
825
+		);
826
+
827
+		if ($has_custom) {
828
+			return add_query_arg(array('receipt' => 'true'), $this->invoice_url('launch'));
829
+		}
830
+		return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets the string which represents the URL trigger for the invoice template in the message template system.
836
+	 *
837
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
838
+	 * @return string
839
+	 * @throws EE_Error
840
+	 */
841
+	public function invoice_url($messenger = 'html')
842
+	{
843
+		/**
844
+		 * The below will be deprecated one version after this.  We check first if there is a custom invoice template
845
+		 * already in use on old system.  If there is then we just return the standard url for it.
846
+		 *
847
+		 * @since 4.5.0
848
+		 */
849
+		$template_relative_path = 'modules/gateways/Invoice/lib/templates/invoice_body.template.php';
850
+		$has_custom = EEH_Template::locate_template(
851
+			$template_relative_path,
852
+			array(),
853
+			true,
854
+			true,
855
+			true
856
+		);
857
+
858
+		if ($has_custom) {
859
+			if ($messenger == 'html') {
860
+				return $this->invoice_url('launch');
861
+			}
862
+			$route = $messenger == 'download' || $messenger == 'pdf' ? 'download_invoice' : 'launch_invoice';
863
+
864
+			$query_args = array('ee' => $route, 'id' => $this->reg_url_link());
865
+			if ($messenger == 'html') {
866
+				$query_args['html'] = true;
867
+			}
868
+			return add_query_arg($query_args, get_permalink(EE_Registry::instance()->CFG->core->thank_you_page_id));
869
+		}
870
+		return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
871
+	}
872
+
873
+
874
+	/**
875
+	 * get Registration URL Link
876
+	 *
877
+	 * @access public
878
+	 * @return string
879
+	 * @throws EE_Error
880
+	 */
881
+	public function reg_url_link()
882
+	{
883
+		return (string) $this->get('REG_url_link');
884
+	}
885
+
886
+
887
+	/**
888
+	 * Echoes out invoice_url()
889
+	 *
890
+	 * @param string $type 'download','launch', or 'html' (default is 'launch')
891
+	 * @return void
892
+	 * @throws EE_Error
893
+	 */
894
+	public function e_invoice_url($type = 'launch')
895
+	{
896
+		echo $this->invoice_url($type);
897
+	}
898
+
899
+
900
+	/**
901
+	 * Echoes out payment_overview_url
902
+	 */
903
+	public function e_payment_overview_url()
904
+	{
905
+		echo $this->payment_overview_url();
906
+	}
907
+
908
+
909
+	/**
910
+	 * Gets the URL for the checkout payment options reg step
911
+	 * with this registration's REG_url_link added as a query parameter
912
+	 *
913
+	 * @param bool $clear_session Set to true when you want to clear the session on revisiting the
914
+	 *                            payment overview url.
915
+	 * @return string
916
+	 * @throws InvalidInterfaceException
917
+	 * @throws InvalidDataTypeException
918
+	 * @throws EE_Error
919
+	 * @throws InvalidArgumentException
920
+	 */
921
+	public function payment_overview_url($clear_session = false)
922
+	{
923
+		return add_query_arg(
924
+			(array) apply_filters(
925
+				'FHEE__EE_Registration__payment_overview_url__query_args',
926
+				array(
927
+					'e_reg_url_link' => $this->reg_url_link(),
928
+					'step'           => 'payment_options',
929
+					'revisit'        => true,
930
+					'clear_session'  => (bool) $clear_session,
931
+				),
932
+				$this
933
+			),
934
+			EE_Registry::instance()->CFG->core->reg_page_url()
935
+		);
936
+	}
937
+
938
+
939
+	/**
940
+	 * Gets the URL for the checkout attendee information reg step
941
+	 * with this registration's REG_url_link added as a query parameter
942
+	 *
943
+	 * @return string
944
+	 * @throws InvalidInterfaceException
945
+	 * @throws InvalidDataTypeException
946
+	 * @throws EE_Error
947
+	 * @throws InvalidArgumentException
948
+	 */
949
+	public function edit_attendee_information_url()
950
+	{
951
+		return add_query_arg(
952
+			(array) apply_filters(
953
+				'FHEE__EE_Registration__edit_attendee_information_url__query_args',
954
+				array(
955
+					'e_reg_url_link' => $this->reg_url_link(),
956
+					'step'           => 'attendee_information',
957
+					'revisit'        => true,
958
+				),
959
+				$this
960
+			),
961
+			EE_Registry::instance()->CFG->core->reg_page_url()
962
+		);
963
+	}
964
+
965
+
966
+	/**
967
+	 * Simply generates and returns the appropriate admin_url link to edit this registration
968
+	 *
969
+	 * @return string
970
+	 * @throws EE_Error
971
+	 */
972
+	public function get_admin_edit_url()
973
+	{
974
+		return EEH_URL::add_query_args_and_nonce(
975
+			array(
976
+				'page'    => 'espresso_registrations',
977
+				'action'  => 'view_registration',
978
+				'_REG_ID' => $this->ID(),
979
+			),
980
+			admin_url('admin.php')
981
+		);
982
+	}
983
+
984
+
985
+	/**
986
+	 *    is_primary_registrant?
987
+	 */
988
+	public function is_primary_registrant()
989
+	{
990
+		return $this->get('REG_count') == 1 ? true : false;
991
+	}
992
+
993
+
994
+	/**
995
+	 * This returns the primary registration object for this registration group (which may be this object).
996
+	 *
997
+	 * @return EE_Registration
998
+	 * @throws EE_Error
999
+	 */
1000
+	public function get_primary_registration()
1001
+	{
1002
+		if ($this->is_primary_registrant()) {
1003
+			return $this;
1004
+		}
1005
+
1006
+		// k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1007
+		/** @var EE_Registration $primary_registrant */
1008
+		$primary_registrant = EEM_Registration::instance()->get_one(
1009
+			array(
1010
+				array(
1011
+					'TXN_ID'    => $this->transaction_ID(),
1012
+					'REG_count' => 1,
1013
+				),
1014
+			)
1015
+		);
1016
+		return $primary_registrant;
1017
+	}
1018
+
1019
+
1020
+	/**
1021
+	 *        get  Attendee Number
1022
+	 *
1023
+	 * @access        public
1024
+	 */
1025
+	public function count()
1026
+	{
1027
+		return $this->get('REG_count');
1028
+	}
1029
+
1030
+
1031
+	/**
1032
+	 *        get Group Size
1033
+	 */
1034
+	public function group_size()
1035
+	{
1036
+		return $this->get('REG_group_size');
1037
+	}
1038
+
1039
+
1040
+	/**
1041
+	 *        get Registration Date
1042
+	 */
1043
+	public function date()
1044
+	{
1045
+		return $this->get('REG_date');
1046
+	}
1047
+
1048
+
1049
+	/**
1050
+	 * gets a pretty date
1051
+	 *
1052
+	 * @param string $date_format
1053
+	 * @param string $time_format
1054
+	 * @return string
1055
+	 * @throws EE_Error
1056
+	 */
1057
+	public function pretty_date($date_format = null, $time_format = null)
1058
+	{
1059
+		return $this->get_datetime('REG_date', $date_format, $time_format);
1060
+	}
1061
+
1062
+
1063
+	/**
1064
+	 * final_price
1065
+	 * the registration's share of the transaction total, so that the
1066
+	 * sum of all the transaction's REG_final_prices equal the transaction's total
1067
+	 *
1068
+	 * @return float
1069
+	 * @throws EE_Error
1070
+	 */
1071
+	public function final_price()
1072
+	{
1073
+		return $this->get('REG_final_price');
1074
+	}
1075
+
1076
+
1077
+	/**
1078
+	 * pretty_final_price
1079
+	 *  final price as formatted string, with correct decimal places and currency symbol
1080
+	 *
1081
+	 * @return string
1082
+	 * @throws EE_Error
1083
+	 */
1084
+	public function pretty_final_price()
1085
+	{
1086
+		return $this->get_pretty('REG_final_price');
1087
+	}
1088
+
1089
+
1090
+	/**
1091
+	 * get paid (yeah)
1092
+	 *
1093
+	 * @return float
1094
+	 * @throws EE_Error
1095
+	 */
1096
+	public function paid()
1097
+	{
1098
+		return $this->get('REG_paid');
1099
+	}
1100
+
1101
+
1102
+	/**
1103
+	 * pretty_paid
1104
+	 *
1105
+	 * @return float
1106
+	 * @throws EE_Error
1107
+	 */
1108
+	public function pretty_paid()
1109
+	{
1110
+		return $this->get_pretty('REG_paid');
1111
+	}
1112
+
1113
+
1114
+	/**
1115
+	 * owes_monies_and_can_pay
1116
+	 * whether or not this registration has monies owing and it's' status allows payment
1117
+	 *
1118
+	 * @param array $requires_payment
1119
+	 * @return bool
1120
+	 * @throws EE_Error
1121
+	 */
1122
+	public function owes_monies_and_can_pay($requires_payment = array())
1123
+	{
1124
+		// these reg statuses require payment (if event is not free)
1125
+		$requires_payment = ! empty($requires_payment)
1126
+			? $requires_payment
1127
+			: EEM_Registration::reg_statuses_that_allow_payment();
1128
+		if (in_array($this->status_ID(), $requires_payment) &&
1129
+			$this->final_price() != 0 &&
1130
+			$this->final_price() != $this->paid()
1131
+		) {
1132
+			return true;
1133
+		} else {
1134
+			return false;
1135
+		}
1136
+	}
1137
+
1138
+
1139
+	/**
1140
+	 * Prints out the return value of $this->pretty_status()
1141
+	 *
1142
+	 * @param bool $show_icons
1143
+	 * @return void
1144
+	 * @throws EE_Error
1145
+	 */
1146
+	public function e_pretty_status($show_icons = false)
1147
+	{
1148
+		echo $this->pretty_status($show_icons);
1149
+	}
1150
+
1151
+
1152
+	/**
1153
+	 * Returns a nice version of the status for displaying to customers
1154
+	 *
1155
+	 * @param bool $show_icons
1156
+	 * @return string
1157
+	 * @throws EE_Error
1158
+	 */
1159
+	public function pretty_status($show_icons = false)
1160
+	{
1161
+		$status = EEM_Status::instance()->localized_status(
1162
+			array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
1163
+			false,
1164
+			'sentence'
1165
+		);
1166
+		$icon = '';
1167
+		switch ($this->status_ID()) {
1168
+			case EEM_Registration::status_id_approved:
1169
+				$icon = $show_icons
1170
+					? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1171
+					: '';
1172
+				break;
1173
+			case EEM_Registration::status_id_pending_payment:
1174
+				$icon = $show_icons
1175
+					? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1176
+					: '';
1177
+				break;
1178
+			case EEM_Registration::status_id_not_approved:
1179
+				$icon = $show_icons
1180
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1181
+					: '';
1182
+				break;
1183
+			case EEM_Registration::status_id_cancelled:
1184
+				$icon = $show_icons
1185
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1186
+					: '';
1187
+				break;
1188
+			case EEM_Registration::status_id_incomplete:
1189
+				$icon = $show_icons
1190
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1191
+					: '';
1192
+				break;
1193
+			case EEM_Registration::status_id_declined:
1194
+				$icon = $show_icons
1195
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1196
+					: '';
1197
+				break;
1198
+			case EEM_Registration::status_id_wait_list:
1199
+				$icon = $show_icons
1200
+					? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1201
+					: '';
1202
+				break;
1203
+		}
1204
+		return $icon . $status[ $this->status_ID() ];
1205
+	}
1206
+
1207
+
1208
+	/**
1209
+	 *        get Attendee Is Going
1210
+	 */
1211
+	public function att_is_going()
1212
+	{
1213
+		return $this->get('REG_att_is_going');
1214
+	}
1215
+
1216
+
1217
+	/**
1218
+	 * Gets related answers
1219
+	 *
1220
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1221
+	 * @return EE_Answer[]
1222
+	 * @throws EE_Error
1223
+	 */
1224
+	public function answers($query_params = null)
1225
+	{
1226
+		return $this->get_many_related('Answer', $query_params);
1227
+	}
1228
+
1229
+
1230
+	/**
1231
+	 * Gets the registration's answer value to the specified question
1232
+	 * (either the question's ID or a question object)
1233
+	 *
1234
+	 * @param EE_Question|int $question
1235
+	 * @param bool            $pretty_value
1236
+	 * @return array|string if pretty_value= true, the result will always be a string
1237
+	 * (because the answer might be an array of answer values, so passing pretty_value=true
1238
+	 * will convert it into some kind of string)
1239
+	 * @throws EE_Error
1240
+	 */
1241
+	public function answer_value_to_question($question, $pretty_value = true)
1242
+	{
1243
+		$question_id = EEM_Question::instance()->ensure_is_ID($question);
1244
+		return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1245
+	}
1246
+
1247
+
1248
+	/**
1249
+	 * question_groups
1250
+	 * returns an array of EE_Question_Group objects for this registration
1251
+	 *
1252
+	 * @return EE_Question_Group[]
1253
+	 * @throws EE_Error
1254
+	 * @throws EntityNotFoundException
1255
+	 */
1256
+	public function question_groups()
1257
+	{
1258
+		$question_groups = array();
1259
+		if ($this->event() instanceof EE_Event) {
1260
+			$question_groups = $this->event()->question_groups(
1261
+				array(
1262
+					array(
1263
+						'Event_Question_Group.EQG_primary' => $this->count() == 1 ? true : false,
1264
+					),
1265
+					'order_by' => array('QSG_order' => 'ASC'),
1266
+				)
1267
+			);
1268
+		}
1269
+		return $question_groups;
1270
+	}
1271
+
1272
+
1273
+	/**
1274
+	 * count_question_groups
1275
+	 * returns a count of the number of EE_Question_Group objects for this registration
1276
+	 *
1277
+	 * @return int
1278
+	 * @throws EE_Error
1279
+	 * @throws EntityNotFoundException
1280
+	 */
1281
+	public function count_question_groups()
1282
+	{
1283
+		$qg_count = 0;
1284
+		if ($this->event() instanceof EE_Event) {
1285
+			$qg_count = $this->event()->count_related(
1286
+				'Question_Group',
1287
+				array(
1288
+					array(
1289
+						'Event_Question_Group.EQG_primary' => $this->count() == 1 ? true : false,
1290
+					),
1291
+				)
1292
+			);
1293
+		}
1294
+		return $qg_count;
1295
+	}
1296
+
1297
+
1298
+	/**
1299
+	 * Returns the registration date in the 'standard' string format
1300
+	 * (function may be improved in the future to allow for different formats and timezones)
1301
+	 *
1302
+	 * @return string
1303
+	 * @throws EE_Error
1304
+	 */
1305
+	public function reg_date()
1306
+	{
1307
+		return $this->get_datetime('REG_date');
1308
+	}
1309
+
1310
+
1311
+	/**
1312
+	 * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1313
+	 * the ticket this registration purchased, or the datetime they have registered
1314
+	 * to attend)
1315
+	 *
1316
+	 * @return EE_Datetime_Ticket
1317
+	 * @throws EE_Error
1318
+	 */
1319
+	public function datetime_ticket()
1320
+	{
1321
+		return $this->get_first_related('Datetime_Ticket');
1322
+	}
1323
+
1324
+
1325
+	/**
1326
+	 * Sets the registration's datetime_ticket.
1327
+	 *
1328
+	 * @param EE_Datetime_Ticket $datetime_ticket
1329
+	 * @return EE_Datetime_Ticket
1330
+	 * @throws EE_Error
1331
+	 */
1332
+	public function set_datetime_ticket($datetime_ticket)
1333
+	{
1334
+		return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1335
+	}
1336
+
1337
+	/**
1338
+	 * Gets deleted
1339
+	 *
1340
+	 * @return bool
1341
+	 * @throws EE_Error
1342
+	 */
1343
+	public function deleted()
1344
+	{
1345
+		return $this->get('REG_deleted');
1346
+	}
1347
+
1348
+	/**
1349
+	 * Sets deleted
1350
+	 *
1351
+	 * @param boolean $deleted
1352
+	 * @return bool
1353
+	 * @throws EE_Error
1354
+	 * @throws RuntimeException
1355
+	 */
1356
+	public function set_deleted($deleted)
1357
+	{
1358
+		if ($deleted) {
1359
+			$this->delete();
1360
+		} else {
1361
+			$this->restore();
1362
+		}
1363
+	}
1364
+
1365
+
1366
+	/**
1367
+	 * Get the status object of this object
1368
+	 *
1369
+	 * @return EE_Status
1370
+	 * @throws EE_Error
1371
+	 */
1372
+	public function status_obj()
1373
+	{
1374
+		return $this->get_first_related('Status');
1375
+	}
1376
+
1377
+
1378
+	/**
1379
+	 * Returns the number of times this registration has checked into any of the datetimes
1380
+	 * its available for
1381
+	 *
1382
+	 * @return int
1383
+	 * @throws EE_Error
1384
+	 */
1385
+	public function count_checkins()
1386
+	{
1387
+		return $this->get_model()->count_related($this, 'Checkin');
1388
+	}
1389
+
1390
+
1391
+	/**
1392
+	 * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1393
+	 * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1394
+	 *
1395
+	 * @return int
1396
+	 * @throws EE_Error
1397
+	 */
1398
+	public function count_checkins_not_checkedout()
1399
+	{
1400
+		return $this->get_model()->count_related($this, 'Checkin', array(array('CHK_in' => 1)));
1401
+	}
1402
+
1403
+
1404
+	/**
1405
+	 * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1406
+	 *
1407
+	 * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1408
+	 * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1409
+	 *                                          consider registration status as well as datetime access.
1410
+	 * @return bool
1411
+	 * @throws EE_Error
1412
+	 */
1413
+	public function can_checkin($DTT_OR_ID, $check_approved = true)
1414
+	{
1415
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1416
+
1417
+		// first check registration status
1418
+		if (($check_approved && ! $this->is_approved()) || ! $DTT_ID) {
1419
+			return false;
1420
+		}
1421
+		// is there a datetime ticket that matches this dtt_ID?
1422
+		if (! (EEM_Datetime_Ticket::instance()->exists(
1423
+			array(
1424
+				array(
1425
+					'TKT_ID' => $this->get('TKT_ID'),
1426
+					'DTT_ID' => $DTT_ID,
1427
+				),
1428
+			)
1429
+		))
1430
+		) {
1431
+			return false;
1432
+		}
1433
+
1434
+		// final check is against TKT_uses
1435
+		return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1436
+	}
1437
+
1438
+
1439
+	/**
1440
+	 * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1441
+	 * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1442
+	 * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1443
+	 * then return false.  Otherwise return true.
1444
+	 *
1445
+	 * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1446
+	 * @return bool true means can checkin.  false means cannot checkin.
1447
+	 * @throws EE_Error
1448
+	 */
1449
+	public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1450
+	{
1451
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1452
+
1453
+		if (! $DTT_ID) {
1454
+			return false;
1455
+		}
1456
+
1457
+		$max_uses = $this->ticket() instanceof EE_Ticket ? $this->ticket()->uses() : EE_INF;
1458
+
1459
+		// if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1460
+		// check-in or not.
1461
+		if (! $max_uses || $max_uses === EE_INF) {
1462
+			return true;
1463
+		}
1464
+
1465
+		// does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1466
+		// go ahead and toggle.
1467
+		if (EEM_Checkin::instance()->exists(array(array('REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID)))) {
1468
+			return true;
1469
+		}
1470
+
1471
+		// made it here so the last check is whether the number of checkins per unique datetime on this registration
1472
+		// disallows further check-ins.
1473
+		$count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1474
+			array(
1475
+				array(
1476
+					'REG_ID' => $this->ID(),
1477
+					'CHK_in' => true,
1478
+				),
1479
+			),
1480
+			'DTT_ID',
1481
+			true
1482
+		);
1483
+		// checkins have already reached their max number of uses
1484
+		// so registrant can NOT checkin
1485
+		if ($count_unique_dtt_checkins >= $max_uses) {
1486
+			EE_Error::add_error(
1487
+				esc_html__(
1488
+					'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1489
+					'event_espresso'
1490
+				),
1491
+				__FILE__,
1492
+				__FUNCTION__,
1493
+				__LINE__
1494
+			);
1495
+			return false;
1496
+		}
1497
+		return true;
1498
+	}
1499
+
1500
+
1501
+	/**
1502
+	 * toggle Check-in status for this registration
1503
+	 * Check-ins are toggled in the following order:
1504
+	 * never checked in -> checked in
1505
+	 * checked in -> checked out
1506
+	 * checked out -> checked in
1507
+	 *
1508
+	 * @param  int $DTT_ID  include specific datetime to toggle Check-in for.
1509
+	 *                      If not included or null, then it is assumed latest datetime is being toggled.
1510
+	 * @param bool $verify  If true then can_checkin() is used to verify whether the person
1511
+	 *                      can be checked in or not.  Otherwise this forces change in checkin status.
1512
+	 * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1513
+	 * @throws EE_Error
1514
+	 */
1515
+	public function toggle_checkin_status($DTT_ID = null, $verify = false)
1516
+	{
1517
+		if (empty($DTT_ID)) {
1518
+			$datetime = $this->get_latest_related_datetime();
1519
+			$DTT_ID = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1520
+			// verify the registration can checkin for the given DTT_ID
1521
+		} elseif (! $this->can_checkin($DTT_ID, $verify)) {
1522
+			EE_Error::add_error(
1523
+				sprintf(
1524
+					esc_html__(
1525
+						'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1526
+						'event_espresso'
1527
+					),
1528
+					$this->ID(),
1529
+					$DTT_ID
1530
+				),
1531
+				__FILE__,
1532
+				__FUNCTION__,
1533
+				__LINE__
1534
+			);
1535
+			return false;
1536
+		}
1537
+		$status_paths = array(
1538
+			EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1539
+			EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1540
+			EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1541
+		);
1542
+		// start by getting the current status so we know what status we'll be changing to.
1543
+		$cur_status = $this->check_in_status_for_datetime($DTT_ID, null);
1544
+		$status_to = $status_paths[ $cur_status ];
1545
+		// database only records true for checked IN or false for checked OUT
1546
+		// no record ( null ) means checked in NEVER, but we obviously don't save that
1547
+		$new_status = $status_to === EE_Checkin::status_checked_in ? true : false;
1548
+		// add relation - note Check-ins are always creating new rows
1549
+		// because we are keeping track of Check-ins over time.
1550
+		// Eventually we'll probably want to show a list table
1551
+		// for the individual Check-ins so that they can be managed.
1552
+		$checkin = EE_Checkin::new_instance(
1553
+			array(
1554
+				'REG_ID' => $this->ID(),
1555
+				'DTT_ID' => $DTT_ID,
1556
+				'CHK_in' => $new_status,
1557
+			)
1558
+		);
1559
+		// if the record could not be saved then return false
1560
+		if ($checkin->save() === 0) {
1561
+			if (WP_DEBUG) {
1562
+				global $wpdb;
1563
+				$error = sprintf(
1564
+					esc_html__(
1565
+						'Registration check in update failed because of the following database error: %1$s%2$s',
1566
+						'event_espresso'
1567
+					),
1568
+					'<br />',
1569
+					$wpdb->last_error
1570
+				);
1571
+			} else {
1572
+				$error = esc_html__(
1573
+					'Registration check in update failed because of an unknown database error',
1574
+					'event_espresso'
1575
+				);
1576
+			}
1577
+			EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1578
+			return false;
1579
+		}
1580
+		return $status_to;
1581
+	}
1582
+
1583
+
1584
+	/**
1585
+	 * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1586
+	 * "Latest" is defined by the `DTT_EVT_start` column.
1587
+	 *
1588
+	 * @return EE_Datetime|null
1589
+	 * @throws EE_Error
1590
+	 */
1591
+	public function get_latest_related_datetime()
1592
+	{
1593
+		return EEM_Datetime::instance()->get_one(
1594
+			array(
1595
+				array(
1596
+					'Ticket.Registration.REG_ID' => $this->ID(),
1597
+				),
1598
+				'order_by' => array('DTT_EVT_start' => 'DESC'),
1599
+			)
1600
+		);
1601
+	}
1602
+
1603
+
1604
+	/**
1605
+	 * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1606
+	 * "Earliest" is defined by the `DTT_EVT_start` column.
1607
+	 *
1608
+	 * @throws EE_Error
1609
+	 */
1610
+	public function get_earliest_related_datetime()
1611
+	{
1612
+		return EEM_Datetime::instance()->get_one(
1613
+			array(
1614
+				array(
1615
+					'Ticket.Registration.REG_ID' => $this->ID(),
1616
+				),
1617
+				'order_by' => array('DTT_EVT_start' => 'ASC'),
1618
+			)
1619
+		);
1620
+	}
1621
+
1622
+
1623
+	/**
1624
+	 * This method simply returns the check-in status for this registration and the given datetime.
1625
+	 * If neither the datetime nor the checkin values are provided as arguments,
1626
+	 * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1627
+	 *
1628
+	 * @param  int       $DTT_ID  The ID of the datetime we're checking against
1629
+	 *                            (if empty we'll get the primary datetime for
1630
+	 *                            this registration (via event) and use it's ID);
1631
+	 * @param EE_Checkin $checkin If present, we use the given checkin object rather than the dtt_id.
1632
+	 *
1633
+	 * @return int                Integer representing Check-in status.
1634
+	 * @throws EE_Error
1635
+	 */
1636
+	public function check_in_status_for_datetime($DTT_ID = 0, $checkin = null)
1637
+	{
1638
+		$checkin_query_params = array(
1639
+			'order_by' => array('CHK_timestamp' => 'DESC'),
1640
+		);
1641
+
1642
+		if ($DTT_ID > 0) {
1643
+			$checkin_query_params[0] = array('DTT_ID' => $DTT_ID);
1644
+		}
1645
+
1646
+		// get checkin object (if exists)
1647
+		$checkin = $checkin instanceof EE_Checkin
1648
+			? $checkin
1649
+			: $this->get_first_related('Checkin', $checkin_query_params);
1650
+		if ($checkin instanceof EE_Checkin) {
1651
+			if ($checkin->get('CHK_in')) {
1652
+				return EE_Checkin::status_checked_in; // checked in
1653
+			}
1654
+			return EE_Checkin::status_checked_out; // had checked in but is now checked out.
1655
+		}
1656
+		return EE_Checkin::status_checked_never; // never been checked in
1657
+	}
1658
+
1659
+
1660
+	/**
1661
+	 * This method returns a localized message for the toggled Check-in message.
1662
+	 *
1663
+	 * @param  int $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1664
+	 *                     then it is assumed Check-in for primary datetime was toggled.
1665
+	 * @param bool $error  This just flags that you want an error message returned. This is put in so that the error
1666
+	 *                     message can be customized with the attendee name.
1667
+	 * @return string internationalized message
1668
+	 * @throws EE_Error
1669
+	 */
1670
+	public function get_checkin_msg($DTT_ID, $error = false)
1671
+	{
1672
+		// let's get the attendee first so we can include the name of the attendee
1673
+		$attendee = $this->get_first_related('Attendee');
1674
+		if ($attendee instanceof EE_Attendee) {
1675
+			if ($error) {
1676
+				return sprintf(__("%s's check-in status was not changed.", "event_espresso"), $attendee->full_name());
1677
+			}
1678
+			$cur_status = $this->check_in_status_for_datetime($DTT_ID);
1679
+			// what is the status message going to be?
1680
+			switch ($cur_status) {
1681
+				case EE_Checkin::status_checked_never:
1682
+					return sprintf(
1683
+						__("%s has been removed from Check-in records", "event_espresso"),
1684
+						$attendee->full_name()
1685
+					);
1686
+					break;
1687
+				case EE_Checkin::status_checked_in:
1688
+					return sprintf(__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1689
+					break;
1690
+				case EE_Checkin::status_checked_out:
1691
+					return sprintf(__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1692
+					break;
1693
+			}
1694
+		}
1695
+		return esc_html__("The check-in status could not be determined.", "event_espresso");
1696
+	}
1697
+
1698
+
1699
+	/**
1700
+	 * Returns the related EE_Transaction to this registration
1701
+	 *
1702
+	 * @return EE_Transaction
1703
+	 * @throws EE_Error
1704
+	 * @throws EntityNotFoundException
1705
+	 */
1706
+	public function transaction()
1707
+	{
1708
+		$transaction = $this->get_first_related('Transaction');
1709
+		if (! $transaction instanceof \EE_Transaction) {
1710
+			throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1711
+		}
1712
+		return $transaction;
1713
+	}
1714
+
1715
+
1716
+	/**
1717
+	 *        get Registration Code
1718
+	 */
1719
+	public function reg_code()
1720
+	{
1721
+		return $this->get('REG_code');
1722
+	}
1723
+
1724
+
1725
+	/**
1726
+	 *        get Transaction ID
1727
+	 */
1728
+	public function transaction_ID()
1729
+	{
1730
+		return $this->get('TXN_ID');
1731
+	}
1732
+
1733
+
1734
+	/**
1735
+	 * @return int
1736
+	 * @throws EE_Error
1737
+	 */
1738
+	public function ticket_ID()
1739
+	{
1740
+		return $this->get('TKT_ID');
1741
+	}
1742
+
1743
+
1744
+	/**
1745
+	 *        Set Registration Code
1746
+	 *
1747
+	 * @access    public
1748
+	 * @param    string  $REG_code Registration Code
1749
+	 * @param    boolean $use_default
1750
+	 * @throws EE_Error
1751
+	 */
1752
+	public function set_reg_code($REG_code, $use_default = false)
1753
+	{
1754
+		if (empty($REG_code)) {
1755
+			EE_Error::add_error(
1756
+				esc_html__('REG_code can not be empty.', 'event_espresso'),
1757
+				__FILE__,
1758
+				__FUNCTION__,
1759
+				__LINE__
1760
+			);
1761
+			return;
1762
+		}
1763
+		if (! $this->reg_code()) {
1764
+			parent::set('REG_code', $REG_code, $use_default);
1765
+		} else {
1766
+			EE_Error::doing_it_wrong(
1767
+				__CLASS__ . '::' . __FUNCTION__,
1768
+				esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
1769
+				'4.6.0'
1770
+			);
1771
+		}
1772
+	}
1773
+
1774
+
1775
+	/**
1776
+	 * Returns all other registrations in the same group as this registrant who have the same ticket option.
1777
+	 * Note, if you want to just get all registrations in the same transaction (group), use:
1778
+	 *    $registration->transaction()->registrations();
1779
+	 *
1780
+	 * @since 4.5.0
1781
+	 * @return EE_Registration[] or empty array if this isn't a group registration.
1782
+	 * @throws EE_Error
1783
+	 */
1784
+	public function get_all_other_registrations_in_group()
1785
+	{
1786
+		if ($this->group_size() < 2) {
1787
+			return array();
1788
+		}
1789
+
1790
+		$query[0] = array(
1791
+			'TXN_ID' => $this->transaction_ID(),
1792
+			'REG_ID' => array('!=', $this->ID()),
1793
+			'TKT_ID' => $this->ticket_ID(),
1794
+		);
1795
+		/** @var EE_Registration[] $registrations */
1796
+		$registrations = $this->get_model()->get_all($query);
1797
+		return $registrations;
1798
+	}
1799
+
1800
+	/**
1801
+	 * Return the link to the admin details for the object.
1802
+	 *
1803
+	 * @return string
1804
+	 * @throws EE_Error
1805
+	 */
1806
+	public function get_admin_details_link()
1807
+	{
1808
+		EE_Registry::instance()->load_helper('URL');
1809
+		return EEH_URL::add_query_args_and_nonce(
1810
+			array(
1811
+				'page'    => 'espresso_registrations',
1812
+				'action'  => 'view_registration',
1813
+				'_REG_ID' => $this->ID(),
1814
+			),
1815
+			admin_url('admin.php')
1816
+		);
1817
+	}
1818
+
1819
+	/**
1820
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
1821
+	 *
1822
+	 * @return string
1823
+	 * @throws EE_Error
1824
+	 */
1825
+	public function get_admin_edit_link()
1826
+	{
1827
+		return $this->get_admin_details_link();
1828
+	}
1829
+
1830
+	/**
1831
+	 * Returns the link to a settings page for the object.
1832
+	 *
1833
+	 * @return string
1834
+	 * @throws EE_Error
1835
+	 */
1836
+	public function get_admin_settings_link()
1837
+	{
1838
+		return $this->get_admin_details_link();
1839
+	}
1840
+
1841
+	/**
1842
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
1843
+	 *
1844
+	 * @return string
1845
+	 */
1846
+	public function get_admin_overview_link()
1847
+	{
1848
+		EE_Registry::instance()->load_helper('URL');
1849
+		return EEH_URL::add_query_args_and_nonce(
1850
+			array(
1851
+				'page' => 'espresso_registrations',
1852
+			),
1853
+			admin_url('admin.php')
1854
+		);
1855
+	}
1856
+
1857
+
1858
+	/**
1859
+	 * @param array $query_params
1860
+	 *
1861
+	 * @return \EE_Registration[]
1862
+	 * @throws EE_Error
1863
+	 */
1864
+	public function payments($query_params = array())
1865
+	{
1866
+		return $this->get_many_related('Payment', $query_params);
1867
+	}
1868
+
1869
+
1870
+	/**
1871
+	 * @param array $query_params
1872
+	 *
1873
+	 * @return \EE_Registration_Payment[]
1874
+	 * @throws EE_Error
1875
+	 */
1876
+	public function registration_payments($query_params = array())
1877
+	{
1878
+		return $this->get_many_related('Registration_Payment', $query_params);
1879
+	}
1880
+
1881
+
1882
+	/**
1883
+	 * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
1884
+	 * Note: if there are no payments on the registration there will be no payment method returned.
1885
+	 *
1886
+	 * @return EE_Payment_Method|null
1887
+	 */
1888
+	public function payment_method()
1889
+	{
1890
+		return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
1891
+	}
1892
+
1893
+
1894
+	/**
1895
+	 * @return \EE_Line_Item
1896
+	 * @throws EntityNotFoundException
1897
+	 * @throws EE_Error
1898
+	 */
1899
+	public function ticket_line_item()
1900
+	{
1901
+		$ticket = $this->ticket();
1902
+		$transaction = $this->transaction();
1903
+		$line_item = null;
1904
+		$ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
1905
+			$transaction->total_line_item(),
1906
+			'Ticket',
1907
+			array($ticket->ID())
1908
+		);
1909
+		foreach ($ticket_line_items as $ticket_line_item) {
1910
+			if ($ticket_line_item instanceof \EE_Line_Item
1911
+				&& $ticket_line_item->OBJ_type() === 'Ticket'
1912
+				&& $ticket_line_item->OBJ_ID() === $ticket->ID()
1913
+			) {
1914
+				$line_item = $ticket_line_item;
1915
+				break;
1916
+			}
1917
+		}
1918
+		if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
1919
+			throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
1920
+		}
1921
+		return $line_item;
1922
+	}
1923
+
1924
+
1925
+	/**
1926
+	 * Soft Deletes this model object.
1927
+	 *
1928
+	 * @return boolean | int
1929
+	 * @throws RuntimeException
1930
+	 * @throws EE_Error
1931
+	 */
1932
+	public function delete()
1933
+	{
1934
+		if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
1935
+			$this->set_status(EEM_Registration::status_id_cancelled);
1936
+		}
1937
+		return parent::delete();
1938
+	}
1939
+
1940
+
1941
+	/**
1942
+	 * Restores whatever the previous status was on a registration before it was trashed (if possible)
1943
+	 *
1944
+	 * @throws EE_Error
1945
+	 * @throws RuntimeException
1946
+	 */
1947
+	public function restore()
1948
+	{
1949
+		$previous_status = $this->get_extra_meta(
1950
+			EE_Registration::PRE_TRASH_REG_STATUS_KEY,
1951
+			true,
1952
+			EEM_Registration::status_id_cancelled
1953
+		);
1954
+		if ($previous_status) {
1955
+			$this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
1956
+			$this->set_status($previous_status);
1957
+		}
1958
+		return parent::restore();
1959
+	}
1960
+
1961
+
1962
+	/**
1963
+	 * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
1964
+	 *
1965
+	 * @param  boolean $trigger_set_status_logic EE_Registration::set_status() can trigger additional logic
1966
+	 *                                           depending on whether the reg status changes to or from "Approved"
1967
+	 * @return boolean whether the Registration status was updated
1968
+	 * @throws EE_Error
1969
+	 * @throws RuntimeException
1970
+	 */
1971
+	public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
1972
+	{
1973
+		$paid = $this->paid();
1974
+		$price = $this->final_price();
1975
+		switch (true) {
1976
+			// overpaid or paid
1977
+			case EEH_Money::compare_floats($paid, $price, '>'):
1978
+			case EEH_Money::compare_floats($paid, $price):
1979
+				$new_status = EEM_Registration::status_id_approved;
1980
+				break;
1981
+			//  underpaid
1982
+			case EEH_Money::compare_floats($paid, $price, '<'):
1983
+				$new_status = EEM_Registration::status_id_pending_payment;
1984
+				break;
1985
+			// uhhh Houston...
1986
+			default:
1987
+				throw new RuntimeException(
1988
+					esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
1989
+				);
1990
+		}
1991
+		if ($new_status !== $this->status_ID()) {
1992
+			if ($trigger_set_status_logic) {
1993
+				return $this->set_status($new_status);
1994
+			}
1995
+			parent::set('STS_ID', $new_status);
1996
+			return true;
1997
+		}
1998
+		return false;
1999
+	}
2000
+
2001
+
2002
+	/*************************** DEPRECATED ***************************/
2003
+
2004
+
2005
+	/**
2006
+	 * @deprecated
2007
+	 * @since     4.7.0
2008
+	 * @access    public
2009
+	 */
2010
+	public function price_paid()
2011
+	{
2012
+		EE_Error::doing_it_wrong(
2013
+			'EE_Registration::price_paid()',
2014
+			esc_html__(
2015
+				'This method is deprecated, please use EE_Registration::final_price() instead.',
2016
+				'event_espresso'
2017
+			),
2018
+			'4.7.0'
2019
+		);
2020
+		return $this->final_price();
2021
+	}
2022
+
2023
+
2024
+	/**
2025
+	 * @deprecated
2026
+	 * @since     4.7.0
2027
+	 * @access    public
2028
+	 * @param    float $REG_final_price
2029
+	 * @throws EE_Error
2030
+	 * @throws RuntimeException
2031
+	 */
2032
+	public function set_price_paid($REG_final_price = 0.00)
2033
+	{
2034
+		EE_Error::doing_it_wrong(
2035
+			'EE_Registration::set_price_paid()',
2036
+			esc_html__(
2037
+				'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2038
+				'event_espresso'
2039
+			),
2040
+			'4.7.0'
2041
+		);
2042
+		$this->set_final_price($REG_final_price);
2043
+	}
2044
+
2045
+
2046
+	/**
2047
+	 * @deprecated
2048
+	 * @since 4.7.0
2049
+	 * @return string
2050
+	 * @throws EE_Error
2051
+	 */
2052
+	public function pretty_price_paid()
2053
+	{
2054
+		EE_Error::doing_it_wrong(
2055
+			'EE_Registration::pretty_price_paid()',
2056
+			esc_html__(
2057
+				'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2058
+				'event_espresso'
2059
+			),
2060
+			'4.7.0'
2061
+		);
2062
+		return $this->pretty_final_price();
2063
+	}
2064
+
2065
+
2066
+	/**
2067
+	 * Gets the primary datetime related to this registration via the related Event to this registration
2068
+	 *
2069
+	 * @deprecated 4.9.17
2070
+	 * @return EE_Datetime
2071
+	 * @throws EE_Error
2072
+	 * @throws EntityNotFoundException
2073
+	 */
2074
+	public function get_related_primary_datetime()
2075
+	{
2076
+		EE_Error::doing_it_wrong(
2077
+			__METHOD__,
2078
+			esc_html__(
2079
+				'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2080
+				'event_espresso'
2081
+			),
2082
+			'4.9.17',
2083
+			'5.0.0'
2084
+		);
2085
+		return $this->event()->primary_datetime();
2086
+	}
2087 2087
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Ticket.class.php 1 patch
Indentation   +1460 added lines, -1460 removed lines patch added patch discarded remove patch
@@ -12,1464 +12,1464 @@
 block discarded – undo
12 12
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
13 13
 {
14 14
 
15
-    /**
16
-     * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
17
-     */
18
-    const sold_out = 'TKS';
19
-
20
-    /**
21
-     *
22
-     */
23
-    const expired = 'TKE';
24
-
25
-    /**
26
-     *
27
-     */
28
-    const archived = 'TKA';
29
-
30
-    /**
31
-     *
32
-     */
33
-    const pending = 'TKP';
34
-
35
-    /**
36
-     *
37
-     */
38
-    const onsale = 'TKO';
39
-
40
-    /**
41
-     * extra meta key for tracking ticket reservations
42
-     *
43
-     * @type string
44
-     */
45
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
46
-
47
-    /**
48
-     * cached result from method of the same name
49
-     *
50
-     * @var float $_ticket_total_with_taxes
51
-     */
52
-    private $_ticket_total_with_taxes;
53
-
54
-
55
-    /**
56
-     * @param array  $props_n_values          incoming values
57
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
58
-     *                                        used.)
59
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
60
-     *                                        date_format and the second value is the time format
61
-     * @return EE_Ticket
62
-     * @throws \EE_Error
63
-     */
64
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
65
-    {
66
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
67
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
68
-    }
69
-
70
-
71
-    /**
72
-     * @param array  $props_n_values  incoming values from the database
73
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
74
-     *                                the website will be used.
75
-     * @return EE_Ticket
76
-     * @throws \EE_Error
77
-     */
78
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
79
-    {
80
-        return new self($props_n_values, true, $timezone);
81
-    }
82
-
83
-
84
-    /**
85
-     * @return bool
86
-     * @throws \EE_Error
87
-     */
88
-    public function parent()
89
-    {
90
-        return $this->get('TKT_parent');
91
-    }
92
-
93
-
94
-    /**
95
-     * return if a ticket has quantities available for purchase
96
-     *
97
-     * @param  int $DTT_ID the primary key for a particular datetime
98
-     * @return boolean
99
-     * @throws \EE_Error
100
-     */
101
-    public function available($DTT_ID = 0)
102
-    {
103
-        // are we checking availability for a particular datetime ?
104
-        if ($DTT_ID) {
105
-            // get that datetime object
106
-            $datetime = $this->get_first_related('Datetime', array(array('DTT_ID' => $DTT_ID)));
107
-            // if  ticket sales for this datetime have exceeded the reg limit...
108
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
109
-                return false;
110
-            }
111
-        }
112
-        // datetime is still open for registration, but is this ticket sold out ?
113
-        return $this->qty() < 1 || $this->qty() > $this->sold() ? true : false;
114
-    }
115
-
116
-
117
-    /**
118
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
119
-     *
120
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
121
-     *                               relevant status const
122
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
123
-     *               further processing
124
-     * @return mixed status int if the display string isn't requested
125
-     * @throws \EE_Error
126
-     */
127
-    public function ticket_status($display = false, $remaining = null)
128
-    {
129
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
130
-        if (! $remaining) {
131
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
132
-        }
133
-        if ($this->get('TKT_deleted')) {
134
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
135
-        }
136
-        if ($this->is_expired()) {
137
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
138
-        }
139
-        if ($this->is_pending()) {
140
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
141
-        }
142
-        if ($this->is_on_sale()) {
143
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
144
-        }
145
-        return '';
146
-    }
147
-
148
-
149
-    /**
150
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
151
-     * considering ALL the factors used for figuring that out.
152
-     *
153
-     * @access public
154
-     * @param  int $DTT_ID if an int above 0 is included here then we get a specific dtt.
155
-     * @return boolean         true = tickets remaining, false not.
156
-     * @throws \EE_Error
157
-     */
158
-    public function is_remaining($DTT_ID = 0)
159
-    {
160
-        $num_remaining = $this->remaining($DTT_ID);
161
-        if ($num_remaining === 0) {
162
-            return false;
163
-        }
164
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
165
-            return false;
166
-        }
167
-        return true;
168
-    }
169
-
170
-
171
-    /**
172
-     * return the total number of tickets available for purchase
173
-     *
174
-     * @param  int $DTT_ID the primary key for a particular datetime.
175
-     *                     set to 0 for all related datetimes
176
-     * @return int
177
-     * @throws \EE_Error
178
-     */
179
-    public function remaining($DTT_ID = 0)
180
-    {
181
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
182
-    }
183
-
184
-
185
-    /**
186
-     * Gets min
187
-     *
188
-     * @return int
189
-     * @throws \EE_Error
190
-     */
191
-    public function min()
192
-    {
193
-        return $this->get('TKT_min');
194
-    }
195
-
196
-
197
-    /**
198
-     * return if a ticket is no longer available cause its available dates have expired.
199
-     *
200
-     * @return boolean
201
-     * @throws \EE_Error
202
-     */
203
-    public function is_expired()
204
-    {
205
-        return ($this->get_raw('TKT_end_date') < time());
206
-    }
207
-
208
-
209
-    /**
210
-     * Return if a ticket is yet to go on sale or not
211
-     *
212
-     * @return boolean
213
-     * @throws \EE_Error
214
-     */
215
-    public function is_pending()
216
-    {
217
-        return ($this->get_raw('TKT_start_date') > time());
218
-    }
219
-
220
-
221
-    /**
222
-     * Return if a ticket is on sale or not
223
-     *
224
-     * @return boolean
225
-     * @throws \EE_Error
226
-     */
227
-    public function is_on_sale()
228
-    {
229
-        return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
230
-    }
231
-
232
-
233
-    /**
234
-     * This returns the chronologically last datetime that this ticket is associated with
235
-     *
236
-     * @param string $dt_frmt
237
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
238
-     *                            the end date ie: Jan 01 "to" Dec 31
239
-     * @return string
240
-     * @throws \EE_Error
241
-     */
242
-    public function date_range($dt_frmt = '', $conjunction = ' - ')
243
-    {
244
-        $first_date = $this->first_datetime() instanceof EE_Datetime ? $this->first_datetime()->start_date($dt_frmt)
245
-            : '';
246
-        $last_date = $this->last_datetime() instanceof EE_Datetime ? $this->last_datetime()->end_date($dt_frmt) : '';
247
-
248
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
249
-    }
250
-
251
-
252
-    /**
253
-     * This returns the chronologically first datetime that this ticket is associated with
254
-     *
255
-     * @return EE_Datetime
256
-     * @throws \EE_Error
257
-     */
258
-    public function first_datetime()
259
-    {
260
-        $datetimes = $this->datetimes(array('limit' => 1));
261
-        return reset($datetimes);
262
-    }
263
-
264
-
265
-    /**
266
-     * Gets all the datetimes this ticket can be used for attending.
267
-     * Unless otherwise specified, orders datetimes by start date.
268
-     *
269
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
270
-     * @return EE_Datetime[]|EE_Base_Class[]
271
-     * @throws \EE_Error
272
-     */
273
-    public function datetimes($query_params = array())
274
-    {
275
-        if (! isset($query_params['order_by'])) {
276
-            $query_params['order_by']['DTT_order'] = 'ASC';
277
-        }
278
-        return $this->get_many_related('Datetime', $query_params);
279
-    }
280
-
281
-
282
-    /**
283
-     * This returns the chronologically last datetime that this ticket is associated with
284
-     *
285
-     * @return EE_Datetime
286
-     * @throws \EE_Error
287
-     */
288
-    public function last_datetime()
289
-    {
290
-        $datetimes = $this->datetimes(array('limit' => 1, 'order_by' => array('DTT_EVT_start' => 'DESC')));
291
-        return end($datetimes);
292
-    }
293
-
294
-
295
-    /**
296
-     * This returns the total tickets sold depending on the given parameters.
297
-     *
298
-     * @param  string $what   Can be one of two options: 'ticket', 'datetime'.
299
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
300
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
301
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
302
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
303
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
304
-     * @param  int    $dtt_id [optional] include the dtt_id with $what = 'datetime'.
305
-     * @return mixed (array|int)          how many tickets have sold
306
-     * @throws \EE_Error
307
-     */
308
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
309
-    {
310
-        $total = 0;
311
-        $tickets_sold = $this->_all_tickets_sold();
312
-        switch ($what) {
313
-            case 'ticket':
314
-                return $tickets_sold['ticket'];
315
-                break;
316
-            case 'datetime':
317
-                if (empty($tickets_sold['datetime'])) {
318
-                    return $total;
319
-                }
320
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
321
-                    EE_Error::add_error(
322
-                        __(
323
-                            'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
324
-                            'event_espresso'
325
-                        ),
326
-                        __FILE__,
327
-                        __FUNCTION__,
328
-                        __LINE__
329
-                    );
330
-                    return $total;
331
-                }
332
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
333
-                break;
334
-            default:
335
-                return $total;
336
-        }
337
-    }
338
-
339
-
340
-    /**
341
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
342
-     *
343
-     * @return EE_Ticket[]
344
-     * @throws \EE_Error
345
-     */
346
-    protected function _all_tickets_sold()
347
-    {
348
-        $datetimes = $this->get_many_related('Datetime');
349
-        $tickets_sold = array();
350
-        if (! empty($datetimes)) {
351
-            foreach ($datetimes as $datetime) {
352
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
353
-            }
354
-        }
355
-        // Tickets sold
356
-        $tickets_sold['ticket'] = $this->sold();
357
-        return $tickets_sold;
358
-    }
359
-
360
-
361
-    /**
362
-     * This returns the base price object for the ticket.
363
-     *
364
-     * @param  bool $return_array whether to return as an array indexed by price id or just the object.
365
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
366
-     * @throws \EE_Error
367
-     */
368
-    public function base_price($return_array = false)
369
-    {
370
-        $_where = array('Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price);
371
-        return $return_array
372
-            ? $this->get_many_related('Price', array($_where))
373
-            : $this->get_first_related('Price', array($_where));
374
-    }
375
-
376
-
377
-    /**
378
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
379
-     *
380
-     * @access public
381
-     * @return EE_Price[]
382
-     * @throws \EE_Error
383
-     */
384
-    public function price_modifiers()
385
-    {
386
-        $query_params = array(
387
-            0 => array(
388
-                'Price_Type.PBT_ID' => array(
389
-                    'NOT IN',
390
-                    array(EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax),
391
-                ),
392
-            ),
393
-        );
394
-        return $this->prices($query_params);
395
-    }
396
-
397
-
398
-    /**
399
-     * Gets all the prices that combine to form the final price of this ticket
400
-     *
401
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
402
-     * @return EE_Price[]|EE_Base_Class[]
403
-     * @throws \EE_Error
404
-     */
405
-    public function prices($query_params = array())
406
-    {
407
-        return $this->get_many_related('Price', $query_params);
408
-    }
409
-
410
-
411
-    /**
412
-     * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
413
-     *
414
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
415
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
416
-     * @throws \EE_Error
417
-     */
418
-    public function datetime_tickets($query_params = array())
419
-    {
420
-        return $this->get_many_related('Datetime_Ticket', $query_params);
421
-    }
422
-
423
-
424
-    /**
425
-     * Gets all the datetimes from the db ordered by DTT_order
426
-     *
427
-     * @param boolean $show_expired
428
-     * @param boolean $show_deleted
429
-     * @return EE_Datetime[]
430
-     * @throws \EE_Error
431
-     */
432
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
433
-    {
434
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
435
-            $this->ID(),
436
-            $show_expired,
437
-            $show_deleted
438
-        );
439
-    }
440
-
441
-
442
-    /**
443
-     * Gets ID
444
-     *
445
-     * @return string
446
-     * @throws \EE_Error
447
-     */
448
-    public function ID()
449
-    {
450
-        return $this->get('TKT_ID');
451
-    }
452
-
453
-
454
-    /**
455
-     * get the author of the ticket.
456
-     *
457
-     * @since 4.5.0
458
-     * @return int
459
-     * @throws \EE_Error
460
-     */
461
-    public function wp_user()
462
-    {
463
-        return $this->get('TKT_wp_user');
464
-    }
465
-
466
-
467
-    /**
468
-     * Gets the template for the ticket
469
-     *
470
-     * @return EE_Ticket_Template|EE_Base_Class
471
-     * @throws \EE_Error
472
-     */
473
-    public function template()
474
-    {
475
-        return $this->get_first_related('Ticket_Template');
476
-    }
477
-
478
-
479
-    /**
480
-     * Simply returns an array of EE_Price objects that are taxes.
481
-     *
482
-     * @return EE_Price[]
483
-     * @throws \EE_Error
484
-     */
485
-    public function get_ticket_taxes_for_admin()
486
-    {
487
-        return EE_Taxes::get_taxes_for_admin();
488
-    }
489
-
490
-
491
-    /**
492
-     * @return float
493
-     * @throws \EE_Error
494
-     */
495
-    public function ticket_price()
496
-    {
497
-        return $this->get('TKT_price');
498
-    }
499
-
500
-
501
-    /**
502
-     * @return mixed
503
-     * @throws \EE_Error
504
-     */
505
-    public function pretty_price()
506
-    {
507
-        return $this->get_pretty('TKT_price');
508
-    }
509
-
510
-
511
-    /**
512
-     * @return bool
513
-     * @throws \EE_Error
514
-     */
515
-    public function is_free()
516
-    {
517
-        return $this->get_ticket_total_with_taxes() === (float) 0;
518
-    }
519
-
520
-
521
-    /**
522
-     * get_ticket_total_with_taxes
523
-     *
524
-     * @param bool $no_cache
525
-     * @return float
526
-     * @throws \EE_Error
527
-     */
528
-    public function get_ticket_total_with_taxes($no_cache = false)
529
-    {
530
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
531
-            $this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
532
-        }
533
-        return (float) $this->_ticket_total_with_taxes;
534
-    }
535
-
536
-
537
-    public function ensure_TKT_Price_correct()
538
-    {
539
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
540
-        $this->save();
541
-    }
542
-
543
-
544
-    /**
545
-     * @return float
546
-     * @throws \EE_Error
547
-     */
548
-    public function get_ticket_subtotal()
549
-    {
550
-        return EE_Taxes::get_subtotal_for_admin($this);
551
-    }
552
-
553
-
554
-    /**
555
-     * Returns the total taxes applied to this ticket
556
-     *
557
-     * @return float
558
-     * @throws \EE_Error
559
-     */
560
-    public function get_ticket_taxes_total_for_admin()
561
-    {
562
-        return EE_Taxes::get_total_taxes_for_admin($this);
563
-    }
564
-
565
-
566
-    /**
567
-     * Sets name
568
-     *
569
-     * @param string $name
570
-     * @throws \EE_Error
571
-     */
572
-    public function set_name($name)
573
-    {
574
-        $this->set('TKT_name', $name);
575
-    }
576
-
577
-
578
-    /**
579
-     * Gets description
580
-     *
581
-     * @return string
582
-     * @throws \EE_Error
583
-     */
584
-    public function description()
585
-    {
586
-        return $this->get('TKT_description');
587
-    }
588
-
589
-
590
-    /**
591
-     * Sets description
592
-     *
593
-     * @param string $description
594
-     * @throws \EE_Error
595
-     */
596
-    public function set_description($description)
597
-    {
598
-        $this->set('TKT_description', $description);
599
-    }
600
-
601
-
602
-    /**
603
-     * Gets start_date
604
-     *
605
-     * @param string $dt_frmt
606
-     * @param string $tm_frmt
607
-     * @return string
608
-     * @throws \EE_Error
609
-     */
610
-    public function start_date($dt_frmt = '', $tm_frmt = '')
611
-    {
612
-        return $this->_get_datetime('TKT_start_date', $dt_frmt, $tm_frmt);
613
-    }
614
-
615
-
616
-    /**
617
-     * Sets start_date
618
-     *
619
-     * @param string $start_date
620
-     * @return void
621
-     * @throws \EE_Error
622
-     */
623
-    public function set_start_date($start_date)
624
-    {
625
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
626
-    }
627
-
628
-
629
-    /**
630
-     * Gets end_date
631
-     *
632
-     * @param string $dt_frmt
633
-     * @param string $tm_frmt
634
-     * @return string
635
-     * @throws \EE_Error
636
-     */
637
-    public function end_date($dt_frmt = '', $tm_frmt = '')
638
-    {
639
-        return $this->_get_datetime('TKT_end_date', $dt_frmt, $tm_frmt);
640
-    }
641
-
642
-
643
-    /**
644
-     * Sets end_date
645
-     *
646
-     * @param string $end_date
647
-     * @return void
648
-     * @throws \EE_Error
649
-     */
650
-    public function set_end_date($end_date)
651
-    {
652
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
653
-    }
654
-
655
-
656
-    /**
657
-     * Sets sell until time
658
-     *
659
-     * @since 4.5.0
660
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
661
-     * @throws \EE_Error
662
-     */
663
-    public function set_end_time($time)
664
-    {
665
-        $this->_set_time_for($time, 'TKT_end_date');
666
-    }
667
-
668
-
669
-    /**
670
-     * Sets min
671
-     *
672
-     * @param int $min
673
-     * @return void
674
-     * @throws \EE_Error
675
-     */
676
-    public function set_min($min)
677
-    {
678
-        $this->set('TKT_min', $min);
679
-    }
680
-
681
-
682
-    /**
683
-     * Gets max
684
-     *
685
-     * @return int
686
-     * @throws \EE_Error
687
-     */
688
-    public function max()
689
-    {
690
-        return $this->get('TKT_max');
691
-    }
692
-
693
-
694
-    /**
695
-     * Sets max
696
-     *
697
-     * @param int $max
698
-     * @return void
699
-     * @throws \EE_Error
700
-     */
701
-    public function set_max($max)
702
-    {
703
-        $this->set('TKT_max', $max);
704
-    }
705
-
706
-
707
-    /**
708
-     * Sets price
709
-     *
710
-     * @param float $price
711
-     * @return void
712
-     * @throws \EE_Error
713
-     */
714
-    public function set_price($price)
715
-    {
716
-        $this->set('TKT_price', $price);
717
-    }
718
-
719
-
720
-    /**
721
-     * Gets sold
722
-     *
723
-     * @return int
724
-     * @throws \EE_Error
725
-     */
726
-    public function sold()
727
-    {
728
-        return $this->get_raw('TKT_sold');
729
-    }
730
-
731
-
732
-    /**
733
-     * Sets sold
734
-     *
735
-     * @param int $sold
736
-     * @return void
737
-     * @throws \EE_Error
738
-     */
739
-    public function set_sold($sold)
740
-    {
741
-        // sold can not go below zero
742
-        $sold = max(0, $sold);
743
-        $this->set('TKT_sold', $sold);
744
-    }
745
-
746
-
747
-    /**
748
-     * increments sold by amount passed by $qty
749
-     *
750
-     * @param int $qty
751
-     * @return void
752
-     * @throws \EE_Error
753
-     */
754
-    public function increase_sold($qty = 1)
755
-    {
756
-        $sold = $this->sold() + $qty;
757
-        // remove ticket reservation, but don't adjust datetime reservations,  because that will happen
758
-        // via \EE_Datetime::increase_sold() when \EE_Ticket::_increase_sold_for_datetimes() is called
759
-        $this->decrease_reserved($qty, false, "TKT: {$this->ID()} (ln:" . __LINE__ . ')');
760
-        $this->_increase_sold_for_datetimes($qty);
761
-        $this->set_sold($sold);
762
-        do_action(
763
-            'AHEE__EE_Ticket__increase_sold',
764
-            $this,
765
-            $qty,
766
-            $sold
767
-        );
768
-    }
769
-
770
-
771
-    /**
772
-     * Increases sold on related datetimes
773
-     *
774
-     * @param int $qty
775
-     * @return void
776
-     * @throws \EE_Error
777
-     */
778
-    protected function _increase_sold_for_datetimes($qty = 1)
779
-    {
780
-        $datetimes = $this->datetimes();
781
-        if (is_array($datetimes)) {
782
-            foreach ($datetimes as $datetime) {
783
-                if ($datetime instanceof EE_Datetime) {
784
-                    $datetime->increase_sold($qty);
785
-                    $datetime->save();
786
-                }
787
-            }
788
-        }
789
-    }
790
-
791
-
792
-    /**
793
-     * decrements (subtracts) sold by amount passed by $qty
794
-     *
795
-     * @param int $qty
796
-     * @return void
797
-     * @throws \EE_Error
798
-     */
799
-    public function decrease_sold($qty = 1)
800
-    {
801
-        $sold = $this->sold() - $qty;
802
-        $this->_decrease_sold_for_datetimes($qty);
803
-        $this->set_sold($sold);
804
-        do_action(
805
-            'AHEE__EE_Ticket__decrease_sold',
806
-            $this,
807
-            $qty,
808
-            $sold
809
-        );
810
-    }
811
-
812
-
813
-    /**
814
-     * Decreases sold on related datetimes
815
-     *
816
-     * @param int $qty
817
-     * @return void
818
-     * @throws \EE_Error
819
-     */
820
-    protected function _decrease_sold_for_datetimes($qty = 1)
821
-    {
822
-        $datetimes = $this->datetimes();
823
-        if (is_array($datetimes)) {
824
-            foreach ($datetimes as $datetime) {
825
-                if ($datetime instanceof EE_Datetime) {
826
-                    $datetime->decrease_sold($qty);
827
-                    $datetime->save();
828
-                }
829
-            }
830
-        }
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets qty of reserved tickets
836
-     *
837
-     * @return int
838
-     * @throws \EE_Error
839
-     */
840
-    public function reserved()
841
-    {
842
-        return $this->get_raw('TKT_reserved');
843
-    }
844
-
845
-
846
-    /**
847
-     * Sets reserved
848
-     *
849
-     * @param int $reserved
850
-     * @return void
851
-     * @throws \EE_Error
852
-     */
853
-    public function set_reserved($reserved)
854
-    {
855
-        // reserved can not go below zero
856
-        $reserved = max(0, (int) $reserved);
857
-        $this->set('TKT_reserved', $reserved);
858
-    }
859
-
860
-
861
-    /**
862
-     * increments reserved by amount passed by $qty
863
-     *
864
-     * @param int    $qty
865
-     * @param string $source
866
-     * @return void
867
-     * @throws EE_Error
868
-     * @throws InvalidArgumentException
869
-     * @throws ReflectionException
870
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
871
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
872
-     */
873
-    public function increase_reserved($qty = 1, $source = 'unknown')
874
-    {
875
-        $qty = absint($qty);
876
-        $reserved = $this->reserved() + $qty;
877
-        if ($reserved
878
-            && $this->add_extra_meta(
879
-                EE_Ticket::META_KEY_TICKET_RESERVATIONS,
880
-                "{$qty} from {$source}"
881
-            )
882
-        ) {
883
-            $this->_increase_reserved_for_datetimes($qty);
884
-            $this->set_reserved($reserved);
885
-            do_action(
886
-                'AHEE__EE_Ticket__increase_reserved',
887
-                $this,
888
-                $qty,
889
-                $reserved
890
-            );
891
-        }
892
-    }
893
-
894
-
895
-    /**
896
-     * Increases sold on related datetimes
897
-     *
898
-     * @param int $qty
899
-     * @return void
900
-     * @throws \EE_Error
901
-     */
902
-    protected function _increase_reserved_for_datetimes($qty = 1)
903
-    {
904
-        $datetimes = $this->datetimes();
905
-        if (is_array($datetimes)) {
906
-            foreach ($datetimes as $datetime) {
907
-                if ($datetime instanceof EE_Datetime) {
908
-                    $datetime->increase_reserved($qty);
909
-                    $datetime->save();
910
-                }
911
-            }
912
-        }
913
-    }
914
-
915
-
916
-    /**
917
-     * decrements (subtracts) reserved by amount passed by $qty
918
-     *
919
-     * @param int    $qty
920
-     * @param bool   $adjust_datetimes
921
-     * @param string $source
922
-     * @return void
923
-     * @throws EE_Error
924
-     * @throws InvalidArgumentException
925
-     * @throws ReflectionException
926
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
927
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
928
-     */
929
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
930
-    {
931
-        $reserved = $this->reserved() - absint($qty);
932
-        if ($this->add_extra_meta(
933
-            EE_Ticket::META_KEY_TICKET_RESERVATIONS,
934
-            "-{$qty} from {$source}"
935
-        )) {
936
-            if ($adjust_datetimes) {
937
-                $this->_decrease_reserved_for_datetimes($qty);
938
-            }
939
-            $this->set_reserved($reserved);
940
-            do_action(
941
-                'AHEE__EE_Ticket__decrease_reserved',
942
-                $this,
943
-                $qty,
944
-                $reserved
945
-            );
946
-        }
947
-    }
948
-
949
-
950
-    /**
951
-     * Increases sold on related datetimes
952
-     *
953
-     * @param int $qty
954
-     * @return void
955
-     * @throws \EE_Error
956
-     */
957
-    protected function _decrease_reserved_for_datetimes($qty = 1)
958
-    {
959
-        $datetimes = $this->datetimes();
960
-        if (is_array($datetimes)) {
961
-            foreach ($datetimes as $datetime) {
962
-                if ($datetime instanceof EE_Datetime) {
963
-                    $datetime->decrease_reserved($qty);
964
-                    $datetime->save();
965
-                }
966
-            }
967
-        }
968
-    }
969
-
970
-
971
-    /**
972
-     * Gets ticket quantity
973
-     *
974
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
975
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
976
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
977
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
978
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
979
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
980
-     * @return int
981
-     * @throws \EE_Error
982
-     */
983
-    public function qty($context = '')
984
-    {
985
-        switch ($context) {
986
-            case 'reg_limit':
987
-                return $this->real_quantity_on_ticket();
988
-            case 'saleable':
989
-                return $this->real_quantity_on_ticket('saleable');
990
-            default:
991
-                return $this->get_raw('TKT_qty');
992
-        }
993
-    }
994
-
995
-
996
-    /**
997
-     * Gets ticket quantity
998
-     *
999
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1000
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1001
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1002
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1003
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1004
-     * @param  int   $DTT_ID      the primary key for a particular datetime.
1005
-     *                            set to 0 for all related datetimes
1006
-     * @return int
1007
-     * @throws \EE_Error
1008
-     */
1009
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1010
-    {
1011
-        $raw = $this->get_raw('TKT_qty');
1012
-        // return immediately if it's zero
1013
-        if ($raw === 0) {
1014
-            return $raw;
1015
-        }
1016
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1017
-        // ensure qty doesn't exceed raw value for THIS ticket
1018
-        $qty = min(EE_INF, $raw);
1019
-        // echo "\n . qty: " . $qty . '<br />';
1020
-        // calculate this ticket's total sales and reservations
1021
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1022
-        // echo "\n . sold: " . $this->sold() . '<br />';
1023
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1024
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1025
-        // first we need to calculate the maximum number of tickets available for the datetime
1026
-        // do we want data for one datetime or all of them ?
1027
-        $query_params = $DTT_ID ? array(array('DTT_ID' => $DTT_ID)) : array();
1028
-        $datetimes = $this->datetimes($query_params);
1029
-        if (is_array($datetimes) && ! empty($datetimes)) {
1030
-            foreach ($datetimes as $datetime) {
1031
-                if ($datetime instanceof EE_Datetime) {
1032
-                    $datetime->refresh_from_db();
1033
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1034
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1035
-                    // initialize with no restrictions for each datetime
1036
-                    // but adjust datetime qty based on datetime reg limit
1037
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1038
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1039
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1040
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1041
-                    // and reservations for this datetime, that do NOT include sales and reservations
1042
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1043
-                    if ($context === 'saleable') {
1044
-                        $datetime_qty = max(
1045
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1046
-                            0
1047
-                        );
1048
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1049
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1050
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1051
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1052
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1053
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1054
-                    }
1055
-                    $qty = min($datetime_qty, $qty);
1056
-                    // echo "\n . . qty: " . $qty . '<br />';
1057
-                }
1058
-            }
1059
-        }
1060
-        // NOW that we know the  maximum number of tickets available for the datetime
1061
-        // we can finally factor in the details for this specific ticket
1062
-        if ($qty > 0 && $context === 'saleable') {
1063
-            // and subtract the sales for THIS ticket
1064
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1065
-            // echo "\n . qty: " . $qty . '<br />';
1066
-        }
1067
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1068
-        return $qty;
1069
-    }
1070
-
1071
-
1072
-    /**
1073
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1074
-     *
1075
-     * @param int $qty
1076
-     * @return void
1077
-     * @throws \EE_Error
1078
-     */
1079
-    public function set_qty($qty)
1080
-    {
1081
-        $datetimes = $this->datetimes();
1082
-        foreach ($datetimes as $datetime) {
1083
-            if ($datetime instanceof EE_Datetime) {
1084
-                $qty = min($qty, $datetime->reg_limit());
1085
-            }
1086
-        }
1087
-        $this->set('TKT_qty', $qty);
1088
-    }
1089
-
1090
-
1091
-    /**
1092
-     * Gets uses
1093
-     *
1094
-     * @return int
1095
-     * @throws \EE_Error
1096
-     */
1097
-    public function uses()
1098
-    {
1099
-        return $this->get('TKT_uses');
1100
-    }
1101
-
1102
-
1103
-    /**
1104
-     * Sets uses
1105
-     *
1106
-     * @param int $uses
1107
-     * @return void
1108
-     * @throws \EE_Error
1109
-     */
1110
-    public function set_uses($uses)
1111
-    {
1112
-        $this->set('TKT_uses', $uses);
1113
-    }
1114
-
1115
-
1116
-    /**
1117
-     * returns whether ticket is required or not.
1118
-     *
1119
-     * @return boolean
1120
-     * @throws \EE_Error
1121
-     */
1122
-    public function required()
1123
-    {
1124
-        return $this->get('TKT_required');
1125
-    }
1126
-
1127
-
1128
-    /**
1129
-     * sets the TKT_required property
1130
-     *
1131
-     * @param boolean $required
1132
-     * @return void
1133
-     * @throws \EE_Error
1134
-     */
1135
-    public function set_required($required)
1136
-    {
1137
-        $this->set('TKT_required', $required);
1138
-    }
1139
-
1140
-
1141
-    /**
1142
-     * Gets taxable
1143
-     *
1144
-     * @return boolean
1145
-     * @throws \EE_Error
1146
-     */
1147
-    public function taxable()
1148
-    {
1149
-        return $this->get('TKT_taxable');
1150
-    }
1151
-
1152
-
1153
-    /**
1154
-     * Sets taxable
1155
-     *
1156
-     * @param boolean $taxable
1157
-     * @return void
1158
-     * @throws \EE_Error
1159
-     */
1160
-    public function set_taxable($taxable)
1161
-    {
1162
-        $this->set('TKT_taxable', $taxable);
1163
-    }
1164
-
1165
-
1166
-    /**
1167
-     * Gets is_default
1168
-     *
1169
-     * @return boolean
1170
-     * @throws \EE_Error
1171
-     */
1172
-    public function is_default()
1173
-    {
1174
-        return $this->get('TKT_is_default');
1175
-    }
1176
-
1177
-
1178
-    /**
1179
-     * Sets is_default
1180
-     *
1181
-     * @param boolean $is_default
1182
-     * @return void
1183
-     * @throws \EE_Error
1184
-     */
1185
-    public function set_is_default($is_default)
1186
-    {
1187
-        $this->set('TKT_is_default', $is_default);
1188
-    }
1189
-
1190
-
1191
-    /**
1192
-     * Gets order
1193
-     *
1194
-     * @return int
1195
-     * @throws \EE_Error
1196
-     */
1197
-    public function order()
1198
-    {
1199
-        return $this->get('TKT_order');
1200
-    }
1201
-
1202
-
1203
-    /**
1204
-     * Sets order
1205
-     *
1206
-     * @param int $order
1207
-     * @return void
1208
-     * @throws \EE_Error
1209
-     */
1210
-    public function set_order($order)
1211
-    {
1212
-        $this->set('TKT_order', $order);
1213
-    }
1214
-
1215
-
1216
-    /**
1217
-     * Gets row
1218
-     *
1219
-     * @return int
1220
-     * @throws \EE_Error
1221
-     */
1222
-    public function row()
1223
-    {
1224
-        return $this->get('TKT_row');
1225
-    }
1226
-
1227
-
1228
-    /**
1229
-     * Sets row
1230
-     *
1231
-     * @param int $row
1232
-     * @return void
1233
-     * @throws \EE_Error
1234
-     */
1235
-    public function set_row($row)
1236
-    {
1237
-        $this->set('TKT_row', $row);
1238
-    }
1239
-
1240
-
1241
-    /**
1242
-     * Gets deleted
1243
-     *
1244
-     * @return boolean
1245
-     * @throws \EE_Error
1246
-     */
1247
-    public function deleted()
1248
-    {
1249
-        return $this->get('TKT_deleted');
1250
-    }
1251
-
1252
-
1253
-    /**
1254
-     * Sets deleted
1255
-     *
1256
-     * @param boolean $deleted
1257
-     * @return void
1258
-     * @throws \EE_Error
1259
-     */
1260
-    public function set_deleted($deleted)
1261
-    {
1262
-        $this->set('TKT_deleted', $deleted);
1263
-    }
1264
-
1265
-
1266
-    /**
1267
-     * Gets parent
1268
-     *
1269
-     * @return int
1270
-     * @throws \EE_Error
1271
-     */
1272
-    public function parent_ID()
1273
-    {
1274
-        return $this->get('TKT_parent');
1275
-    }
1276
-
1277
-
1278
-    /**
1279
-     * Sets parent
1280
-     *
1281
-     * @param int $parent
1282
-     * @return void
1283
-     * @throws \EE_Error
1284
-     */
1285
-    public function set_parent_ID($parent)
1286
-    {
1287
-        $this->set('TKT_parent', $parent);
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1293
-     *
1294
-     * @return string
1295
-     * @throws \EE_Error
1296
-     */
1297
-    public function name_and_info()
1298
-    {
1299
-        $times = array();
1300
-        foreach ($this->datetimes() as $datetime) {
1301
-            $times[] = $datetime->start_date_and_time();
1302
-        }
1303
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1304
-    }
1305
-
1306
-
1307
-    /**
1308
-     * Gets name
1309
-     *
1310
-     * @return string
1311
-     * @throws \EE_Error
1312
-     */
1313
-    public function name()
1314
-    {
1315
-        return $this->get('TKT_name');
1316
-    }
1317
-
1318
-
1319
-    /**
1320
-     * Gets price
1321
-     *
1322
-     * @return float
1323
-     * @throws \EE_Error
1324
-     */
1325
-    public function price()
1326
-    {
1327
-        return $this->get('TKT_price');
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Gets all the registrations for this ticket
1333
-     *
1334
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1335
-     * @return EE_Registration[]|EE_Base_Class[]
1336
-     * @throws \EE_Error
1337
-     */
1338
-    public function registrations($query_params = array())
1339
-    {
1340
-        return $this->get_many_related('Registration', $query_params);
1341
-    }
1342
-
1343
-
1344
-    /**
1345
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1346
-     * into account
1347
-     *
1348
-     * @return int
1349
-     * @throws \EE_Error
1350
-     */
1351
-    public function update_tickets_sold()
1352
-    {
1353
-        $count_regs_for_this_ticket = $this->count_registrations(
1354
-            array(
1355
-                array(
1356
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1357
-                    'REG_deleted' => 0,
1358
-                ),
1359
-            )
1360
-        );
1361
-        $sold = $this->sold();
1362
-        if ($count_regs_for_this_ticket > $sold) {
1363
-            $this->increase_sold($count_regs_for_this_ticket - $sold);
1364
-            $this->save();
1365
-        } elseif ($count_regs_for_this_ticket < $sold) {
1366
-            $this->decrease_sold($count_regs_for_this_ticket - $sold);
1367
-            $this->save();
1368
-        }
1369
-        return $count_regs_for_this_ticket;
1370
-    }
1371
-
1372
-
1373
-    /**
1374
-     * Counts the registrations for this ticket
1375
-     *
1376
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1377
-     * @return int
1378
-     */
1379
-    public function count_registrations($query_params = array())
1380
-    {
1381
-        return $this->count_related('Registration', $query_params);
1382
-    }
1383
-
1384
-
1385
-    /**
1386
-     * Implementation for EEI_Has_Icon interface method.
1387
-     *
1388
-     * @see EEI_Visual_Representation for comments
1389
-     * @return string
1390
-     */
1391
-    public function get_icon()
1392
-    {
1393
-        return '<span class="dashicons dashicons-tickets-alt"></span>';
1394
-    }
1395
-
1396
-
1397
-    /**
1398
-     * Implementation of the EEI_Event_Relation interface method
1399
-     *
1400
-     * @see EEI_Event_Relation for comments
1401
-     * @return EE_Event
1402
-     * @throws \EE_Error
1403
-     * @throws UnexpectedEntityException
1404
-     */
1405
-    public function get_related_event()
1406
-    {
1407
-        // get one datetime to use for getting the event
1408
-        $datetime = $this->first_datetime();
1409
-        if (! $datetime instanceof \EE_Datetime) {
1410
-            throw new UnexpectedEntityException(
1411
-                $datetime,
1412
-                'EE_Datetime',
1413
-                sprintf(
1414
-                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1415
-                    $this->name()
1416
-                )
1417
-            );
1418
-        }
1419
-        $event = $datetime->event();
1420
-        if (! $event instanceof \EE_Event) {
1421
-            throw new UnexpectedEntityException(
1422
-                $event,
1423
-                'EE_Event',
1424
-                sprintf(
1425
-                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1426
-                    $this->name()
1427
-                )
1428
-            );
1429
-        }
1430
-        return $event;
1431
-    }
1432
-
1433
-
1434
-    /**
1435
-     * Implementation of the EEI_Event_Relation interface method
1436
-     *
1437
-     * @see EEI_Event_Relation for comments
1438
-     * @return string
1439
-     * @throws UnexpectedEntityException
1440
-     * @throws \EE_Error
1441
-     */
1442
-    public function get_event_name()
1443
-    {
1444
-        $event = $this->get_related_event();
1445
-        return $event instanceof EE_Event ? $event->name() : '';
1446
-    }
1447
-
1448
-
1449
-    /**
1450
-     * Implementation of the EEI_Event_Relation interface method
1451
-     *
1452
-     * @see EEI_Event_Relation for comments
1453
-     * @return int
1454
-     * @throws UnexpectedEntityException
1455
-     * @throws \EE_Error
1456
-     */
1457
-    public function get_event_ID()
1458
-    {
1459
-        $event = $this->get_related_event();
1460
-        return $event instanceof EE_Event ? $event->ID() : 0;
1461
-    }
1462
-
1463
-
1464
-    /**
1465
-     * This simply returns whether a ticket can be permanently deleted or not.
1466
-     * The criteria for determining this is whether the ticket has any related registrations.
1467
-     * If there are none then it can be permanently deleted.
1468
-     *
1469
-     * @return bool
1470
-     */
1471
-    public function is_permanently_deleteable()
1472
-    {
1473
-        return $this->count_registrations() === 0;
1474
-    }
15
+	/**
16
+	 * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
17
+	 */
18
+	const sold_out = 'TKS';
19
+
20
+	/**
21
+	 *
22
+	 */
23
+	const expired = 'TKE';
24
+
25
+	/**
26
+	 *
27
+	 */
28
+	const archived = 'TKA';
29
+
30
+	/**
31
+	 *
32
+	 */
33
+	const pending = 'TKP';
34
+
35
+	/**
36
+	 *
37
+	 */
38
+	const onsale = 'TKO';
39
+
40
+	/**
41
+	 * extra meta key for tracking ticket reservations
42
+	 *
43
+	 * @type string
44
+	 */
45
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
46
+
47
+	/**
48
+	 * cached result from method of the same name
49
+	 *
50
+	 * @var float $_ticket_total_with_taxes
51
+	 */
52
+	private $_ticket_total_with_taxes;
53
+
54
+
55
+	/**
56
+	 * @param array  $props_n_values          incoming values
57
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
58
+	 *                                        used.)
59
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
60
+	 *                                        date_format and the second value is the time format
61
+	 * @return EE_Ticket
62
+	 * @throws \EE_Error
63
+	 */
64
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
65
+	{
66
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
67
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
68
+	}
69
+
70
+
71
+	/**
72
+	 * @param array  $props_n_values  incoming values from the database
73
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
74
+	 *                                the website will be used.
75
+	 * @return EE_Ticket
76
+	 * @throws \EE_Error
77
+	 */
78
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
79
+	{
80
+		return new self($props_n_values, true, $timezone);
81
+	}
82
+
83
+
84
+	/**
85
+	 * @return bool
86
+	 * @throws \EE_Error
87
+	 */
88
+	public function parent()
89
+	{
90
+		return $this->get('TKT_parent');
91
+	}
92
+
93
+
94
+	/**
95
+	 * return if a ticket has quantities available for purchase
96
+	 *
97
+	 * @param  int $DTT_ID the primary key for a particular datetime
98
+	 * @return boolean
99
+	 * @throws \EE_Error
100
+	 */
101
+	public function available($DTT_ID = 0)
102
+	{
103
+		// are we checking availability for a particular datetime ?
104
+		if ($DTT_ID) {
105
+			// get that datetime object
106
+			$datetime = $this->get_first_related('Datetime', array(array('DTT_ID' => $DTT_ID)));
107
+			// if  ticket sales for this datetime have exceeded the reg limit...
108
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
109
+				return false;
110
+			}
111
+		}
112
+		// datetime is still open for registration, but is this ticket sold out ?
113
+		return $this->qty() < 1 || $this->qty() > $this->sold() ? true : false;
114
+	}
115
+
116
+
117
+	/**
118
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
119
+	 *
120
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
121
+	 *                               relevant status const
122
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
123
+	 *               further processing
124
+	 * @return mixed status int if the display string isn't requested
125
+	 * @throws \EE_Error
126
+	 */
127
+	public function ticket_status($display = false, $remaining = null)
128
+	{
129
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
130
+		if (! $remaining) {
131
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
132
+		}
133
+		if ($this->get('TKT_deleted')) {
134
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
135
+		}
136
+		if ($this->is_expired()) {
137
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
138
+		}
139
+		if ($this->is_pending()) {
140
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
141
+		}
142
+		if ($this->is_on_sale()) {
143
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
144
+		}
145
+		return '';
146
+	}
147
+
148
+
149
+	/**
150
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
151
+	 * considering ALL the factors used for figuring that out.
152
+	 *
153
+	 * @access public
154
+	 * @param  int $DTT_ID if an int above 0 is included here then we get a specific dtt.
155
+	 * @return boolean         true = tickets remaining, false not.
156
+	 * @throws \EE_Error
157
+	 */
158
+	public function is_remaining($DTT_ID = 0)
159
+	{
160
+		$num_remaining = $this->remaining($DTT_ID);
161
+		if ($num_remaining === 0) {
162
+			return false;
163
+		}
164
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
165
+			return false;
166
+		}
167
+		return true;
168
+	}
169
+
170
+
171
+	/**
172
+	 * return the total number of tickets available for purchase
173
+	 *
174
+	 * @param  int $DTT_ID the primary key for a particular datetime.
175
+	 *                     set to 0 for all related datetimes
176
+	 * @return int
177
+	 * @throws \EE_Error
178
+	 */
179
+	public function remaining($DTT_ID = 0)
180
+	{
181
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
182
+	}
183
+
184
+
185
+	/**
186
+	 * Gets min
187
+	 *
188
+	 * @return int
189
+	 * @throws \EE_Error
190
+	 */
191
+	public function min()
192
+	{
193
+		return $this->get('TKT_min');
194
+	}
195
+
196
+
197
+	/**
198
+	 * return if a ticket is no longer available cause its available dates have expired.
199
+	 *
200
+	 * @return boolean
201
+	 * @throws \EE_Error
202
+	 */
203
+	public function is_expired()
204
+	{
205
+		return ($this->get_raw('TKT_end_date') < time());
206
+	}
207
+
208
+
209
+	/**
210
+	 * Return if a ticket is yet to go on sale or not
211
+	 *
212
+	 * @return boolean
213
+	 * @throws \EE_Error
214
+	 */
215
+	public function is_pending()
216
+	{
217
+		return ($this->get_raw('TKT_start_date') > time());
218
+	}
219
+
220
+
221
+	/**
222
+	 * Return if a ticket is on sale or not
223
+	 *
224
+	 * @return boolean
225
+	 * @throws \EE_Error
226
+	 */
227
+	public function is_on_sale()
228
+	{
229
+		return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
230
+	}
231
+
232
+
233
+	/**
234
+	 * This returns the chronologically last datetime that this ticket is associated with
235
+	 *
236
+	 * @param string $dt_frmt
237
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
238
+	 *                            the end date ie: Jan 01 "to" Dec 31
239
+	 * @return string
240
+	 * @throws \EE_Error
241
+	 */
242
+	public function date_range($dt_frmt = '', $conjunction = ' - ')
243
+	{
244
+		$first_date = $this->first_datetime() instanceof EE_Datetime ? $this->first_datetime()->start_date($dt_frmt)
245
+			: '';
246
+		$last_date = $this->last_datetime() instanceof EE_Datetime ? $this->last_datetime()->end_date($dt_frmt) : '';
247
+
248
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
249
+	}
250
+
251
+
252
+	/**
253
+	 * This returns the chronologically first datetime that this ticket is associated with
254
+	 *
255
+	 * @return EE_Datetime
256
+	 * @throws \EE_Error
257
+	 */
258
+	public function first_datetime()
259
+	{
260
+		$datetimes = $this->datetimes(array('limit' => 1));
261
+		return reset($datetimes);
262
+	}
263
+
264
+
265
+	/**
266
+	 * Gets all the datetimes this ticket can be used for attending.
267
+	 * Unless otherwise specified, orders datetimes by start date.
268
+	 *
269
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
270
+	 * @return EE_Datetime[]|EE_Base_Class[]
271
+	 * @throws \EE_Error
272
+	 */
273
+	public function datetimes($query_params = array())
274
+	{
275
+		if (! isset($query_params['order_by'])) {
276
+			$query_params['order_by']['DTT_order'] = 'ASC';
277
+		}
278
+		return $this->get_many_related('Datetime', $query_params);
279
+	}
280
+
281
+
282
+	/**
283
+	 * This returns the chronologically last datetime that this ticket is associated with
284
+	 *
285
+	 * @return EE_Datetime
286
+	 * @throws \EE_Error
287
+	 */
288
+	public function last_datetime()
289
+	{
290
+		$datetimes = $this->datetimes(array('limit' => 1, 'order_by' => array('DTT_EVT_start' => 'DESC')));
291
+		return end($datetimes);
292
+	}
293
+
294
+
295
+	/**
296
+	 * This returns the total tickets sold depending on the given parameters.
297
+	 *
298
+	 * @param  string $what   Can be one of two options: 'ticket', 'datetime'.
299
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
300
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
301
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
302
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
303
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
304
+	 * @param  int    $dtt_id [optional] include the dtt_id with $what = 'datetime'.
305
+	 * @return mixed (array|int)          how many tickets have sold
306
+	 * @throws \EE_Error
307
+	 */
308
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
309
+	{
310
+		$total = 0;
311
+		$tickets_sold = $this->_all_tickets_sold();
312
+		switch ($what) {
313
+			case 'ticket':
314
+				return $tickets_sold['ticket'];
315
+				break;
316
+			case 'datetime':
317
+				if (empty($tickets_sold['datetime'])) {
318
+					return $total;
319
+				}
320
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
321
+					EE_Error::add_error(
322
+						__(
323
+							'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
324
+							'event_espresso'
325
+						),
326
+						__FILE__,
327
+						__FUNCTION__,
328
+						__LINE__
329
+					);
330
+					return $total;
331
+				}
332
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
333
+				break;
334
+			default:
335
+				return $total;
336
+		}
337
+	}
338
+
339
+
340
+	/**
341
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
342
+	 *
343
+	 * @return EE_Ticket[]
344
+	 * @throws \EE_Error
345
+	 */
346
+	protected function _all_tickets_sold()
347
+	{
348
+		$datetimes = $this->get_many_related('Datetime');
349
+		$tickets_sold = array();
350
+		if (! empty($datetimes)) {
351
+			foreach ($datetimes as $datetime) {
352
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
353
+			}
354
+		}
355
+		// Tickets sold
356
+		$tickets_sold['ticket'] = $this->sold();
357
+		return $tickets_sold;
358
+	}
359
+
360
+
361
+	/**
362
+	 * This returns the base price object for the ticket.
363
+	 *
364
+	 * @param  bool $return_array whether to return as an array indexed by price id or just the object.
365
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
366
+	 * @throws \EE_Error
367
+	 */
368
+	public function base_price($return_array = false)
369
+	{
370
+		$_where = array('Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price);
371
+		return $return_array
372
+			? $this->get_many_related('Price', array($_where))
373
+			: $this->get_first_related('Price', array($_where));
374
+	}
375
+
376
+
377
+	/**
378
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
379
+	 *
380
+	 * @access public
381
+	 * @return EE_Price[]
382
+	 * @throws \EE_Error
383
+	 */
384
+	public function price_modifiers()
385
+	{
386
+		$query_params = array(
387
+			0 => array(
388
+				'Price_Type.PBT_ID' => array(
389
+					'NOT IN',
390
+					array(EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax),
391
+				),
392
+			),
393
+		);
394
+		return $this->prices($query_params);
395
+	}
396
+
397
+
398
+	/**
399
+	 * Gets all the prices that combine to form the final price of this ticket
400
+	 *
401
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
402
+	 * @return EE_Price[]|EE_Base_Class[]
403
+	 * @throws \EE_Error
404
+	 */
405
+	public function prices($query_params = array())
406
+	{
407
+		return $this->get_many_related('Price', $query_params);
408
+	}
409
+
410
+
411
+	/**
412
+	 * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
413
+	 *
414
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
415
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
416
+	 * @throws \EE_Error
417
+	 */
418
+	public function datetime_tickets($query_params = array())
419
+	{
420
+		return $this->get_many_related('Datetime_Ticket', $query_params);
421
+	}
422
+
423
+
424
+	/**
425
+	 * Gets all the datetimes from the db ordered by DTT_order
426
+	 *
427
+	 * @param boolean $show_expired
428
+	 * @param boolean $show_deleted
429
+	 * @return EE_Datetime[]
430
+	 * @throws \EE_Error
431
+	 */
432
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
433
+	{
434
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
435
+			$this->ID(),
436
+			$show_expired,
437
+			$show_deleted
438
+		);
439
+	}
440
+
441
+
442
+	/**
443
+	 * Gets ID
444
+	 *
445
+	 * @return string
446
+	 * @throws \EE_Error
447
+	 */
448
+	public function ID()
449
+	{
450
+		return $this->get('TKT_ID');
451
+	}
452
+
453
+
454
+	/**
455
+	 * get the author of the ticket.
456
+	 *
457
+	 * @since 4.5.0
458
+	 * @return int
459
+	 * @throws \EE_Error
460
+	 */
461
+	public function wp_user()
462
+	{
463
+		return $this->get('TKT_wp_user');
464
+	}
465
+
466
+
467
+	/**
468
+	 * Gets the template for the ticket
469
+	 *
470
+	 * @return EE_Ticket_Template|EE_Base_Class
471
+	 * @throws \EE_Error
472
+	 */
473
+	public function template()
474
+	{
475
+		return $this->get_first_related('Ticket_Template');
476
+	}
477
+
478
+
479
+	/**
480
+	 * Simply returns an array of EE_Price objects that are taxes.
481
+	 *
482
+	 * @return EE_Price[]
483
+	 * @throws \EE_Error
484
+	 */
485
+	public function get_ticket_taxes_for_admin()
486
+	{
487
+		return EE_Taxes::get_taxes_for_admin();
488
+	}
489
+
490
+
491
+	/**
492
+	 * @return float
493
+	 * @throws \EE_Error
494
+	 */
495
+	public function ticket_price()
496
+	{
497
+		return $this->get('TKT_price');
498
+	}
499
+
500
+
501
+	/**
502
+	 * @return mixed
503
+	 * @throws \EE_Error
504
+	 */
505
+	public function pretty_price()
506
+	{
507
+		return $this->get_pretty('TKT_price');
508
+	}
509
+
510
+
511
+	/**
512
+	 * @return bool
513
+	 * @throws \EE_Error
514
+	 */
515
+	public function is_free()
516
+	{
517
+		return $this->get_ticket_total_with_taxes() === (float) 0;
518
+	}
519
+
520
+
521
+	/**
522
+	 * get_ticket_total_with_taxes
523
+	 *
524
+	 * @param bool $no_cache
525
+	 * @return float
526
+	 * @throws \EE_Error
527
+	 */
528
+	public function get_ticket_total_with_taxes($no_cache = false)
529
+	{
530
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
531
+			$this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
532
+		}
533
+		return (float) $this->_ticket_total_with_taxes;
534
+	}
535
+
536
+
537
+	public function ensure_TKT_Price_correct()
538
+	{
539
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
540
+		$this->save();
541
+	}
542
+
543
+
544
+	/**
545
+	 * @return float
546
+	 * @throws \EE_Error
547
+	 */
548
+	public function get_ticket_subtotal()
549
+	{
550
+		return EE_Taxes::get_subtotal_for_admin($this);
551
+	}
552
+
553
+
554
+	/**
555
+	 * Returns the total taxes applied to this ticket
556
+	 *
557
+	 * @return float
558
+	 * @throws \EE_Error
559
+	 */
560
+	public function get_ticket_taxes_total_for_admin()
561
+	{
562
+		return EE_Taxes::get_total_taxes_for_admin($this);
563
+	}
564
+
565
+
566
+	/**
567
+	 * Sets name
568
+	 *
569
+	 * @param string $name
570
+	 * @throws \EE_Error
571
+	 */
572
+	public function set_name($name)
573
+	{
574
+		$this->set('TKT_name', $name);
575
+	}
576
+
577
+
578
+	/**
579
+	 * Gets description
580
+	 *
581
+	 * @return string
582
+	 * @throws \EE_Error
583
+	 */
584
+	public function description()
585
+	{
586
+		return $this->get('TKT_description');
587
+	}
588
+
589
+
590
+	/**
591
+	 * Sets description
592
+	 *
593
+	 * @param string $description
594
+	 * @throws \EE_Error
595
+	 */
596
+	public function set_description($description)
597
+	{
598
+		$this->set('TKT_description', $description);
599
+	}
600
+
601
+
602
+	/**
603
+	 * Gets start_date
604
+	 *
605
+	 * @param string $dt_frmt
606
+	 * @param string $tm_frmt
607
+	 * @return string
608
+	 * @throws \EE_Error
609
+	 */
610
+	public function start_date($dt_frmt = '', $tm_frmt = '')
611
+	{
612
+		return $this->_get_datetime('TKT_start_date', $dt_frmt, $tm_frmt);
613
+	}
614
+
615
+
616
+	/**
617
+	 * Sets start_date
618
+	 *
619
+	 * @param string $start_date
620
+	 * @return void
621
+	 * @throws \EE_Error
622
+	 */
623
+	public function set_start_date($start_date)
624
+	{
625
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
626
+	}
627
+
628
+
629
+	/**
630
+	 * Gets end_date
631
+	 *
632
+	 * @param string $dt_frmt
633
+	 * @param string $tm_frmt
634
+	 * @return string
635
+	 * @throws \EE_Error
636
+	 */
637
+	public function end_date($dt_frmt = '', $tm_frmt = '')
638
+	{
639
+		return $this->_get_datetime('TKT_end_date', $dt_frmt, $tm_frmt);
640
+	}
641
+
642
+
643
+	/**
644
+	 * Sets end_date
645
+	 *
646
+	 * @param string $end_date
647
+	 * @return void
648
+	 * @throws \EE_Error
649
+	 */
650
+	public function set_end_date($end_date)
651
+	{
652
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
653
+	}
654
+
655
+
656
+	/**
657
+	 * Sets sell until time
658
+	 *
659
+	 * @since 4.5.0
660
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
661
+	 * @throws \EE_Error
662
+	 */
663
+	public function set_end_time($time)
664
+	{
665
+		$this->_set_time_for($time, 'TKT_end_date');
666
+	}
667
+
668
+
669
+	/**
670
+	 * Sets min
671
+	 *
672
+	 * @param int $min
673
+	 * @return void
674
+	 * @throws \EE_Error
675
+	 */
676
+	public function set_min($min)
677
+	{
678
+		$this->set('TKT_min', $min);
679
+	}
680
+
681
+
682
+	/**
683
+	 * Gets max
684
+	 *
685
+	 * @return int
686
+	 * @throws \EE_Error
687
+	 */
688
+	public function max()
689
+	{
690
+		return $this->get('TKT_max');
691
+	}
692
+
693
+
694
+	/**
695
+	 * Sets max
696
+	 *
697
+	 * @param int $max
698
+	 * @return void
699
+	 * @throws \EE_Error
700
+	 */
701
+	public function set_max($max)
702
+	{
703
+		$this->set('TKT_max', $max);
704
+	}
705
+
706
+
707
+	/**
708
+	 * Sets price
709
+	 *
710
+	 * @param float $price
711
+	 * @return void
712
+	 * @throws \EE_Error
713
+	 */
714
+	public function set_price($price)
715
+	{
716
+		$this->set('TKT_price', $price);
717
+	}
718
+
719
+
720
+	/**
721
+	 * Gets sold
722
+	 *
723
+	 * @return int
724
+	 * @throws \EE_Error
725
+	 */
726
+	public function sold()
727
+	{
728
+		return $this->get_raw('TKT_sold');
729
+	}
730
+
731
+
732
+	/**
733
+	 * Sets sold
734
+	 *
735
+	 * @param int $sold
736
+	 * @return void
737
+	 * @throws \EE_Error
738
+	 */
739
+	public function set_sold($sold)
740
+	{
741
+		// sold can not go below zero
742
+		$sold = max(0, $sold);
743
+		$this->set('TKT_sold', $sold);
744
+	}
745
+
746
+
747
+	/**
748
+	 * increments sold by amount passed by $qty
749
+	 *
750
+	 * @param int $qty
751
+	 * @return void
752
+	 * @throws \EE_Error
753
+	 */
754
+	public function increase_sold($qty = 1)
755
+	{
756
+		$sold = $this->sold() + $qty;
757
+		// remove ticket reservation, but don't adjust datetime reservations,  because that will happen
758
+		// via \EE_Datetime::increase_sold() when \EE_Ticket::_increase_sold_for_datetimes() is called
759
+		$this->decrease_reserved($qty, false, "TKT: {$this->ID()} (ln:" . __LINE__ . ')');
760
+		$this->_increase_sold_for_datetimes($qty);
761
+		$this->set_sold($sold);
762
+		do_action(
763
+			'AHEE__EE_Ticket__increase_sold',
764
+			$this,
765
+			$qty,
766
+			$sold
767
+		);
768
+	}
769
+
770
+
771
+	/**
772
+	 * Increases sold on related datetimes
773
+	 *
774
+	 * @param int $qty
775
+	 * @return void
776
+	 * @throws \EE_Error
777
+	 */
778
+	protected function _increase_sold_for_datetimes($qty = 1)
779
+	{
780
+		$datetimes = $this->datetimes();
781
+		if (is_array($datetimes)) {
782
+			foreach ($datetimes as $datetime) {
783
+				if ($datetime instanceof EE_Datetime) {
784
+					$datetime->increase_sold($qty);
785
+					$datetime->save();
786
+				}
787
+			}
788
+		}
789
+	}
790
+
791
+
792
+	/**
793
+	 * decrements (subtracts) sold by amount passed by $qty
794
+	 *
795
+	 * @param int $qty
796
+	 * @return void
797
+	 * @throws \EE_Error
798
+	 */
799
+	public function decrease_sold($qty = 1)
800
+	{
801
+		$sold = $this->sold() - $qty;
802
+		$this->_decrease_sold_for_datetimes($qty);
803
+		$this->set_sold($sold);
804
+		do_action(
805
+			'AHEE__EE_Ticket__decrease_sold',
806
+			$this,
807
+			$qty,
808
+			$sold
809
+		);
810
+	}
811
+
812
+
813
+	/**
814
+	 * Decreases sold on related datetimes
815
+	 *
816
+	 * @param int $qty
817
+	 * @return void
818
+	 * @throws \EE_Error
819
+	 */
820
+	protected function _decrease_sold_for_datetimes($qty = 1)
821
+	{
822
+		$datetimes = $this->datetimes();
823
+		if (is_array($datetimes)) {
824
+			foreach ($datetimes as $datetime) {
825
+				if ($datetime instanceof EE_Datetime) {
826
+					$datetime->decrease_sold($qty);
827
+					$datetime->save();
828
+				}
829
+			}
830
+		}
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets qty of reserved tickets
836
+	 *
837
+	 * @return int
838
+	 * @throws \EE_Error
839
+	 */
840
+	public function reserved()
841
+	{
842
+		return $this->get_raw('TKT_reserved');
843
+	}
844
+
845
+
846
+	/**
847
+	 * Sets reserved
848
+	 *
849
+	 * @param int $reserved
850
+	 * @return void
851
+	 * @throws \EE_Error
852
+	 */
853
+	public function set_reserved($reserved)
854
+	{
855
+		// reserved can not go below zero
856
+		$reserved = max(0, (int) $reserved);
857
+		$this->set('TKT_reserved', $reserved);
858
+	}
859
+
860
+
861
+	/**
862
+	 * increments reserved by amount passed by $qty
863
+	 *
864
+	 * @param int    $qty
865
+	 * @param string $source
866
+	 * @return void
867
+	 * @throws EE_Error
868
+	 * @throws InvalidArgumentException
869
+	 * @throws ReflectionException
870
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
871
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
872
+	 */
873
+	public function increase_reserved($qty = 1, $source = 'unknown')
874
+	{
875
+		$qty = absint($qty);
876
+		$reserved = $this->reserved() + $qty;
877
+		if ($reserved
878
+			&& $this->add_extra_meta(
879
+				EE_Ticket::META_KEY_TICKET_RESERVATIONS,
880
+				"{$qty} from {$source}"
881
+			)
882
+		) {
883
+			$this->_increase_reserved_for_datetimes($qty);
884
+			$this->set_reserved($reserved);
885
+			do_action(
886
+				'AHEE__EE_Ticket__increase_reserved',
887
+				$this,
888
+				$qty,
889
+				$reserved
890
+			);
891
+		}
892
+	}
893
+
894
+
895
+	/**
896
+	 * Increases sold on related datetimes
897
+	 *
898
+	 * @param int $qty
899
+	 * @return void
900
+	 * @throws \EE_Error
901
+	 */
902
+	protected function _increase_reserved_for_datetimes($qty = 1)
903
+	{
904
+		$datetimes = $this->datetimes();
905
+		if (is_array($datetimes)) {
906
+			foreach ($datetimes as $datetime) {
907
+				if ($datetime instanceof EE_Datetime) {
908
+					$datetime->increase_reserved($qty);
909
+					$datetime->save();
910
+				}
911
+			}
912
+		}
913
+	}
914
+
915
+
916
+	/**
917
+	 * decrements (subtracts) reserved by amount passed by $qty
918
+	 *
919
+	 * @param int    $qty
920
+	 * @param bool   $adjust_datetimes
921
+	 * @param string $source
922
+	 * @return void
923
+	 * @throws EE_Error
924
+	 * @throws InvalidArgumentException
925
+	 * @throws ReflectionException
926
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
927
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
928
+	 */
929
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
930
+	{
931
+		$reserved = $this->reserved() - absint($qty);
932
+		if ($this->add_extra_meta(
933
+			EE_Ticket::META_KEY_TICKET_RESERVATIONS,
934
+			"-{$qty} from {$source}"
935
+		)) {
936
+			if ($adjust_datetimes) {
937
+				$this->_decrease_reserved_for_datetimes($qty);
938
+			}
939
+			$this->set_reserved($reserved);
940
+			do_action(
941
+				'AHEE__EE_Ticket__decrease_reserved',
942
+				$this,
943
+				$qty,
944
+				$reserved
945
+			);
946
+		}
947
+	}
948
+
949
+
950
+	/**
951
+	 * Increases sold on related datetimes
952
+	 *
953
+	 * @param int $qty
954
+	 * @return void
955
+	 * @throws \EE_Error
956
+	 */
957
+	protected function _decrease_reserved_for_datetimes($qty = 1)
958
+	{
959
+		$datetimes = $this->datetimes();
960
+		if (is_array($datetimes)) {
961
+			foreach ($datetimes as $datetime) {
962
+				if ($datetime instanceof EE_Datetime) {
963
+					$datetime->decrease_reserved($qty);
964
+					$datetime->save();
965
+				}
966
+			}
967
+		}
968
+	}
969
+
970
+
971
+	/**
972
+	 * Gets ticket quantity
973
+	 *
974
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
975
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
976
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
977
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
978
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
979
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
980
+	 * @return int
981
+	 * @throws \EE_Error
982
+	 */
983
+	public function qty($context = '')
984
+	{
985
+		switch ($context) {
986
+			case 'reg_limit':
987
+				return $this->real_quantity_on_ticket();
988
+			case 'saleable':
989
+				return $this->real_quantity_on_ticket('saleable');
990
+			default:
991
+				return $this->get_raw('TKT_qty');
992
+		}
993
+	}
994
+
995
+
996
+	/**
997
+	 * Gets ticket quantity
998
+	 *
999
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1000
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1001
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1002
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1003
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1004
+	 * @param  int   $DTT_ID      the primary key for a particular datetime.
1005
+	 *                            set to 0 for all related datetimes
1006
+	 * @return int
1007
+	 * @throws \EE_Error
1008
+	 */
1009
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1010
+	{
1011
+		$raw = $this->get_raw('TKT_qty');
1012
+		// return immediately if it's zero
1013
+		if ($raw === 0) {
1014
+			return $raw;
1015
+		}
1016
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1017
+		// ensure qty doesn't exceed raw value for THIS ticket
1018
+		$qty = min(EE_INF, $raw);
1019
+		// echo "\n . qty: " . $qty . '<br />';
1020
+		// calculate this ticket's total sales and reservations
1021
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1022
+		// echo "\n . sold: " . $this->sold() . '<br />';
1023
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1024
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1025
+		// first we need to calculate the maximum number of tickets available for the datetime
1026
+		// do we want data for one datetime or all of them ?
1027
+		$query_params = $DTT_ID ? array(array('DTT_ID' => $DTT_ID)) : array();
1028
+		$datetimes = $this->datetimes($query_params);
1029
+		if (is_array($datetimes) && ! empty($datetimes)) {
1030
+			foreach ($datetimes as $datetime) {
1031
+				if ($datetime instanceof EE_Datetime) {
1032
+					$datetime->refresh_from_db();
1033
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1034
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1035
+					// initialize with no restrictions for each datetime
1036
+					// but adjust datetime qty based on datetime reg limit
1037
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1038
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1039
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1040
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1041
+					// and reservations for this datetime, that do NOT include sales and reservations
1042
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1043
+					if ($context === 'saleable') {
1044
+						$datetime_qty = max(
1045
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1046
+							0
1047
+						);
1048
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1049
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1050
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1051
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1052
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1053
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1054
+					}
1055
+					$qty = min($datetime_qty, $qty);
1056
+					// echo "\n . . qty: " . $qty . '<br />';
1057
+				}
1058
+			}
1059
+		}
1060
+		// NOW that we know the  maximum number of tickets available for the datetime
1061
+		// we can finally factor in the details for this specific ticket
1062
+		if ($qty > 0 && $context === 'saleable') {
1063
+			// and subtract the sales for THIS ticket
1064
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1065
+			// echo "\n . qty: " . $qty . '<br />';
1066
+		}
1067
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1068
+		return $qty;
1069
+	}
1070
+
1071
+
1072
+	/**
1073
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1074
+	 *
1075
+	 * @param int $qty
1076
+	 * @return void
1077
+	 * @throws \EE_Error
1078
+	 */
1079
+	public function set_qty($qty)
1080
+	{
1081
+		$datetimes = $this->datetimes();
1082
+		foreach ($datetimes as $datetime) {
1083
+			if ($datetime instanceof EE_Datetime) {
1084
+				$qty = min($qty, $datetime->reg_limit());
1085
+			}
1086
+		}
1087
+		$this->set('TKT_qty', $qty);
1088
+	}
1089
+
1090
+
1091
+	/**
1092
+	 * Gets uses
1093
+	 *
1094
+	 * @return int
1095
+	 * @throws \EE_Error
1096
+	 */
1097
+	public function uses()
1098
+	{
1099
+		return $this->get('TKT_uses');
1100
+	}
1101
+
1102
+
1103
+	/**
1104
+	 * Sets uses
1105
+	 *
1106
+	 * @param int $uses
1107
+	 * @return void
1108
+	 * @throws \EE_Error
1109
+	 */
1110
+	public function set_uses($uses)
1111
+	{
1112
+		$this->set('TKT_uses', $uses);
1113
+	}
1114
+
1115
+
1116
+	/**
1117
+	 * returns whether ticket is required or not.
1118
+	 *
1119
+	 * @return boolean
1120
+	 * @throws \EE_Error
1121
+	 */
1122
+	public function required()
1123
+	{
1124
+		return $this->get('TKT_required');
1125
+	}
1126
+
1127
+
1128
+	/**
1129
+	 * sets the TKT_required property
1130
+	 *
1131
+	 * @param boolean $required
1132
+	 * @return void
1133
+	 * @throws \EE_Error
1134
+	 */
1135
+	public function set_required($required)
1136
+	{
1137
+		$this->set('TKT_required', $required);
1138
+	}
1139
+
1140
+
1141
+	/**
1142
+	 * Gets taxable
1143
+	 *
1144
+	 * @return boolean
1145
+	 * @throws \EE_Error
1146
+	 */
1147
+	public function taxable()
1148
+	{
1149
+		return $this->get('TKT_taxable');
1150
+	}
1151
+
1152
+
1153
+	/**
1154
+	 * Sets taxable
1155
+	 *
1156
+	 * @param boolean $taxable
1157
+	 * @return void
1158
+	 * @throws \EE_Error
1159
+	 */
1160
+	public function set_taxable($taxable)
1161
+	{
1162
+		$this->set('TKT_taxable', $taxable);
1163
+	}
1164
+
1165
+
1166
+	/**
1167
+	 * Gets is_default
1168
+	 *
1169
+	 * @return boolean
1170
+	 * @throws \EE_Error
1171
+	 */
1172
+	public function is_default()
1173
+	{
1174
+		return $this->get('TKT_is_default');
1175
+	}
1176
+
1177
+
1178
+	/**
1179
+	 * Sets is_default
1180
+	 *
1181
+	 * @param boolean $is_default
1182
+	 * @return void
1183
+	 * @throws \EE_Error
1184
+	 */
1185
+	public function set_is_default($is_default)
1186
+	{
1187
+		$this->set('TKT_is_default', $is_default);
1188
+	}
1189
+
1190
+
1191
+	/**
1192
+	 * Gets order
1193
+	 *
1194
+	 * @return int
1195
+	 * @throws \EE_Error
1196
+	 */
1197
+	public function order()
1198
+	{
1199
+		return $this->get('TKT_order');
1200
+	}
1201
+
1202
+
1203
+	/**
1204
+	 * Sets order
1205
+	 *
1206
+	 * @param int $order
1207
+	 * @return void
1208
+	 * @throws \EE_Error
1209
+	 */
1210
+	public function set_order($order)
1211
+	{
1212
+		$this->set('TKT_order', $order);
1213
+	}
1214
+
1215
+
1216
+	/**
1217
+	 * Gets row
1218
+	 *
1219
+	 * @return int
1220
+	 * @throws \EE_Error
1221
+	 */
1222
+	public function row()
1223
+	{
1224
+		return $this->get('TKT_row');
1225
+	}
1226
+
1227
+
1228
+	/**
1229
+	 * Sets row
1230
+	 *
1231
+	 * @param int $row
1232
+	 * @return void
1233
+	 * @throws \EE_Error
1234
+	 */
1235
+	public function set_row($row)
1236
+	{
1237
+		$this->set('TKT_row', $row);
1238
+	}
1239
+
1240
+
1241
+	/**
1242
+	 * Gets deleted
1243
+	 *
1244
+	 * @return boolean
1245
+	 * @throws \EE_Error
1246
+	 */
1247
+	public function deleted()
1248
+	{
1249
+		return $this->get('TKT_deleted');
1250
+	}
1251
+
1252
+
1253
+	/**
1254
+	 * Sets deleted
1255
+	 *
1256
+	 * @param boolean $deleted
1257
+	 * @return void
1258
+	 * @throws \EE_Error
1259
+	 */
1260
+	public function set_deleted($deleted)
1261
+	{
1262
+		$this->set('TKT_deleted', $deleted);
1263
+	}
1264
+
1265
+
1266
+	/**
1267
+	 * Gets parent
1268
+	 *
1269
+	 * @return int
1270
+	 * @throws \EE_Error
1271
+	 */
1272
+	public function parent_ID()
1273
+	{
1274
+		return $this->get('TKT_parent');
1275
+	}
1276
+
1277
+
1278
+	/**
1279
+	 * Sets parent
1280
+	 *
1281
+	 * @param int $parent
1282
+	 * @return void
1283
+	 * @throws \EE_Error
1284
+	 */
1285
+	public function set_parent_ID($parent)
1286
+	{
1287
+		$this->set('TKT_parent', $parent);
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1293
+	 *
1294
+	 * @return string
1295
+	 * @throws \EE_Error
1296
+	 */
1297
+	public function name_and_info()
1298
+	{
1299
+		$times = array();
1300
+		foreach ($this->datetimes() as $datetime) {
1301
+			$times[] = $datetime->start_date_and_time();
1302
+		}
1303
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1304
+	}
1305
+
1306
+
1307
+	/**
1308
+	 * Gets name
1309
+	 *
1310
+	 * @return string
1311
+	 * @throws \EE_Error
1312
+	 */
1313
+	public function name()
1314
+	{
1315
+		return $this->get('TKT_name');
1316
+	}
1317
+
1318
+
1319
+	/**
1320
+	 * Gets price
1321
+	 *
1322
+	 * @return float
1323
+	 * @throws \EE_Error
1324
+	 */
1325
+	public function price()
1326
+	{
1327
+		return $this->get('TKT_price');
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Gets all the registrations for this ticket
1333
+	 *
1334
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1335
+	 * @return EE_Registration[]|EE_Base_Class[]
1336
+	 * @throws \EE_Error
1337
+	 */
1338
+	public function registrations($query_params = array())
1339
+	{
1340
+		return $this->get_many_related('Registration', $query_params);
1341
+	}
1342
+
1343
+
1344
+	/**
1345
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1346
+	 * into account
1347
+	 *
1348
+	 * @return int
1349
+	 * @throws \EE_Error
1350
+	 */
1351
+	public function update_tickets_sold()
1352
+	{
1353
+		$count_regs_for_this_ticket = $this->count_registrations(
1354
+			array(
1355
+				array(
1356
+					'STS_ID'      => EEM_Registration::status_id_approved,
1357
+					'REG_deleted' => 0,
1358
+				),
1359
+			)
1360
+		);
1361
+		$sold = $this->sold();
1362
+		if ($count_regs_for_this_ticket > $sold) {
1363
+			$this->increase_sold($count_regs_for_this_ticket - $sold);
1364
+			$this->save();
1365
+		} elseif ($count_regs_for_this_ticket < $sold) {
1366
+			$this->decrease_sold($count_regs_for_this_ticket - $sold);
1367
+			$this->save();
1368
+		}
1369
+		return $count_regs_for_this_ticket;
1370
+	}
1371
+
1372
+
1373
+	/**
1374
+	 * Counts the registrations for this ticket
1375
+	 *
1376
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1377
+	 * @return int
1378
+	 */
1379
+	public function count_registrations($query_params = array())
1380
+	{
1381
+		return $this->count_related('Registration', $query_params);
1382
+	}
1383
+
1384
+
1385
+	/**
1386
+	 * Implementation for EEI_Has_Icon interface method.
1387
+	 *
1388
+	 * @see EEI_Visual_Representation for comments
1389
+	 * @return string
1390
+	 */
1391
+	public function get_icon()
1392
+	{
1393
+		return '<span class="dashicons dashicons-tickets-alt"></span>';
1394
+	}
1395
+
1396
+
1397
+	/**
1398
+	 * Implementation of the EEI_Event_Relation interface method
1399
+	 *
1400
+	 * @see EEI_Event_Relation for comments
1401
+	 * @return EE_Event
1402
+	 * @throws \EE_Error
1403
+	 * @throws UnexpectedEntityException
1404
+	 */
1405
+	public function get_related_event()
1406
+	{
1407
+		// get one datetime to use for getting the event
1408
+		$datetime = $this->first_datetime();
1409
+		if (! $datetime instanceof \EE_Datetime) {
1410
+			throw new UnexpectedEntityException(
1411
+				$datetime,
1412
+				'EE_Datetime',
1413
+				sprintf(
1414
+					__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1415
+					$this->name()
1416
+				)
1417
+			);
1418
+		}
1419
+		$event = $datetime->event();
1420
+		if (! $event instanceof \EE_Event) {
1421
+			throw new UnexpectedEntityException(
1422
+				$event,
1423
+				'EE_Event',
1424
+				sprintf(
1425
+					__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1426
+					$this->name()
1427
+				)
1428
+			);
1429
+		}
1430
+		return $event;
1431
+	}
1432
+
1433
+
1434
+	/**
1435
+	 * Implementation of the EEI_Event_Relation interface method
1436
+	 *
1437
+	 * @see EEI_Event_Relation for comments
1438
+	 * @return string
1439
+	 * @throws UnexpectedEntityException
1440
+	 * @throws \EE_Error
1441
+	 */
1442
+	public function get_event_name()
1443
+	{
1444
+		$event = $this->get_related_event();
1445
+		return $event instanceof EE_Event ? $event->name() : '';
1446
+	}
1447
+
1448
+
1449
+	/**
1450
+	 * Implementation of the EEI_Event_Relation interface method
1451
+	 *
1452
+	 * @see EEI_Event_Relation for comments
1453
+	 * @return int
1454
+	 * @throws UnexpectedEntityException
1455
+	 * @throws \EE_Error
1456
+	 */
1457
+	public function get_event_ID()
1458
+	{
1459
+		$event = $this->get_related_event();
1460
+		return $event instanceof EE_Event ? $event->ID() : 0;
1461
+	}
1462
+
1463
+
1464
+	/**
1465
+	 * This simply returns whether a ticket can be permanently deleted or not.
1466
+	 * The criteria for determining this is whether the ticket has any related registrations.
1467
+	 * If there are none then it can be permanently deleted.
1468
+	 *
1469
+	 * @return bool
1470
+	 */
1471
+	public function is_permanently_deleteable()
1472
+	{
1473
+		return $this->count_registrations() === 0;
1474
+	}
1475 1475
 }
Please login to merge, or discard this patch.