Completed
Branch models-cleanup/main (de94a1)
by
unknown
14:03 queued 11:37
created
core/db_classes/EE_Export.class.php 1 patch
Indentation   +777 added lines, -777 removed lines patch added patch discarded remove patch
@@ -16,781 +16,781 @@
 block discarded – undo
16 16
 class EE_Export
17 17
 {
18 18
 
19
-    const option_prefix = 'ee_report_job_';
20
-
21
-
22
-    // instance of the EE_Export object
23
-    private static $_instance;
24
-
25
-    // instance of the EE_CSV object
26
-    /**
27
-     *
28
-     * @var EE_CSV
29
-     */
30
-    public $EE_CSV = null;
31
-
32
-
33
-    private $_req_data = array();
34
-
35
-
36
-    /**
37
-     *        private constructor to prevent direct creation
38
-     *
39
-     * @Constructor
40
-     * @access private
41
-     * @param array $request_data
42
-     */
43
-    private function __construct($request_data = array())
44
-    {
45
-        $this->_req_data = $request_data;
46
-        $this->today = date("Y-m-d", time());
47
-        require_once(EE_CLASSES . 'EE_CSV.class.php');
48
-        $this->EE_CSV = EE_CSV::instance();
49
-    }
50
-
51
-
52
-    /**
53
-     *        @ singleton method used to instantiate class object
54
-     *        @ access public
55
-     *
56
-     * @param array $request_data
57
-     * @return \EE_Export
58
-     */
59
-    public static function instance($request_data = array())
60
-    {
61
-        // check if class object is instantiated
62
-        if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_Export)) {
63
-            self::$_instance = new self($request_data);
64
-        }
65
-        return self::$_instance;
66
-    }
67
-
68
-
69
-    /**
70
-     * @Export Event Espresso data - routes export requests
71
-     * @access public
72
-     * @return void | bool
73
-     */
74
-    public function export()
75
-    {
76
-        // in case of bulk exports, the "actual" action will be in action2, but first check regular action for "export" keyword
77
-        if (isset($this->_req_data['action']) && strpos($this->_req_data['action'], 'export') === false) {
78
-            // check if action2 has export action
79
-            if (isset($this->_req_data['action2']) && strpos($this->_req_data['action2'], 'export') !== false) {
80
-                // whoop! there it is!
81
-                $this->_req_data['action'] = $this->_req_data['action2'];
82
-            }
83
-        }
84
-
85
-        $this->_req_data['export'] = isset($this->_req_data['export']) ? $this->_req_data['export'] : '';
86
-
87
-        switch ($this->_req_data['export']) {
88
-            case 'report':
89
-                switch ($this->_req_data['action']) {
90
-                    case "event":
91
-                    case "export_events":
92
-                    case 'all_event_data':
93
-                        $this->export_all_event_data();
94
-                        break;
95
-
96
-                    case 'registrations_report_for_event':
97
-                        $this->report_registrations_for_event($this->_req_data['EVT_ID']);
98
-                        break;
99
-
100
-                    case 'attendees':
101
-                        $this->export_attendees();
102
-                        break;
103
-
104
-                    case 'categories':
105
-                        $this->export_categories();
106
-                        break;
107
-
108
-                    default:
109
-                        EE_Error::add_error(
110
-                            __('An error occurred! The requested export report could not be found.', 'event_espresso'),
111
-                            __FILE__,
112
-                            __FUNCTION__,
113
-                            __LINE__
114
-                        );
115
-                        return false;
116
-                        break;
117
-                }
118
-                break; // end of switch export : report
119
-            default:
120
-                break;
121
-        } // end of switch export
122
-
123
-        exit;
124
-    }
125
-
126
-    /**
127
-     * Downloads a CSV file with all the columns, but no data. This should be used for importing
128
-     *
129
-     * @return null kills execution
130
-     */
131
-    public function export_sample()
132
-    {
133
-        $event = EEM_Event::instance()->get_one();
134
-        $this->_req_data['EVT_ID'] = $event->ID();
135
-        $this->export_all_event_data();
136
-    }
137
-
138
-
139
-    /**
140
-     * @Export data for ALL events
141
-     * @access public
142
-     * @return void
143
-     */
144
-    public function export_all_event_data()
145
-    {
146
-        // are any Event IDs set?
147
-        $event_query_params = array();
148
-        $related_models_query_params = array();
149
-        $related_through_reg_query_params = array();
150
-        $datetime_ticket_query_params = array();
151
-        $price_query_params = array();
152
-        $price_type_query_params = array();
153
-        $term_query_params = array();
154
-        $state_country_query_params = array();
155
-        $question_group_query_params = array();
156
-        $question_query_params = array();
157
-        if (isset($this->_req_data['EVT_ID'])) {
158
-            // do we have an array of IDs ?
159
-
160
-            if (is_array($this->_req_data['EVT_ID'])) {
161
-                $EVT_IDs = array_map('sanitize_text_field', $this->_req_data['EVT_ID']);
162
-                $value_to_equal = array('IN', $EVT_IDs);
163
-                $filename = 'events';
164
-            } else {
165
-                // generate regular where = clause
166
-                $EVT_ID = absint($this->_req_data['EVT_ID']);
167
-                $value_to_equal = $EVT_ID;
168
-                $event = EE_Registry::instance()->load_model('Event')->get_one_by_ID($EVT_ID);
169
-
170
-                $filename = 'event-' . ($event instanceof EE_Event ? $event->slug() : __('unknown', 'event_espresso'));
171
-            }
172
-            $event_query_params[0]['EVT_ID'] = $value_to_equal;
173
-            $related_models_query_params[0]['Event.EVT_ID'] = $value_to_equal;
174
-            $related_through_reg_query_params[0]['Registration.EVT_ID'] = $value_to_equal;
175
-            $datetime_ticket_query_params[0]['Datetime.EVT_ID'] = $value_to_equal;
176
-            $price_query_params[0]['Ticket.Datetime.EVT_ID'] = $value_to_equal;
177
-            $price_type_query_params[0]['Price.Ticket.Datetime.EVT_ID'] = $value_to_equal;
178
-            $term_query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $value_to_equal;
179
-            $state_country_query_params[0]['Venue.Event.EVT_ID'] = $value_to_equal;
180
-            $question_group_query_params[0]['Event.EVT_ID'] = $value_to_equal;
181
-            $question_query_params[0]['Question_Group.Event.EVT_ID'] = $value_to_equal;
182
-        } else {
183
-            $filename = 'all-events';
184
-        }
185
-
186
-
187
-        // array in the format:  table name =>  query where clause
188
-        $models_to_export = array(
189
-            'Event'                   => $event_query_params,
190
-            'Datetime'                => $related_models_query_params,
191
-            'Ticket_Template'         => $price_query_params,
192
-            'Ticket'                  => $datetime_ticket_query_params,
193
-            'Datetime_Ticket'         => $datetime_ticket_query_params,
194
-            'Price_Type'              => $price_type_query_params,
195
-            'Price'                   => $price_query_params,
196
-            'Ticket_Price'            => $price_query_params,
197
-            'Term'                    => $term_query_params,
198
-            'Term_Taxonomy'           => $related_models_query_params,
199
-            'Term_Relationship'       => $related_models_query_params, // model has NO primary key...
200
-            'Country'                 => $state_country_query_params,
201
-            'State'                   => $state_country_query_params,
202
-            'Venue'                   => $related_models_query_params,
203
-            'Event_Venue'             => $related_models_query_params,
204
-            'Question_Group'          => $question_group_query_params,
205
-            'Event_Question_Group'    => $question_group_query_params,
206
-            'Question'                => $question_query_params,
207
-            'Question_Group_Question' => $question_query_params,
208
-            // 'Transaction'=>$related_through_reg_query_params,
209
-            // 'Registration'=>$related_models_query_params,
210
-            // 'Attendee'=>$related_through_reg_query_params,
211
-            // 'Line_Item'=>
212
-
213
-        );
214
-
215
-        $model_data = $this->_get_export_data_for_models($models_to_export);
216
-
217
-        $filename = $this->generate_filename($filename);
218
-
219
-        if (! $this->EE_CSV->export_multiple_model_data_to_csv($filename, $model_data)) {
220
-            EE_Error::add_error(
221
-                __(
222
-                    "'An error occurred and the Event details could not be exported from the database.'",
223
-                    "event_espresso"
224
-                ),
225
-                __FILE__,
226
-                __FUNCTION__,
227
-                __LINE__
228
-            );
229
-        }
230
-    }
231
-
232
-    public function report_attendees()
233
-    {
234
-        $attendee_rows = EEM_Attendee::instance()->get_all_wpdb_results(
235
-            array(
236
-                'force_join' => array('State', 'Country'),
237
-                'caps'       => EEM_Base::caps_read_admin,
238
-            )
239
-        );
240
-        $csv_data = array();
241
-        foreach ($attendee_rows as $attendee_row) {
242
-            $csv_row = array();
243
-            foreach (EEM_Attendee::instance()->field_settings() as $field_name => $field_obj) {
244
-                if ($field_name == 'STA_ID') {
245
-                    $state_name_field = EEM_State::instance()->field_settings_for('STA_name');
246
-                    $csv_row[ __('State', 'event_espresso') ] = $attendee_row[ $state_name_field->get_qualified_column(
247
-                    ) ];
248
-                } elseif ($field_name == 'CNT_ISO') {
249
-                    $country_name_field = EEM_Country::instance()->field_settings_for('CNT_name');
250
-                    $csv_row[ __(
251
-                        'Country',
252
-                        'event_espresso'
253
-                    ) ] = $attendee_row[ $country_name_field->get_qualified_column() ];
254
-                } else {
255
-                    $csv_row[ $field_obj->get_nicename() ] = $attendee_row[ $field_obj->get_qualified_column() ];
256
-                }
257
-            }
258
-            $csv_data[] = $csv_row;
259
-        }
260
-
261
-        $filename = $this->generate_filename('contact-list-report');
262
-
263
-        $handle = $this->EE_CSV->begin_sending_csv($filename);
264
-        $this->EE_CSV->write_data_array_to_csv($handle, $csv_data);
265
-        $this->EE_CSV->end_sending_csv($handle);
266
-    }
267
-
268
-
269
-    /**
270
-     * @Export data for ALL attendees
271
-     * @access public
272
-     * @return void
273
-     */
274
-    public function export_attendees()
275
-    {
276
-
277
-        $states_that_have_an_attendee = EEM_State::instance()->get_all(
278
-            array(0 => array('Attendee.ATT_ID' => array('IS NOT NULL')))
279
-        );
280
-        $countries_that_have_an_attendee = EEM_Country::instance()->get_all(
281
-            array(0 => array('Attendee.ATT_ID' => array('IS NOT NULL')))
282
-        );
283
-        // $states_to_export_query_params
284
-        $models_to_export = array(
285
-            'Country'  => array(array('CNT_ISO' => array('IN', array_keys($countries_that_have_an_attendee)))),
286
-            'State'    => array(array('STA_ID' => array('IN', array_keys($states_that_have_an_attendee)))),
287
-            'Attendee' => array(),
288
-        );
289
-
290
-
291
-        $model_data = $this->_get_export_data_for_models($models_to_export);
292
-        $filename = $this->generate_filename('all-attendees');
293
-
294
-        if (! $this->EE_CSV->export_multiple_model_data_to_csv($filename, $model_data)) {
295
-            EE_Error::add_error(
296
-                __(
297
-                    'An error occurred and the Attendee data could not be exported from the database.',
298
-                    'event_espresso'
299
-                ),
300
-                __FILE__,
301
-                __FUNCTION__,
302
-                __LINE__
303
-            );
304
-        }
305
-    }
306
-
307
-    /**
308
-     * Shortcut for preparing a database result for display
309
-     *
310
-     * @param EEM_Base       $model
311
-     * @param string         $field_name
312
-     * @param string         $raw_db_value
313
-     * @param boolean|string $pretty_schema true to display pretty, a string to use a specific "Schema", or false to
314
-     *                                      NOT display pretty
315
-     * @return string
316
-     */
317
-    protected function _prepare_value_from_db_for_display($model, $field_name, $raw_db_value, $pretty_schema = true)
318
-    {
319
-        $field_obj = $model->field_settings_for($field_name);
320
-        $value_on_model_obj = $field_obj->prepare_for_set_from_db($raw_db_value);
321
-        if ($field_obj instanceof EE_Datetime_Field) {
322
-            $field_obj->set_date_format(
323
-                EE_CSV::instance()->get_date_format_for_csv($field_obj->get_date_format($pretty_schema)),
324
-                $pretty_schema
325
-            );
326
-            $field_obj->set_time_format(
327
-                EE_CSV::instance()->get_time_format_for_csv($field_obj->get_time_format($pretty_schema)),
328
-                $pretty_schema
329
-            );
330
-        }
331
-        if ($pretty_schema === true) {
332
-            return $field_obj->prepare_for_pretty_echoing($value_on_model_obj);
333
-        } elseif (is_string($pretty_schema)) {
334
-            return $field_obj->prepare_for_pretty_echoing($value_on_model_obj, $pretty_schema);
335
-        } else {
336
-            return $field_obj->prepare_for_get($value_on_model_obj);
337
-        }
338
-    }
339
-
340
-    /**
341
-     * Export a custom CSV of registration info including: A bunch of the reg fields, the time of the event, the price
342
-     * name, and the questions associated with the registrations
343
-     *
344
-     * @param int $event_id
345
-     */
346
-    public function report_registrations_for_event($event_id = null)
347
-    {
348
-        $reg_fields_to_include = array(
349
-            'TXN_ID',
350
-            'ATT_ID',
351
-            'REG_ID',
352
-            'REG_date',
353
-            'REG_code',
354
-            'REG_count',
355
-            'REG_final_price',
356
-
357
-        );
358
-        $att_fields_to_include = array(
359
-            'ATT_fname',
360
-            'ATT_lname',
361
-            'ATT_email',
362
-            'ATT_address',
363
-            'ATT_address2',
364
-            'ATT_city',
365
-            'STA_ID',
366
-            'CNT_ISO',
367
-            'ATT_zip',
368
-            'ATT_phone',
369
-        );
370
-
371
-        $registrations_csv_ready_array = array();
372
-        $reg_model = EE_Registry::instance()->load_model('Registration');
373
-        $query_params = apply_filters(
374
-            'FHEE__EE_Export__report_registration_for_event',
375
-            array(
376
-                array(
377
-                    'OR'                 => array(
378
-                        // don't include registrations from failed or abandoned transactions...
379
-                        'Transaction.STS_ID' => array(
380
-                            'NOT IN',
381
-                            array(EEM_Transaction::failed_status_code, EEM_Transaction::abandoned_status_code),
382
-                        ),
383
-                        // unless the registration is approved, in which case include it regardless of transaction status
384
-                        'STS_ID'             => EEM_Registration::status_id_approved,
385
-                    ),
386
-                    'Ticket.TKT_deleted' => array('IN', array(true, false)),
387
-                ),
388
-                'order_by'   => array('Transaction.TXN_ID' => 'asc', 'REG_count' => 'asc'),
389
-                'force_join' => array('Transaction', 'Ticket', 'Attendee'),
390
-                'caps'       => EEM_Base::caps_read_admin,
391
-            ),
392
-            $event_id
393
-        );
394
-        if ($event_id) {
395
-            $query_params[0]['EVT_ID'] = $event_id;
396
-        } else {
397
-            $query_params['force_join'][] = 'Event';
398
-        }
399
-        $registration_rows = $reg_model->get_all_wpdb_results($query_params);
400
-        // get all questions which relate to someone in this group
401
-        $registration_ids = array();
402
-        foreach ($registration_rows as $reg_row) {
403
-            $registration_ids[] = intval($reg_row['Registration.REG_ID']);
404
-        }
405
-        // EEM_Question::instance()->show_next_x_db_queries();
406
-        $questions_for_these_regs_rows = EEM_Question::instance()->get_all_wpdb_results(
407
-            array(array('Answer.REG_ID' => array('IN', $registration_ids)))
408
-        );
409
-        foreach ($registration_rows as $reg_row) {
410
-            if (is_array($reg_row)) {
411
-                $reg_csv_array = array();
412
-                if (! $event_id) {
413
-                    // get the event's name and Id
414
-                    $reg_csv_array[ __('Event', 'event_espresso') ] = sprintf(
415
-                        __('%1$s (%2$s)', 'event_espresso'),
416
-                        $this->_prepare_value_from_db_for_display(
417
-                            EEM_Event::instance(),
418
-                            'EVT_name',
419
-                            $reg_row['Event_CPT.post_title']
420
-                        ),
421
-                        $reg_row['Event_CPT.ID']
422
-                    );
423
-                }
424
-                $is_primary_reg = $reg_row['Registration.REG_count'] == '1' ? true : false;
425
-                /*@var $reg_row EE_Registration */
426
-                foreach ($reg_fields_to_include as $field_name) {
427
-                    $field = $reg_model->field_settings_for($field_name);
428
-                    if ($field_name == 'REG_final_price') {
429
-                        $value = $this->_prepare_value_from_db_for_display(
430
-                            $reg_model,
431
-                            $field_name,
432
-                            $reg_row['Registration.REG_final_price'],
433
-                            'localized_float'
434
-                        );
435
-                    } elseif ($field_name == 'REG_count') {
436
-                        $value = sprintf(
437
-                            __('%s of %s', 'event_espresso'),
438
-                            $this->_prepare_value_from_db_for_display(
439
-                                $reg_model,
440
-                                'REG_count',
441
-                                $reg_row['Registration.REG_count']
442
-                            ),
443
-                            $this->_prepare_value_from_db_for_display(
444
-                                $reg_model,
445
-                                'REG_group_size',
446
-                                $reg_row['Registration.REG_group_size']
447
-                            )
448
-                        );
449
-                    } elseif ($field_name == 'REG_date') {
450
-                        $value = $this->_prepare_value_from_db_for_display(
451
-                            $reg_model,
452
-                            $field_name,
453
-                            $reg_row['Registration.REG_date'],
454
-                            'no_html'
455
-                        );
456
-                    } else {
457
-                        $value = $this->_prepare_value_from_db_for_display(
458
-                            $reg_model,
459
-                            $field_name,
460
-                            $reg_row[ $field->get_qualified_column() ]
461
-                        );
462
-                    }
463
-                    $reg_csv_array[ $this->_get_column_name_for_field($field) ] = $value;
464
-                    if ($field_name == 'REG_final_price') {
465
-                        // add a column named Currency after the final price
466
-                        $reg_csv_array[ __("Currency", "event_espresso") ] = EE_Config::instance()->currency->code;
467
-                    }
468
-                }
469
-                // get pretty status
470
-                $stati = EEM_Status::instance()->localized_status(
471
-                    array(
472
-                        $reg_row['Registration.STS_ID']     => __('unknown', 'event_espresso'),
473
-                        $reg_row['TransactionTable.STS_ID'] => __('unknown', 'event_espresso'),
474
-                    ),
475
-                    false,
476
-                    'sentence'
477
-                );
478
-                $reg_csv_array[ __(
479
-                    "Registration Status",
480
-                    'event_espresso'
481
-                ) ] = $stati[ $reg_row['Registration.STS_ID'] ];
482
-                // get pretty trnasaction status
483
-                $reg_csv_array[ __(
484
-                    "Transaction Status",
485
-                    'event_espresso'
486
-                ) ] = $stati[ $reg_row['TransactionTable.STS_ID'] ];
487
-                $reg_csv_array[ __('Transaction Amount Due', 'event_espresso') ] = $is_primary_reg
488
-                    ? $this->_prepare_value_from_db_for_display(
489
-                        EEM_Transaction::instance(),
490
-                        'TXN_total',
491
-                        $reg_row['TransactionTable.TXN_total'],
492
-                        'localized_float'
493
-                    ) : '0.00';
494
-                $reg_csv_array[ __('Amount Paid', 'event_espresso') ] = $is_primary_reg
495
-                    ? $this->_prepare_value_from_db_for_display(
496
-                        EEM_Transaction::instance(),
497
-                        'TXN_paid',
498
-                        $reg_row['TransactionTable.TXN_paid'],
499
-                        'localized_float'
500
-                    ) : '0.00';
501
-                $payment_methods = array();
502
-                $gateway_txn_ids_etc = array();
503
-                $payment_times = array();
504
-                if ($is_primary_reg && $reg_row['TransactionTable.TXN_ID']) {
505
-                    $payments_info = EEM_Payment::instance()->get_all_wpdb_results(
506
-                        array(
507
-                            array(
508
-                                'TXN_ID' => $reg_row['TransactionTable.TXN_ID'],
509
-                                'STS_ID' => EEM_Payment::status_id_approved,
510
-                            ),
511
-                            'force_join' => array('Payment_Method'),
512
-                        ),
513
-                        ARRAY_A,
514
-                        'Payment_Method.PMD_admin_name as name, Payment.PAY_txn_id_chq_nmbr as gateway_txn_id, Payment.PAY_timestamp as payment_time'
515
-                    );
516
-
517
-                    foreach ($payments_info as $payment_method_and_gateway_txn_id) {
518
-                        $payment_methods[] = isset($payment_method_and_gateway_txn_id['name'])
519
-                            ? $payment_method_and_gateway_txn_id['name'] : __('Unknown', 'event_espresso');
520
-                        $gateway_txn_ids_etc[] = isset($payment_method_and_gateway_txn_id['gateway_txn_id'])
521
-                            ? $payment_method_and_gateway_txn_id['gateway_txn_id'] : '';
522
-                        $payment_times[] = isset($payment_method_and_gateway_txn_id['payment_time'])
523
-                            ? $payment_method_and_gateway_txn_id['payment_time'] : '';
524
-                    }
525
-                }
526
-                $reg_csv_array[ __('Payment Date(s)', 'event_espresso') ] = implode(',', $payment_times);
527
-                $reg_csv_array[ __('Payment Method(s)', 'event_espresso') ] = implode(",", $payment_methods);
528
-                $reg_csv_array[ __('Gateway Transaction ID(s)', 'event_espresso') ] = implode(
529
-                    ',',
530
-                    $gateway_txn_ids_etc
531
-                );
532
-
533
-                // get whether or not the user has checked in
534
-                $reg_csv_array[ __("Check-Ins", "event_espresso") ] = $reg_model->count_related(
535
-                    $reg_row['Registration.REG_ID'],
536
-                    'Checkin'
537
-                );
538
-                // get ticket of registration and its price
539
-                $ticket_model = EE_Registry::instance()->load_model('Ticket');
540
-                if ($reg_row['Ticket.TKT_ID']) {
541
-                    $ticket_name = $this->_prepare_value_from_db_for_display(
542
-                        $ticket_model,
543
-                        'TKT_name',
544
-                        $reg_row['Ticket.TKT_name']
545
-                    );
546
-                    $datetimes_strings = array();
547
-                    foreach (
548
-                        EEM_Datetime::instance()->get_all_wpdb_results(
549
-                            array(
550
-                            array('Ticket.TKT_ID' => $reg_row['Ticket.TKT_ID']),
551
-                            'order_by'                 => array('DTT_EVT_start' => 'ASC'),
552
-                            'default_where_conditions' => 'none',
553
-                            )
554
-                        ) as $datetime
555
-                    ) {
556
-                        $datetimes_strings[] = $this->_prepare_value_from_db_for_display(
557
-                            EEM_Datetime::instance(),
558
-                            'DTT_EVT_start',
559
-                            $datetime['Datetime.DTT_EVT_start']
560
-                        );
561
-                    }
562
-                } else {
563
-                    $ticket_name = __('Unknown', 'event_espresso');
564
-                    $datetimes_strings = array(__('Unknown', 'event_espresso'));
565
-                }
566
-                $reg_csv_array[ $ticket_model->field_settings_for('TKT_name')->get_nicename() ] = $ticket_name;
567
-                $reg_csv_array[ __("Datetimes of Ticket", "event_espresso") ] = implode(", ", $datetimes_strings);
568
-                // get datetime(s) of registration
569
-
570
-                // add attendee columns
571
-                foreach ($att_fields_to_include as $att_field_name) {
572
-                    $field_obj = EEM_Attendee::instance()->field_settings_for($att_field_name);
573
-                    if ($reg_row['Attendee_CPT.ID']) {
574
-                        if ($att_field_name == 'STA_ID') {
575
-                            $value = EEM_State::instance()->get_var(
576
-                                array(array('STA_ID' => $reg_row['Attendee_Meta.STA_ID'])),
577
-                                'STA_name'
578
-                            );
579
-                        } elseif ($att_field_name == 'CNT_ISO') {
580
-                            $value = EEM_Country::instance()->get_var(
581
-                                array(array('CNT_ISO' => $reg_row['Attendee_Meta.CNT_ISO'])),
582
-                                'CNT_name'
583
-                            );
584
-                        } else {
585
-                            $value = $this->_prepare_value_from_db_for_display(
586
-                                EEM_Attendee::instance(),
587
-                                $att_field_name,
588
-                                $reg_row[ $field_obj->get_qualified_column() ]
589
-                            );
590
-                        }
591
-                    } else {
592
-                        $value = '';
593
-                    }
594
-
595
-                    $reg_csv_array[ $this->_get_column_name_for_field($field_obj) ] = $value;
596
-                }
597
-
598
-                // make sure each registration has the same questions in the same order
599
-                foreach ($questions_for_these_regs_rows as $question_row) {
600
-                    if (! isset($reg_csv_array[ $question_row['Question.QST_admin_label'] ])) {
601
-                        $reg_csv_array[ $question_row['Question.QST_admin_label'] ] = null;
602
-                    }
603
-                }
604
-                // now fill out the questions THEY answered
605
-                foreach (
606
-                    EEM_Answer::instance()->get_all_wpdb_results(
607
-                        array(array('REG_ID' => $reg_row['Registration.REG_ID']), 'force_join' => array('Question'))
608
-                    ) as $answer_row
609
-                ) {
610
-                    /* @var $answer EE_Answer */
611
-                    if ($answer_row['Question.QST_ID']) {
612
-                        $question_label = $this->_prepare_value_from_db_for_display(
613
-                            EEM_Question::instance(),
614
-                            'QST_admin_label',
615
-                            $answer_row['Question.QST_admin_label']
616
-                        );
617
-                    } else {
618
-                        $question_label = sprintf(__('Question $s', 'event_espresso'), $answer_row['Answer.QST_ID']);
619
-                    }
620
-                    if (isset($answer_row['Question.QST_type']) && $answer_row['Question.QST_type'] == EEM_Question::QST_type_state) {
621
-                        $reg_csv_array[ $question_label ] = EEM_State::instance()->get_state_name_by_ID(
622
-                            $answer_row['Answer.ANS_value']
623
-                        );
624
-                    } else {
625
-                        $reg_csv_array[ $question_label ] = $this->_prepare_value_from_db_for_display(
626
-                            EEM_Answer::instance(),
627
-                            'ANS_value',
628
-                            $answer_row['Answer.ANS_value']
629
-                        );
630
-                    }
631
-                }
632
-                $registrations_csv_ready_array[] = apply_filters(
633
-                    'FHEE__EE_Export__report_registrations__reg_csv_array',
634
-                    $reg_csv_array,
635
-                    $reg_row
636
-                );
637
-            }
638
-        }
639
-
640
-        // if we couldn't export anything, we want to at least show the column headers
641
-        if (empty($registrations_csv_ready_array)) {
642
-            $reg_csv_array = array();
643
-            $model_and_fields_to_include = array(
644
-                'Registration' => $reg_fields_to_include,
645
-                'Attendee'     => $att_fields_to_include,
646
-            );
647
-            foreach ($model_and_fields_to_include as $model_name => $field_list) {
648
-                $model = EE_Registry::instance()->load_model($model_name);
649
-                foreach ($field_list as $field_name) {
650
-                    $field = $model->field_settings_for($field_name);
651
-                    $reg_csv_array[ $this->_get_column_name_for_field(
652
-                        $field
653
-                    ) ] = null;// $registration->get($field->get_name());
654
-                }
655
-            }
656
-            $registrations_csv_ready_array [] = $reg_csv_array;
657
-        }
658
-        if ($event_id) {
659
-            $event_slug = EEM_Event::instance()->get_var(array(array('EVT_ID' => $event_id)), 'EVT_slug');
660
-            if (! $event_slug) {
661
-                $event_slug = __('unknown', 'event_espresso');
662
-            }
663
-        } else {
664
-            $event_slug = __('all', 'event_espresso');
665
-        }
666
-        $filename = sprintf("registrations-for-%s", $event_slug);
667
-
668
-        $handle = $this->EE_CSV->begin_sending_csv($filename);
669
-        $this->EE_CSV->write_data_array_to_csv($handle, $registrations_csv_ready_array);
670
-        $this->EE_CSV->end_sending_csv($handle);
671
-    }
672
-
673
-    /**
674
-     * Gets the 'normal' column named for fields
675
-     *
676
-     * @param EE_Model_Field_Base $field
677
-     * @return string
678
-     */
679
-    protected function _get_column_name_for_field(EE_Model_Field_Base $field)
680
-    {
681
-        return $field->get_nicename() . "[" . $field->get_name() . "]";
682
-    }
683
-
684
-
685
-    /**
686
-     * @Export data for ALL events
687
-     * @access public
688
-     * @return void
689
-     */
690
-    public function export_categories()
691
-    {
692
-        // are any Event IDs set?
693
-        $query_params = array();
694
-        if (isset($this->_req_data['EVT_CAT_ID'])) {
695
-            // do we have an array of IDs ?
696
-            if (is_array($this->_req_data['EVT_CAT_ID'])) {
697
-                // generate an "IN (CSV)" where clause
698
-                $EVT_CAT_IDs = array_map('sanitize_text_field', $this->_req_data['EVT_CAT_ID']);
699
-                $filename = 'event-categories';
700
-                $query_params[0]['term_taxonomy_id'] = array('IN', $EVT_CAT_IDs);
701
-            } else {
702
-                // generate regular where = clause
703
-                $EVT_CAT_ID = absint($this->_req_data['EVT_CAT_ID']);
704
-                $filename = 'event-category#' . $EVT_CAT_ID;
705
-                $query_params[0]['term_taxonomy_id'] = $EVT_CAT_ID;
706
-            }
707
-        } else {
708
-            // no IDs means we will d/l the entire table
709
-            $filename = 'all-categories';
710
-        }
711
-
712
-        $tables_to_export = array(
713
-            'Term_Taxonomy' => $query_params,
714
-        );
715
-
716
-        $table_data = $this->_get_export_data_for_models($tables_to_export);
717
-        $filename = $this->generate_filename($filename);
718
-
719
-        if (! $this->EE_CSV->export_multiple_model_data_to_csv($filename, $table_data)) {
720
-            EE_Error::add_error(
721
-                __(
722
-                    'An error occurred and the Category details could not be exported from the database.',
723
-                    'event_espresso'
724
-                ),
725
-                __FILE__,
726
-                __FUNCTION__,
727
-                __LINE__
728
-            );
729
-        }
730
-    }
731
-
732
-
733
-    /**
734
-     * @process export name to create a suitable filename
735
-     * @access  private
736
-     * @param string - export_name
737
-     * @return string on success, FALSE on fail
738
-     */
739
-    private function generate_filename($export_name = '')
740
-    {
741
-        if ($export_name != '') {
742
-            $filename = get_bloginfo('name') . '-' . $export_name;
743
-            $filename = sanitize_key($filename) . '-' . $this->today;
744
-            return $filename;
745
-        } else {
746
-            EE_Error::add_error(__("No filename was provided", "event_espresso"), __FILE__, __FUNCTION__, __LINE__);
747
-        }
748
-        return false;
749
-    }
750
-
751
-
752
-    /**
753
-     * @recursive function for exporting table data and merging the results with the next results
754
-     * @access    private
755
-     * @param array $models_to_export keys are model names (eg 'Event', 'Attendee', etc.) and values are arrays of
756
-     *                                query params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
757
-     * @return array on success, FALSE on fail
758
-     */
759
-    private function _get_export_data_for_models($models_to_export = array())
760
-    {
761
-        $table_data = false;
762
-        if (is_array($models_to_export)) {
763
-            foreach ($models_to_export as $model_name => $query_params) {
764
-                // check for a numerically-indexed array. in that case, $model_name is the value!!
765
-                if (is_int($model_name)) {
766
-                    $model_name = $query_params;
767
-                    $query_params = array();
768
-                }
769
-                $model = EE_Registry::instance()->load_model($model_name);
770
-                $model_objects = $model->get_all($query_params);
771
-
772
-                $table_data[ $model_name ] = array();
773
-                foreach ($model_objects as $model_object) {
774
-                    $model_data_array = array();
775
-                    $fields = $model->field_settings();
776
-                    foreach ($fields as $field) {
777
-                        $column_name = $field->get_nicename() . "[" . $field->get_name() . "]";
778
-                        if ($field instanceof EE_Datetime_Field) {
779
-                            // $field->set_date_format('Y-m-d');
780
-                            // $field->set_time_format('H:i:s');
781
-                            $model_data_array[ $column_name ] = $model_object->get_datetime(
782
-                                $field->get_name(),
783
-                                'Y-m-d',
784
-                                'H:i:s'
785
-                            );
786
-                        } else {
787
-                            $model_data_array[ $column_name ] = $model_object->get($field->get_name());
788
-                        }
789
-                    }
790
-                    $table_data[ $model_name ][] = $model_data_array;
791
-                }
792
-            }
793
-        }
794
-        return $table_data;
795
-    }
19
+	const option_prefix = 'ee_report_job_';
20
+
21
+
22
+	// instance of the EE_Export object
23
+	private static $_instance;
24
+
25
+	// instance of the EE_CSV object
26
+	/**
27
+	 *
28
+	 * @var EE_CSV
29
+	 */
30
+	public $EE_CSV = null;
31
+
32
+
33
+	private $_req_data = array();
34
+
35
+
36
+	/**
37
+	 *        private constructor to prevent direct creation
38
+	 *
39
+	 * @Constructor
40
+	 * @access private
41
+	 * @param array $request_data
42
+	 */
43
+	private function __construct($request_data = array())
44
+	{
45
+		$this->_req_data = $request_data;
46
+		$this->today = date("Y-m-d", time());
47
+		require_once(EE_CLASSES . 'EE_CSV.class.php');
48
+		$this->EE_CSV = EE_CSV::instance();
49
+	}
50
+
51
+
52
+	/**
53
+	 *        @ singleton method used to instantiate class object
54
+	 *        @ access public
55
+	 *
56
+	 * @param array $request_data
57
+	 * @return \EE_Export
58
+	 */
59
+	public static function instance($request_data = array())
60
+	{
61
+		// check if class object is instantiated
62
+		if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_Export)) {
63
+			self::$_instance = new self($request_data);
64
+		}
65
+		return self::$_instance;
66
+	}
67
+
68
+
69
+	/**
70
+	 * @Export Event Espresso data - routes export requests
71
+	 * @access public
72
+	 * @return void | bool
73
+	 */
74
+	public function export()
75
+	{
76
+		// in case of bulk exports, the "actual" action will be in action2, but first check regular action for "export" keyword
77
+		if (isset($this->_req_data['action']) && strpos($this->_req_data['action'], 'export') === false) {
78
+			// check if action2 has export action
79
+			if (isset($this->_req_data['action2']) && strpos($this->_req_data['action2'], 'export') !== false) {
80
+				// whoop! there it is!
81
+				$this->_req_data['action'] = $this->_req_data['action2'];
82
+			}
83
+		}
84
+
85
+		$this->_req_data['export'] = isset($this->_req_data['export']) ? $this->_req_data['export'] : '';
86
+
87
+		switch ($this->_req_data['export']) {
88
+			case 'report':
89
+				switch ($this->_req_data['action']) {
90
+					case "event":
91
+					case "export_events":
92
+					case 'all_event_data':
93
+						$this->export_all_event_data();
94
+						break;
95
+
96
+					case 'registrations_report_for_event':
97
+						$this->report_registrations_for_event($this->_req_data['EVT_ID']);
98
+						break;
99
+
100
+					case 'attendees':
101
+						$this->export_attendees();
102
+						break;
103
+
104
+					case 'categories':
105
+						$this->export_categories();
106
+						break;
107
+
108
+					default:
109
+						EE_Error::add_error(
110
+							__('An error occurred! The requested export report could not be found.', 'event_espresso'),
111
+							__FILE__,
112
+							__FUNCTION__,
113
+							__LINE__
114
+						);
115
+						return false;
116
+						break;
117
+				}
118
+				break; // end of switch export : report
119
+			default:
120
+				break;
121
+		} // end of switch export
122
+
123
+		exit;
124
+	}
125
+
126
+	/**
127
+	 * Downloads a CSV file with all the columns, but no data. This should be used for importing
128
+	 *
129
+	 * @return null kills execution
130
+	 */
131
+	public function export_sample()
132
+	{
133
+		$event = EEM_Event::instance()->get_one();
134
+		$this->_req_data['EVT_ID'] = $event->ID();
135
+		$this->export_all_event_data();
136
+	}
137
+
138
+
139
+	/**
140
+	 * @Export data for ALL events
141
+	 * @access public
142
+	 * @return void
143
+	 */
144
+	public function export_all_event_data()
145
+	{
146
+		// are any Event IDs set?
147
+		$event_query_params = array();
148
+		$related_models_query_params = array();
149
+		$related_through_reg_query_params = array();
150
+		$datetime_ticket_query_params = array();
151
+		$price_query_params = array();
152
+		$price_type_query_params = array();
153
+		$term_query_params = array();
154
+		$state_country_query_params = array();
155
+		$question_group_query_params = array();
156
+		$question_query_params = array();
157
+		if (isset($this->_req_data['EVT_ID'])) {
158
+			// do we have an array of IDs ?
159
+
160
+			if (is_array($this->_req_data['EVT_ID'])) {
161
+				$EVT_IDs = array_map('sanitize_text_field', $this->_req_data['EVT_ID']);
162
+				$value_to_equal = array('IN', $EVT_IDs);
163
+				$filename = 'events';
164
+			} else {
165
+				// generate regular where = clause
166
+				$EVT_ID = absint($this->_req_data['EVT_ID']);
167
+				$value_to_equal = $EVT_ID;
168
+				$event = EE_Registry::instance()->load_model('Event')->get_one_by_ID($EVT_ID);
169
+
170
+				$filename = 'event-' . ($event instanceof EE_Event ? $event->slug() : __('unknown', 'event_espresso'));
171
+			}
172
+			$event_query_params[0]['EVT_ID'] = $value_to_equal;
173
+			$related_models_query_params[0]['Event.EVT_ID'] = $value_to_equal;
174
+			$related_through_reg_query_params[0]['Registration.EVT_ID'] = $value_to_equal;
175
+			$datetime_ticket_query_params[0]['Datetime.EVT_ID'] = $value_to_equal;
176
+			$price_query_params[0]['Ticket.Datetime.EVT_ID'] = $value_to_equal;
177
+			$price_type_query_params[0]['Price.Ticket.Datetime.EVT_ID'] = $value_to_equal;
178
+			$term_query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $value_to_equal;
179
+			$state_country_query_params[0]['Venue.Event.EVT_ID'] = $value_to_equal;
180
+			$question_group_query_params[0]['Event.EVT_ID'] = $value_to_equal;
181
+			$question_query_params[0]['Question_Group.Event.EVT_ID'] = $value_to_equal;
182
+		} else {
183
+			$filename = 'all-events';
184
+		}
185
+
186
+
187
+		// array in the format:  table name =>  query where clause
188
+		$models_to_export = array(
189
+			'Event'                   => $event_query_params,
190
+			'Datetime'                => $related_models_query_params,
191
+			'Ticket_Template'         => $price_query_params,
192
+			'Ticket'                  => $datetime_ticket_query_params,
193
+			'Datetime_Ticket'         => $datetime_ticket_query_params,
194
+			'Price_Type'              => $price_type_query_params,
195
+			'Price'                   => $price_query_params,
196
+			'Ticket_Price'            => $price_query_params,
197
+			'Term'                    => $term_query_params,
198
+			'Term_Taxonomy'           => $related_models_query_params,
199
+			'Term_Relationship'       => $related_models_query_params, // model has NO primary key...
200
+			'Country'                 => $state_country_query_params,
201
+			'State'                   => $state_country_query_params,
202
+			'Venue'                   => $related_models_query_params,
203
+			'Event_Venue'             => $related_models_query_params,
204
+			'Question_Group'          => $question_group_query_params,
205
+			'Event_Question_Group'    => $question_group_query_params,
206
+			'Question'                => $question_query_params,
207
+			'Question_Group_Question' => $question_query_params,
208
+			// 'Transaction'=>$related_through_reg_query_params,
209
+			// 'Registration'=>$related_models_query_params,
210
+			// 'Attendee'=>$related_through_reg_query_params,
211
+			// 'Line_Item'=>
212
+
213
+		);
214
+
215
+		$model_data = $this->_get_export_data_for_models($models_to_export);
216
+
217
+		$filename = $this->generate_filename($filename);
218
+
219
+		if (! $this->EE_CSV->export_multiple_model_data_to_csv($filename, $model_data)) {
220
+			EE_Error::add_error(
221
+				__(
222
+					"'An error occurred and the Event details could not be exported from the database.'",
223
+					"event_espresso"
224
+				),
225
+				__FILE__,
226
+				__FUNCTION__,
227
+				__LINE__
228
+			);
229
+		}
230
+	}
231
+
232
+	public function report_attendees()
233
+	{
234
+		$attendee_rows = EEM_Attendee::instance()->get_all_wpdb_results(
235
+			array(
236
+				'force_join' => array('State', 'Country'),
237
+				'caps'       => EEM_Base::caps_read_admin,
238
+			)
239
+		);
240
+		$csv_data = array();
241
+		foreach ($attendee_rows as $attendee_row) {
242
+			$csv_row = array();
243
+			foreach (EEM_Attendee::instance()->field_settings() as $field_name => $field_obj) {
244
+				if ($field_name == 'STA_ID') {
245
+					$state_name_field = EEM_State::instance()->field_settings_for('STA_name');
246
+					$csv_row[ __('State', 'event_espresso') ] = $attendee_row[ $state_name_field->get_qualified_column(
247
+					) ];
248
+				} elseif ($field_name == 'CNT_ISO') {
249
+					$country_name_field = EEM_Country::instance()->field_settings_for('CNT_name');
250
+					$csv_row[ __(
251
+						'Country',
252
+						'event_espresso'
253
+					) ] = $attendee_row[ $country_name_field->get_qualified_column() ];
254
+				} else {
255
+					$csv_row[ $field_obj->get_nicename() ] = $attendee_row[ $field_obj->get_qualified_column() ];
256
+				}
257
+			}
258
+			$csv_data[] = $csv_row;
259
+		}
260
+
261
+		$filename = $this->generate_filename('contact-list-report');
262
+
263
+		$handle = $this->EE_CSV->begin_sending_csv($filename);
264
+		$this->EE_CSV->write_data_array_to_csv($handle, $csv_data);
265
+		$this->EE_CSV->end_sending_csv($handle);
266
+	}
267
+
268
+
269
+	/**
270
+	 * @Export data for ALL attendees
271
+	 * @access public
272
+	 * @return void
273
+	 */
274
+	public function export_attendees()
275
+	{
276
+
277
+		$states_that_have_an_attendee = EEM_State::instance()->get_all(
278
+			array(0 => array('Attendee.ATT_ID' => array('IS NOT NULL')))
279
+		);
280
+		$countries_that_have_an_attendee = EEM_Country::instance()->get_all(
281
+			array(0 => array('Attendee.ATT_ID' => array('IS NOT NULL')))
282
+		);
283
+		// $states_to_export_query_params
284
+		$models_to_export = array(
285
+			'Country'  => array(array('CNT_ISO' => array('IN', array_keys($countries_that_have_an_attendee)))),
286
+			'State'    => array(array('STA_ID' => array('IN', array_keys($states_that_have_an_attendee)))),
287
+			'Attendee' => array(),
288
+		);
289
+
290
+
291
+		$model_data = $this->_get_export_data_for_models($models_to_export);
292
+		$filename = $this->generate_filename('all-attendees');
293
+
294
+		if (! $this->EE_CSV->export_multiple_model_data_to_csv($filename, $model_data)) {
295
+			EE_Error::add_error(
296
+				__(
297
+					'An error occurred and the Attendee data could not be exported from the database.',
298
+					'event_espresso'
299
+				),
300
+				__FILE__,
301
+				__FUNCTION__,
302
+				__LINE__
303
+			);
304
+		}
305
+	}
306
+
307
+	/**
308
+	 * Shortcut for preparing a database result for display
309
+	 *
310
+	 * @param EEM_Base       $model
311
+	 * @param string         $field_name
312
+	 * @param string         $raw_db_value
313
+	 * @param boolean|string $pretty_schema true to display pretty, a string to use a specific "Schema", or false to
314
+	 *                                      NOT display pretty
315
+	 * @return string
316
+	 */
317
+	protected function _prepare_value_from_db_for_display($model, $field_name, $raw_db_value, $pretty_schema = true)
318
+	{
319
+		$field_obj = $model->field_settings_for($field_name);
320
+		$value_on_model_obj = $field_obj->prepare_for_set_from_db($raw_db_value);
321
+		if ($field_obj instanceof EE_Datetime_Field) {
322
+			$field_obj->set_date_format(
323
+				EE_CSV::instance()->get_date_format_for_csv($field_obj->get_date_format($pretty_schema)),
324
+				$pretty_schema
325
+			);
326
+			$field_obj->set_time_format(
327
+				EE_CSV::instance()->get_time_format_for_csv($field_obj->get_time_format($pretty_schema)),
328
+				$pretty_schema
329
+			);
330
+		}
331
+		if ($pretty_schema === true) {
332
+			return $field_obj->prepare_for_pretty_echoing($value_on_model_obj);
333
+		} elseif (is_string($pretty_schema)) {
334
+			return $field_obj->prepare_for_pretty_echoing($value_on_model_obj, $pretty_schema);
335
+		} else {
336
+			return $field_obj->prepare_for_get($value_on_model_obj);
337
+		}
338
+	}
339
+
340
+	/**
341
+	 * Export a custom CSV of registration info including: A bunch of the reg fields, the time of the event, the price
342
+	 * name, and the questions associated with the registrations
343
+	 *
344
+	 * @param int $event_id
345
+	 */
346
+	public function report_registrations_for_event($event_id = null)
347
+	{
348
+		$reg_fields_to_include = array(
349
+			'TXN_ID',
350
+			'ATT_ID',
351
+			'REG_ID',
352
+			'REG_date',
353
+			'REG_code',
354
+			'REG_count',
355
+			'REG_final_price',
356
+
357
+		);
358
+		$att_fields_to_include = array(
359
+			'ATT_fname',
360
+			'ATT_lname',
361
+			'ATT_email',
362
+			'ATT_address',
363
+			'ATT_address2',
364
+			'ATT_city',
365
+			'STA_ID',
366
+			'CNT_ISO',
367
+			'ATT_zip',
368
+			'ATT_phone',
369
+		);
370
+
371
+		$registrations_csv_ready_array = array();
372
+		$reg_model = EE_Registry::instance()->load_model('Registration');
373
+		$query_params = apply_filters(
374
+			'FHEE__EE_Export__report_registration_for_event',
375
+			array(
376
+				array(
377
+					'OR'                 => array(
378
+						// don't include registrations from failed or abandoned transactions...
379
+						'Transaction.STS_ID' => array(
380
+							'NOT IN',
381
+							array(EEM_Transaction::failed_status_code, EEM_Transaction::abandoned_status_code),
382
+						),
383
+						// unless the registration is approved, in which case include it regardless of transaction status
384
+						'STS_ID'             => EEM_Registration::status_id_approved,
385
+					),
386
+					'Ticket.TKT_deleted' => array('IN', array(true, false)),
387
+				),
388
+				'order_by'   => array('Transaction.TXN_ID' => 'asc', 'REG_count' => 'asc'),
389
+				'force_join' => array('Transaction', 'Ticket', 'Attendee'),
390
+				'caps'       => EEM_Base::caps_read_admin,
391
+			),
392
+			$event_id
393
+		);
394
+		if ($event_id) {
395
+			$query_params[0]['EVT_ID'] = $event_id;
396
+		} else {
397
+			$query_params['force_join'][] = 'Event';
398
+		}
399
+		$registration_rows = $reg_model->get_all_wpdb_results($query_params);
400
+		// get all questions which relate to someone in this group
401
+		$registration_ids = array();
402
+		foreach ($registration_rows as $reg_row) {
403
+			$registration_ids[] = intval($reg_row['Registration.REG_ID']);
404
+		}
405
+		// EEM_Question::instance()->show_next_x_db_queries();
406
+		$questions_for_these_regs_rows = EEM_Question::instance()->get_all_wpdb_results(
407
+			array(array('Answer.REG_ID' => array('IN', $registration_ids)))
408
+		);
409
+		foreach ($registration_rows as $reg_row) {
410
+			if (is_array($reg_row)) {
411
+				$reg_csv_array = array();
412
+				if (! $event_id) {
413
+					// get the event's name and Id
414
+					$reg_csv_array[ __('Event', 'event_espresso') ] = sprintf(
415
+						__('%1$s (%2$s)', 'event_espresso'),
416
+						$this->_prepare_value_from_db_for_display(
417
+							EEM_Event::instance(),
418
+							'EVT_name',
419
+							$reg_row['Event_CPT.post_title']
420
+						),
421
+						$reg_row['Event_CPT.ID']
422
+					);
423
+				}
424
+				$is_primary_reg = $reg_row['Registration.REG_count'] == '1' ? true : false;
425
+				/*@var $reg_row EE_Registration */
426
+				foreach ($reg_fields_to_include as $field_name) {
427
+					$field = $reg_model->field_settings_for($field_name);
428
+					if ($field_name == 'REG_final_price') {
429
+						$value = $this->_prepare_value_from_db_for_display(
430
+							$reg_model,
431
+							$field_name,
432
+							$reg_row['Registration.REG_final_price'],
433
+							'localized_float'
434
+						);
435
+					} elseif ($field_name == 'REG_count') {
436
+						$value = sprintf(
437
+							__('%s of %s', 'event_espresso'),
438
+							$this->_prepare_value_from_db_for_display(
439
+								$reg_model,
440
+								'REG_count',
441
+								$reg_row['Registration.REG_count']
442
+							),
443
+							$this->_prepare_value_from_db_for_display(
444
+								$reg_model,
445
+								'REG_group_size',
446
+								$reg_row['Registration.REG_group_size']
447
+							)
448
+						);
449
+					} elseif ($field_name == 'REG_date') {
450
+						$value = $this->_prepare_value_from_db_for_display(
451
+							$reg_model,
452
+							$field_name,
453
+							$reg_row['Registration.REG_date'],
454
+							'no_html'
455
+						);
456
+					} else {
457
+						$value = $this->_prepare_value_from_db_for_display(
458
+							$reg_model,
459
+							$field_name,
460
+							$reg_row[ $field->get_qualified_column() ]
461
+						);
462
+					}
463
+					$reg_csv_array[ $this->_get_column_name_for_field($field) ] = $value;
464
+					if ($field_name == 'REG_final_price') {
465
+						// add a column named Currency after the final price
466
+						$reg_csv_array[ __("Currency", "event_espresso") ] = EE_Config::instance()->currency->code;
467
+					}
468
+				}
469
+				// get pretty status
470
+				$stati = EEM_Status::instance()->localized_status(
471
+					array(
472
+						$reg_row['Registration.STS_ID']     => __('unknown', 'event_espresso'),
473
+						$reg_row['TransactionTable.STS_ID'] => __('unknown', 'event_espresso'),
474
+					),
475
+					false,
476
+					'sentence'
477
+				);
478
+				$reg_csv_array[ __(
479
+					"Registration Status",
480
+					'event_espresso'
481
+				) ] = $stati[ $reg_row['Registration.STS_ID'] ];
482
+				// get pretty trnasaction status
483
+				$reg_csv_array[ __(
484
+					"Transaction Status",
485
+					'event_espresso'
486
+				) ] = $stati[ $reg_row['TransactionTable.STS_ID'] ];
487
+				$reg_csv_array[ __('Transaction Amount Due', 'event_espresso') ] = $is_primary_reg
488
+					? $this->_prepare_value_from_db_for_display(
489
+						EEM_Transaction::instance(),
490
+						'TXN_total',
491
+						$reg_row['TransactionTable.TXN_total'],
492
+						'localized_float'
493
+					) : '0.00';
494
+				$reg_csv_array[ __('Amount Paid', 'event_espresso') ] = $is_primary_reg
495
+					? $this->_prepare_value_from_db_for_display(
496
+						EEM_Transaction::instance(),
497
+						'TXN_paid',
498
+						$reg_row['TransactionTable.TXN_paid'],
499
+						'localized_float'
500
+					) : '0.00';
501
+				$payment_methods = array();
502
+				$gateway_txn_ids_etc = array();
503
+				$payment_times = array();
504
+				if ($is_primary_reg && $reg_row['TransactionTable.TXN_ID']) {
505
+					$payments_info = EEM_Payment::instance()->get_all_wpdb_results(
506
+						array(
507
+							array(
508
+								'TXN_ID' => $reg_row['TransactionTable.TXN_ID'],
509
+								'STS_ID' => EEM_Payment::status_id_approved,
510
+							),
511
+							'force_join' => array('Payment_Method'),
512
+						),
513
+						ARRAY_A,
514
+						'Payment_Method.PMD_admin_name as name, Payment.PAY_txn_id_chq_nmbr as gateway_txn_id, Payment.PAY_timestamp as payment_time'
515
+					);
516
+
517
+					foreach ($payments_info as $payment_method_and_gateway_txn_id) {
518
+						$payment_methods[] = isset($payment_method_and_gateway_txn_id['name'])
519
+							? $payment_method_and_gateway_txn_id['name'] : __('Unknown', 'event_espresso');
520
+						$gateway_txn_ids_etc[] = isset($payment_method_and_gateway_txn_id['gateway_txn_id'])
521
+							? $payment_method_and_gateway_txn_id['gateway_txn_id'] : '';
522
+						$payment_times[] = isset($payment_method_and_gateway_txn_id['payment_time'])
523
+							? $payment_method_and_gateway_txn_id['payment_time'] : '';
524
+					}
525
+				}
526
+				$reg_csv_array[ __('Payment Date(s)', 'event_espresso') ] = implode(',', $payment_times);
527
+				$reg_csv_array[ __('Payment Method(s)', 'event_espresso') ] = implode(",", $payment_methods);
528
+				$reg_csv_array[ __('Gateway Transaction ID(s)', 'event_espresso') ] = implode(
529
+					',',
530
+					$gateway_txn_ids_etc
531
+				);
532
+
533
+				// get whether or not the user has checked in
534
+				$reg_csv_array[ __("Check-Ins", "event_espresso") ] = $reg_model->count_related(
535
+					$reg_row['Registration.REG_ID'],
536
+					'Checkin'
537
+				);
538
+				// get ticket of registration and its price
539
+				$ticket_model = EE_Registry::instance()->load_model('Ticket');
540
+				if ($reg_row['Ticket.TKT_ID']) {
541
+					$ticket_name = $this->_prepare_value_from_db_for_display(
542
+						$ticket_model,
543
+						'TKT_name',
544
+						$reg_row['Ticket.TKT_name']
545
+					);
546
+					$datetimes_strings = array();
547
+					foreach (
548
+						EEM_Datetime::instance()->get_all_wpdb_results(
549
+							array(
550
+							array('Ticket.TKT_ID' => $reg_row['Ticket.TKT_ID']),
551
+							'order_by'                 => array('DTT_EVT_start' => 'ASC'),
552
+							'default_where_conditions' => 'none',
553
+							)
554
+						) as $datetime
555
+					) {
556
+						$datetimes_strings[] = $this->_prepare_value_from_db_for_display(
557
+							EEM_Datetime::instance(),
558
+							'DTT_EVT_start',
559
+							$datetime['Datetime.DTT_EVT_start']
560
+						);
561
+					}
562
+				} else {
563
+					$ticket_name = __('Unknown', 'event_espresso');
564
+					$datetimes_strings = array(__('Unknown', 'event_espresso'));
565
+				}
566
+				$reg_csv_array[ $ticket_model->field_settings_for('TKT_name')->get_nicename() ] = $ticket_name;
567
+				$reg_csv_array[ __("Datetimes of Ticket", "event_espresso") ] = implode(", ", $datetimes_strings);
568
+				// get datetime(s) of registration
569
+
570
+				// add attendee columns
571
+				foreach ($att_fields_to_include as $att_field_name) {
572
+					$field_obj = EEM_Attendee::instance()->field_settings_for($att_field_name);
573
+					if ($reg_row['Attendee_CPT.ID']) {
574
+						if ($att_field_name == 'STA_ID') {
575
+							$value = EEM_State::instance()->get_var(
576
+								array(array('STA_ID' => $reg_row['Attendee_Meta.STA_ID'])),
577
+								'STA_name'
578
+							);
579
+						} elseif ($att_field_name == 'CNT_ISO') {
580
+							$value = EEM_Country::instance()->get_var(
581
+								array(array('CNT_ISO' => $reg_row['Attendee_Meta.CNT_ISO'])),
582
+								'CNT_name'
583
+							);
584
+						} else {
585
+							$value = $this->_prepare_value_from_db_for_display(
586
+								EEM_Attendee::instance(),
587
+								$att_field_name,
588
+								$reg_row[ $field_obj->get_qualified_column() ]
589
+							);
590
+						}
591
+					} else {
592
+						$value = '';
593
+					}
594
+
595
+					$reg_csv_array[ $this->_get_column_name_for_field($field_obj) ] = $value;
596
+				}
597
+
598
+				// make sure each registration has the same questions in the same order
599
+				foreach ($questions_for_these_regs_rows as $question_row) {
600
+					if (! isset($reg_csv_array[ $question_row['Question.QST_admin_label'] ])) {
601
+						$reg_csv_array[ $question_row['Question.QST_admin_label'] ] = null;
602
+					}
603
+				}
604
+				// now fill out the questions THEY answered
605
+				foreach (
606
+					EEM_Answer::instance()->get_all_wpdb_results(
607
+						array(array('REG_ID' => $reg_row['Registration.REG_ID']), 'force_join' => array('Question'))
608
+					) as $answer_row
609
+				) {
610
+					/* @var $answer EE_Answer */
611
+					if ($answer_row['Question.QST_ID']) {
612
+						$question_label = $this->_prepare_value_from_db_for_display(
613
+							EEM_Question::instance(),
614
+							'QST_admin_label',
615
+							$answer_row['Question.QST_admin_label']
616
+						);
617
+					} else {
618
+						$question_label = sprintf(__('Question $s', 'event_espresso'), $answer_row['Answer.QST_ID']);
619
+					}
620
+					if (isset($answer_row['Question.QST_type']) && $answer_row['Question.QST_type'] == EEM_Question::QST_type_state) {
621
+						$reg_csv_array[ $question_label ] = EEM_State::instance()->get_state_name_by_ID(
622
+							$answer_row['Answer.ANS_value']
623
+						);
624
+					} else {
625
+						$reg_csv_array[ $question_label ] = $this->_prepare_value_from_db_for_display(
626
+							EEM_Answer::instance(),
627
+							'ANS_value',
628
+							$answer_row['Answer.ANS_value']
629
+						);
630
+					}
631
+				}
632
+				$registrations_csv_ready_array[] = apply_filters(
633
+					'FHEE__EE_Export__report_registrations__reg_csv_array',
634
+					$reg_csv_array,
635
+					$reg_row
636
+				);
637
+			}
638
+		}
639
+
640
+		// if we couldn't export anything, we want to at least show the column headers
641
+		if (empty($registrations_csv_ready_array)) {
642
+			$reg_csv_array = array();
643
+			$model_and_fields_to_include = array(
644
+				'Registration' => $reg_fields_to_include,
645
+				'Attendee'     => $att_fields_to_include,
646
+			);
647
+			foreach ($model_and_fields_to_include as $model_name => $field_list) {
648
+				$model = EE_Registry::instance()->load_model($model_name);
649
+				foreach ($field_list as $field_name) {
650
+					$field = $model->field_settings_for($field_name);
651
+					$reg_csv_array[ $this->_get_column_name_for_field(
652
+						$field
653
+					) ] = null;// $registration->get($field->get_name());
654
+				}
655
+			}
656
+			$registrations_csv_ready_array [] = $reg_csv_array;
657
+		}
658
+		if ($event_id) {
659
+			$event_slug = EEM_Event::instance()->get_var(array(array('EVT_ID' => $event_id)), 'EVT_slug');
660
+			if (! $event_slug) {
661
+				$event_slug = __('unknown', 'event_espresso');
662
+			}
663
+		} else {
664
+			$event_slug = __('all', 'event_espresso');
665
+		}
666
+		$filename = sprintf("registrations-for-%s", $event_slug);
667
+
668
+		$handle = $this->EE_CSV->begin_sending_csv($filename);
669
+		$this->EE_CSV->write_data_array_to_csv($handle, $registrations_csv_ready_array);
670
+		$this->EE_CSV->end_sending_csv($handle);
671
+	}
672
+
673
+	/**
674
+	 * Gets the 'normal' column named for fields
675
+	 *
676
+	 * @param EE_Model_Field_Base $field
677
+	 * @return string
678
+	 */
679
+	protected function _get_column_name_for_field(EE_Model_Field_Base $field)
680
+	{
681
+		return $field->get_nicename() . "[" . $field->get_name() . "]";
682
+	}
683
+
684
+
685
+	/**
686
+	 * @Export data for ALL events
687
+	 * @access public
688
+	 * @return void
689
+	 */
690
+	public function export_categories()
691
+	{
692
+		// are any Event IDs set?
693
+		$query_params = array();
694
+		if (isset($this->_req_data['EVT_CAT_ID'])) {
695
+			// do we have an array of IDs ?
696
+			if (is_array($this->_req_data['EVT_CAT_ID'])) {
697
+				// generate an "IN (CSV)" where clause
698
+				$EVT_CAT_IDs = array_map('sanitize_text_field', $this->_req_data['EVT_CAT_ID']);
699
+				$filename = 'event-categories';
700
+				$query_params[0]['term_taxonomy_id'] = array('IN', $EVT_CAT_IDs);
701
+			} else {
702
+				// generate regular where = clause
703
+				$EVT_CAT_ID = absint($this->_req_data['EVT_CAT_ID']);
704
+				$filename = 'event-category#' . $EVT_CAT_ID;
705
+				$query_params[0]['term_taxonomy_id'] = $EVT_CAT_ID;
706
+			}
707
+		} else {
708
+			// no IDs means we will d/l the entire table
709
+			$filename = 'all-categories';
710
+		}
711
+
712
+		$tables_to_export = array(
713
+			'Term_Taxonomy' => $query_params,
714
+		);
715
+
716
+		$table_data = $this->_get_export_data_for_models($tables_to_export);
717
+		$filename = $this->generate_filename($filename);
718
+
719
+		if (! $this->EE_CSV->export_multiple_model_data_to_csv($filename, $table_data)) {
720
+			EE_Error::add_error(
721
+				__(
722
+					'An error occurred and the Category details could not be exported from the database.',
723
+					'event_espresso'
724
+				),
725
+				__FILE__,
726
+				__FUNCTION__,
727
+				__LINE__
728
+			);
729
+		}
730
+	}
731
+
732
+
733
+	/**
734
+	 * @process export name to create a suitable filename
735
+	 * @access  private
736
+	 * @param string - export_name
737
+	 * @return string on success, FALSE on fail
738
+	 */
739
+	private function generate_filename($export_name = '')
740
+	{
741
+		if ($export_name != '') {
742
+			$filename = get_bloginfo('name') . '-' . $export_name;
743
+			$filename = sanitize_key($filename) . '-' . $this->today;
744
+			return $filename;
745
+		} else {
746
+			EE_Error::add_error(__("No filename was provided", "event_espresso"), __FILE__, __FUNCTION__, __LINE__);
747
+		}
748
+		return false;
749
+	}
750
+
751
+
752
+	/**
753
+	 * @recursive function for exporting table data and merging the results with the next results
754
+	 * @access    private
755
+	 * @param array $models_to_export keys are model names (eg 'Event', 'Attendee', etc.) and values are arrays of
756
+	 *                                query params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
757
+	 * @return array on success, FALSE on fail
758
+	 */
759
+	private function _get_export_data_for_models($models_to_export = array())
760
+	{
761
+		$table_data = false;
762
+		if (is_array($models_to_export)) {
763
+			foreach ($models_to_export as $model_name => $query_params) {
764
+				// check for a numerically-indexed array. in that case, $model_name is the value!!
765
+				if (is_int($model_name)) {
766
+					$model_name = $query_params;
767
+					$query_params = array();
768
+				}
769
+				$model = EE_Registry::instance()->load_model($model_name);
770
+				$model_objects = $model->get_all($query_params);
771
+
772
+				$table_data[ $model_name ] = array();
773
+				foreach ($model_objects as $model_object) {
774
+					$model_data_array = array();
775
+					$fields = $model->field_settings();
776
+					foreach ($fields as $field) {
777
+						$column_name = $field->get_nicename() . "[" . $field->get_name() . "]";
778
+						if ($field instanceof EE_Datetime_Field) {
779
+							// $field->set_date_format('Y-m-d');
780
+							// $field->set_time_format('H:i:s');
781
+							$model_data_array[ $column_name ] = $model_object->get_datetime(
782
+								$field->get_name(),
783
+								'Y-m-d',
784
+								'H:i:s'
785
+							);
786
+						} else {
787
+							$model_data_array[ $column_name ] = $model_object->get($field->get_name());
788
+						}
789
+					}
790
+					$table_data[ $model_name ][] = $model_data_array;
791
+				}
792
+			}
793
+		}
794
+		return $table_data;
795
+	}
796 796
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Message.class.php 1 patch
Indentation   +867 added lines, -867 removed lines patch added patch discarded remove patch
@@ -10,875 +10,875 @@
 block discarded – undo
10 10
 class EE_Message extends EE_Base_Class implements EEI_Admin_Links
11 11
 {
12 12
 
13
-    /**
14
-     * @deprecated 4.9.0  Added for backward compat with add-on's
15
-     * @type null
16
-     */
17
-    public $template_pack;
18
-
19
-    /**
20
-     * @deprecated 4.9.0 Added for backward compat with add-on's
21
-     * @type null
22
-     */
23
-    public $template_variation;
24
-
25
-    /**
26
-     * @deprecated 4.9.0 Added for backward compat with add-on's
27
-     * @type string
28
-     */
29
-    public $content = '';
30
-
31
-
32
-    /**
33
-     * @type EE_messenger $_messenger
34
-     */
35
-    protected $_messenger = null;
36
-
37
-    /**
38
-     * @type EE_message_type $_message_type
39
-     */
40
-    protected $_message_type = null;
41
-
42
-
43
-    /**
44
-     * @param array  $props_n_values
45
-     * @param string $timezone
46
-     * @param array  $date_formats incoming date formats in an array.  First value is the date_format, second is time
47
-     *                             format.
48
-     * @return EE_Message
49
-     */
50
-    public static function new_instance($props_n_values = array(), $timezone = '', $date_formats = array())
51
-    {
52
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__);
53
-        // if object doesn't exist, let's generate a unique token on instantiation so that its available even before saving to db.
54
-        if (! $has_object) {
55
-            EE_Registry::instance()->load_helper('URL');
56
-            $props_n_values['MSG_token'] = EEH_URL::generate_unique_token();
57
-        }
58
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
59
-    }
60
-
61
-
62
-    /**
63
-     * @param array  $props_n_values
64
-     * @param string $timezone
65
-     * @return EE_Message
66
-     */
67
-    public static function new_instance_from_db($props_n_values = array(), $timezone = '')
68
-    {
69
-        return new self($props_n_values, true, $timezone);
70
-    }
71
-
72
-
73
-    /**
74
-     * Gets MSG_token
75
-     *
76
-     * @return int
77
-     */
78
-    public function MSG_token()
79
-    {
80
-        return $this->get('MSG_token');
81
-    }
82
-
83
-
84
-    /**
85
-     * Sets MSG_token
86
-     *
87
-     * @param int $MSG_token
88
-     */
89
-    public function set_MSG_token($MSG_token)
90
-    {
91
-        $this->set('MSG_token', $MSG_token);
92
-    }
93
-
94
-
95
-    /**
96
-     * Gets GRP_ID
97
-     *
98
-     * @return int
99
-     */
100
-    public function GRP_ID()
101
-    {
102
-        return $this->get('GRP_ID');
103
-    }
104
-
105
-
106
-    /**
107
-     * Sets GRP_ID
108
-     *
109
-     * @param int $GRP_ID
110
-     */
111
-    public function set_GRP_ID($GRP_ID)
112
-    {
113
-        $this->set('GRP_ID', $GRP_ID);
114
-    }
115
-
116
-
117
-    /**
118
-     * Gets TXN_ID
119
-     *
120
-     * @return int
121
-     */
122
-    public function TXN_ID()
123
-    {
124
-        return $this->get('TXN_ID');
125
-    }
126
-
127
-
128
-    /**
129
-     * Sets TXN_ID
130
-     *
131
-     * @param int $TXN_ID
132
-     */
133
-    public function set_TXN_ID($TXN_ID)
134
-    {
135
-        $this->set('TXN_ID', $TXN_ID);
136
-    }
137
-
138
-
139
-    /**
140
-     * Gets messenger
141
-     *
142
-     * @return string
143
-     */
144
-    public function messenger()
145
-    {
146
-        return $this->get('MSG_messenger');
147
-    }
148
-
149
-
150
-    /**
151
-     * Sets messenger
152
-     *
153
-     * @param string $messenger
154
-     */
155
-    public function set_messenger($messenger)
156
-    {
157
-        $this->set('MSG_messenger', $messenger);
158
-    }
159
-
160
-
161
-    /**
162
-     * Returns corresponding messenger object for the set messenger on this message
163
-     *
164
-     * @return EE_messenger | null
165
-     */
166
-    public function messenger_object()
167
-    {
168
-        return $this->_messenger;
169
-    }
170
-
171
-
172
-    /**
173
-     * Sets messenger
174
-     *
175
-     * @param EE_messenger $messenger
176
-     */
177
-    public function set_messenger_object(EE_messenger $messenger)
178
-    {
179
-        $this->_messenger = $messenger;
180
-    }
181
-
182
-
183
-    /**
184
-     * validates messenger
185
-     *
186
-     * @param bool $throw_exceptions
187
-     * @return bool
188
-     * @throws \EE_Error
189
-     */
190
-    public function valid_messenger($throw_exceptions = false)
191
-    {
192
-        if ($this->_messenger instanceof EE_messenger) {
193
-            return true;
194
-        }
195
-        if ($throw_exceptions) {
196
-            throw new EE_Error(
197
-                sprintf(
198
-                    __(
199
-                        'The "%1$s" messenger set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
200
-                        'event_espresso'
201
-                    ),
202
-                    $this->messenger()
203
-                )
204
-            );
205
-        }
206
-        return false;
207
-    }
208
-
209
-
210
-    /**
211
-     * This returns the set localized label for the messenger on this message.
212
-     * Note, if unable to retrieve the EE_messenger object then will just return the messenger slug saved
213
-     * with this message.
214
-     *
215
-     * @param   bool $plural whether to return the plural label or not.
216
-     * @return string
217
-     */
218
-    public function messenger_label($plural = false)
219
-    {
220
-        $label_type = $plural ? 'plural' : 'singular';
221
-        $messenger = $this->messenger_object();
222
-        return $messenger instanceof EE_messenger ? $messenger->label[ $label_type ] : $this->messenger();
223
-    }
224
-
225
-
226
-    /**
227
-     * Gets message_type
228
-     *
229
-     * @return string
230
-     */
231
-    public function message_type()
232
-    {
233
-        return $this->get('MSG_message_type');
234
-    }
235
-
236
-
237
-    /**
238
-     * Sets message_type
239
-     *
240
-     * @param string $message_type
241
-     */
242
-    public function set_message_type($message_type)
243
-    {
244
-        $this->set('MSG_message_type', $message_type);
245
-    }
246
-
247
-
248
-    /**
249
-     * Returns the message type object for the set message type on this message
250
-     *
251
-     * @return EE_message_type | null
252
-     */
253
-    public function message_type_object()
254
-    {
255
-        return $this->_message_type;
256
-    }
257
-
258
-
259
-    /**
260
-     * Sets message_type
261
-     *
262
-     * @param EE_message_type $message_type
263
-     * @param bool            $set_priority   This indicates whether to set the priority to whatever the priority is on
264
-     *                                        the message type or not.
265
-     */
266
-    public function set_message_type_object(EE_message_type $message_type, $set_priority = false)
267
-    {
268
-        $this->_message_type = $message_type;
269
-        if ($set_priority) {
270
-            $this->set_priority($this->_message_type->get_priority());
271
-        }
272
-    }
273
-
274
-
275
-    /**
276
-     * validates message_type
277
-     *
278
-     * @param bool $throw_exceptions
279
-     * @return bool
280
-     * @throws \EE_Error
281
-     */
282
-    public function valid_message_type($throw_exceptions = false)
283
-    {
284
-        if ($this->_message_type instanceof EE_message_type) {
285
-            return true;
286
-        }
287
-        if ($throw_exceptions) {
288
-            throw new EE_Error(
289
-                sprintf(
290
-                    __(
291
-                        'The %1$s message type set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
292
-                        'event_espresso'
293
-                    ),
294
-                    $this->message_type()
295
-                )
296
-            );
297
-        }
298
-        return false;
299
-    }
300
-
301
-
302
-    /**
303
-     * validates messenger and message_type (that they are valid EE_messenger and EE_message_type objects).
304
-     *
305
-     * @param bool $throw_exceptions
306
-     * @return bool
307
-     * @throws \EE_Error
308
-     */
309
-    public function is_valid($throw_exceptions = false)
310
-    {
311
-        if ($this->valid_messenger($throw_exceptions) && $this->valid_message_type($throw_exceptions)) {
312
-            return true;
313
-        }
314
-        return false;
315
-    }
316
-
317
-
318
-    /**
319
-     * This validates whether the internal messenger and message type objects are valid for sending.
320
-     * Three checks are done:
321
-     * 1. There is a valid messenger object.
322
-     * 2. There is a valid message type object.
323
-     * 3. The message type object is active for the messenger.
324
-     *
325
-     * @throws EE_Error  But only if $throw_exceptions is set to true.
326
-     * @param bool $throw_exceptions
327
-     * @return bool
328
-     */
329
-    public function is_valid_for_sending_or_generation($throw_exceptions = false)
330
-    {
331
-        $valid = false;
332
-        if ($this->is_valid($throw_exceptions)) {
333
-            /** @var EE_Message_Resource_Manager $message_resource_manager */
334
-            $message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
335
-            $valid = $message_resource_manager->is_message_type_active_for_messenger(
336
-                $this->messenger(),
337
-                $this->message_type()
338
-            );
339
-            if (! $valid && $throw_exceptions) {
340
-                throw new EE_Error(
341
-                    sprintf(
342
-                        __(
343
-                            'The %1$s message type is not a valid message type for the %2$s messenger so it will not be sent.',
344
-                            'event_espresso'
345
-                        ),
346
-                        $this->message_type(),
347
-                        $this->messenger()
348
-                    )
349
-                );
350
-            }
351
-        }
352
-        return $valid;
353
-    }
354
-
355
-
356
-    /**
357
-     * This returns the set localized label for the message type on this message.
358
-     * Note, if unable to retrieve the EE_message_type object then will just return the message type slug saved
359
-     * with this message.
360
-     *
361
-     * @param   bool $plural whether to return the plural label or not.
362
-     * @return string
363
-     */
364
-    public function message_type_label($plural = false)
365
-    {
366
-        $label_type = $plural ? 'plural' : 'singular';
367
-        $message_type = $this->message_type_object();
368
-        return $message_type instanceof EE_message_type
369
-            ? $message_type->label[ $label_type ]
370
-            : str_replace(
371
-                '_',
372
-                ' ',
373
-                $this->message_type()
374
-            );
375
-    }
376
-
377
-
378
-    /**
379
-     * Gets context
380
-     *
381
-     * @return string
382
-     */
383
-    public function context()
384
-    {
385
-        return $this->get('MSG_context');
386
-    }
387
-
388
-
389
-    /**
390
-     * This returns the corresponding localized label for the given context slug, if possible from installed message
391
-     * types. Otherwise, this will just return the set context slug on this object.
392
-     *
393
-     * @return string
394
-     */
395
-    public function context_label()
396
-    {
397
-        /** @type EE_Message_Resource_Manager $message_resource_manager */
398
-        $message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
399
-        $contexts = $message_resource_manager->get_all_contexts();
400
-        return isset($contexts[ $this->context() ]) ? $contexts[ $this->context() ] : $this->context();
401
-    }
402
-
403
-
404
-    /**
405
-     * Sets context
406
-     *
407
-     * @param string $context
408
-     */
409
-    public function set_context($context)
410
-    {
411
-        $this->set('MSG_context', $context);
412
-    }
413
-
414
-
415
-    /**
416
-     * Gets recipient_ID
417
-     *
418
-     * @return int
419
-     */
420
-    public function recipient_ID()
421
-    {
422
-        return $this->get('MSG_recipient_ID');
423
-    }
424
-
425
-
426
-    /**
427
-     * Sets recipient_ID
428
-     *
429
-     * @param string $recipient_ID
430
-     */
431
-    public function set_recipient_ID($recipient_ID)
432
-    {
433
-        $this->set('MSG_recipient_ID', $recipient_ID);
434
-    }
435
-
436
-
437
-    /**
438
-     * Gets recipient_type
439
-     *
440
-     * @return string
441
-     */
442
-    public function recipient_type()
443
-    {
444
-        return $this->get('MSG_recipient_type');
445
-    }
446
-
447
-
448
-    /**
449
-     * Return the related object matching the recipient type and ID.
450
-     *
451
-     * @return EE_Base_Class | null
452
-     */
453
-    public function recipient_object()
454
-    {
455
-        if (! $this->recipient_type() || ! $this->recipient_ID()) {
456
-            return null;
457
-        }
458
-
459
-        return $this->get_first_related($this->recipient_type());
460
-    }
461
-
462
-
463
-    /**
464
-     * Sets recipient_type
465
-     *
466
-     * @param string $recipient_type
467
-     */
468
-    public function set_recipient_type($recipient_type)
469
-    {
470
-        $this->set('MSG_recipient_type', $recipient_type);
471
-    }
472
-
473
-
474
-    /**
475
-     * Gets content
476
-     *
477
-     * @return string
478
-     */
479
-    public function content()
480
-    {
481
-        return $this->get('MSG_content');
482
-    }
483
-
484
-
485
-    /**
486
-     * Sets content
487
-     *
488
-     * @param string $content
489
-     */
490
-    public function set_content($content)
491
-    {
492
-        $this->set('MSG_content', $content);
493
-    }
494
-
495
-
496
-    /**
497
-     * Gets subject
498
-     *
499
-     * @return string
500
-     */
501
-    public function subject()
502
-    {
503
-        return $this->get('MSG_subject');
504
-    }
505
-
506
-
507
-    /**
508
-     * Sets subject
509
-     *
510
-     * @param string $subject
511
-     */
512
-    public function set_subject($subject)
513
-    {
514
-        $this->set('MSG_subject', $subject);
515
-    }
516
-
517
-
518
-    /**
519
-     * Gets to
520
-     *
521
-     * @return string
522
-     */
523
-    public function to()
524
-    {
525
-        $to = $this->get('MSG_to');
526
-        return empty($to) ? __('No recipient', 'event_espresso') : $to;
527
-    }
528
-
529
-
530
-    /**
531
-     * Sets to
532
-     *
533
-     * @param string $to
534
-     */
535
-    public function set_to($to)
536
-    {
537
-        $this->set('MSG_to', $to);
538
-    }
539
-
540
-
541
-    /**
542
-     * Gets from
543
-     *
544
-     * @return string
545
-     */
546
-    public function from()
547
-    {
548
-        return $this->get('MSG_from');
549
-    }
550
-
551
-
552
-    /**
553
-     * Sets from
554
-     *
555
-     * @param string $from
556
-     */
557
-    public function set_from($from)
558
-    {
559
-        $this->set('MSG_from', $from);
560
-    }
561
-
562
-
563
-    /**
564
-     * Gets priority
565
-     *
566
-     * @return int
567
-     */
568
-    public function priority()
569
-    {
570
-        return $this->get('MSG_priority');
571
-    }
572
-
573
-
574
-    /**
575
-     * Sets priority
576
-     * Note.  Send Now Messengers always override any priority that may be set on a Message.  So
577
-     * this method calls the send_now method to verify that.
578
-     *
579
-     * @param int $priority
580
-     */
581
-    public function set_priority($priority)
582
-    {
583
-        $priority = $this->send_now() ? EEM_Message::priority_high : $priority;
584
-        parent::set('MSG_priority', $priority);
585
-    }
586
-
587
-
588
-    /**
589
-     * Overrides parent::set method so we can capture any sets for priority.
590
-     *
591
-     * @param string $field_name
592
-     * @param mixed  $field_value
593
-     * @param bool   $use_default
594
-     * @throws EE_Error
595
-     * @see parent::set() for phpdocs
596
-     */
597
-    public function set($field_name, $field_value, bool $use_default = false)
598
-    {
599
-        if ($field_name === 'MSG_priority') {
600
-            $this->set_priority($field_value);
601
-        }
602
-        parent::set($field_name, $field_value, $use_default);
603
-    }
604
-
605
-
606
-    /**
607
-     * @return bool
608
-     * @throws \EE_Error
609
-     */
610
-    public function send_now()
611
-    {
612
-        $send_now = $this->valid_messenger() && $this->messenger_object()->send_now() ? EEM_Message::priority_high
613
-            : $this->priority();
614
-        return $send_now === EEM_Message::priority_high ? true : false;
615
-    }
616
-
617
-
618
-    /**
619
-     * Gets STS_ID
620
-     *
621
-     * @return string
622
-     */
623
-    public function STS_ID()
624
-    {
625
-        return $this->get('STS_ID');
626
-    }
627
-
628
-
629
-    /**
630
-     * Sets STS_ID
631
-     *
632
-     * @param string $STS_ID
633
-     */
634
-    public function set_STS_ID($STS_ID)
635
-    {
636
-        $this->set('STS_ID', $STS_ID);
637
-    }
638
-
639
-
640
-    /**
641
-     * Gets created
642
-     *
643
-     * @return string
644
-     */
645
-    public function created()
646
-    {
647
-        return $this->get('MSG_created');
648
-    }
649
-
650
-
651
-    /**
652
-     * Sets created
653
-     *
654
-     * @param string $created
655
-     */
656
-    public function set_created($created)
657
-    {
658
-        $this->set('MSG_created', $created);
659
-    }
660
-
661
-
662
-    /**
663
-     * Gets modified
664
-     *
665
-     * @return string
666
-     */
667
-    public function modified()
668
-    {
669
-        return $this->get('MSG_modified');
670
-    }
671
-
672
-
673
-    /**
674
-     * Sets modified
675
-     *
676
-     * @param string $modified
677
-     */
678
-    public function set_modified($modified)
679
-    {
680
-        $this->set('MSG_modified', $modified);
681
-    }
682
-
683
-
684
-    /**
685
-     * Sets generation data for this message.
686
-     *
687
-     * @param mixed $data
688
-     */
689
-    public function set_generation_data($data)
690
-    {
691
-        $this->set_field_or_extra_meta('MSG_generation_data', $data);
692
-    }
693
-
694
-
695
-    /**
696
-     * Returns any set generation data for this message.
697
-     *
698
-     * @return mixed|null
699
-     */
700
-    public function get_generation_data()
701
-    {
702
-        return $this->get_field_or_extra_meta('MSG_generation_data');
703
-    }
704
-
705
-
706
-    /**
707
-     * Gets any error message.
708
-     *
709
-     * @return mixed|null
710
-     */
711
-    public function error_message()
712
-    {
713
-        return $this->get_field_or_extra_meta('MSG_error');
714
-    }
715
-
716
-
717
-    /**
718
-     * Sets an error message.
719
-     *
720
-     * @param $message
721
-     * @return bool|int
722
-     */
723
-    public function set_error_message($message)
724
-    {
725
-        return $this->set_field_or_extra_meta('MSG_error', $message);
726
-    }
727
-
728
-
729
-    /**
730
-     * This retrieves the associated template pack with this message.
731
-     *
732
-     * @return EE_Messages_Template_Pack | null
733
-     */
734
-    public function get_template_pack()
735
-    {
736
-        /**
737
-         * This is deprecated functionality that will be removed eventually but included here now for backward compat.
738
-         */
739
-        if (! empty($this->template_pack)) {
740
-            return $this->template_pack;
741
-        }
742
-        /** @type EE_Message_Template_Group $grp */
743
-        $grp = $this->get_first_related('Message_Template_Group');
744
-        // if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
745
-        if (! $grp instanceof EE_Message_Template_Group) {
746
-            $grp = EEM_Message_Template_Group::instance()->get_one(
747
-                array(
748
-                    array(
749
-                        'MTP_messenger'    => $this->messenger(),
750
-                        'MTP_message_type' => $this->message_type(),
751
-                        'MTP_is_global'    => true,
752
-                    ),
753
-                )
754
-            );
755
-        }
756
-
757
-        return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack() : null;
758
-    }
759
-
760
-
761
-    /**
762
-     * Retrieves the variation used for generating this message.
763
-     *
764
-     * @return string
765
-     */
766
-    public function get_template_pack_variation()
767
-    {
768
-        /**
769
-         * This is deprecated functionality that will be removed eventually but included here now for backward compat.
770
-         */
771
-        if (! empty($this->template_variation)) {
772
-            return $this->template_variation;
773
-        }
774
-
775
-        /** @type EE_Message_Template_Group $grp */
776
-        $grp = $this->get_first_related('Message_Template_Group');
777
-
778
-        // if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
779
-        if (! $grp instanceof EE_Message_Template_Group) {
780
-            $grp = EEM_Message_Template_Group::instance()->get_one(
781
-                array(
782
-                    array(
783
-                        'MTP_messenger'    => $this->messenger(),
784
-                        'MTP_message_type' => $this->message_type(),
785
-                        'MTP_is_global'    => true,
786
-                    ),
787
-                )
788
-            );
789
-        }
790
-
791
-        return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack_variation() : '';
792
-    }
793
-
794
-    /**
795
-     * Return the link to the admin details for the object.
796
-     *
797
-     * @return string
798
-     */
799
-    public function get_admin_details_link()
800
-    {
801
-        EE_Registry::instance()->load_helper('URL');
802
-        EE_Registry::instance()->load_helper('MSG_Template');
803
-        switch ($this->STS_ID()) {
804
-            case EEM_Message::status_failed:
805
-            case EEM_Message::status_debug_only:
806
-                return EEH_MSG_Template::generate_error_display_trigger($this);
807
-                break;
808
-
809
-            case EEM_Message::status_sent:
810
-                return EEH_MSG_Template::generate_browser_trigger($this);
811
-                break;
812
-
813
-            default:
814
-                return '';
815
-        }
816
-    }
817
-
818
-    /**
819
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
820
-     *
821
-     * @return string
822
-     */
823
-    public function get_admin_edit_link()
824
-    {
825
-        return $this->get_admin_details_link();
826
-    }
827
-
828
-    /**
829
-     * Returns the link to a settings page for the object.
830
-     *
831
-     * @return string
832
-     */
833
-    public function get_admin_settings_link()
834
-    {
835
-        EE_Registry::instance()->load_helper('URL');
836
-        return EEH_URL::add_query_args_and_nonce(
837
-            array(
838
-                'page'   => 'espresso_messages',
839
-                'action' => 'settings',
840
-            ),
841
-            admin_url('admin.php')
842
-        );
843
-    }
844
-
845
-    /**
846
-     * Returns the link to the "overview" for the object (typically the "list table" view).
847
-     *
848
-     * @return string
849
-     */
850
-    public function get_admin_overview_link()
851
-    {
852
-        EE_Registry::instance()->load_helper('URL');
853
-        return EEH_URL::add_query_args_and_nonce(
854
-            array(
855
-                'page'   => 'espresso_messages',
856
-                'action' => 'default',
857
-            ),
858
-            admin_url('admin.php')
859
-        );
860
-    }
861
-
862
-
863
-    /**
864
-     * This sets the EEM_Message::status_messenger_executing class on the message and the appropriate error message for
865
-     * it.
866
-     * Note this also SAVES the current message object to the db because it adds an error message to accompany the
867
-     * status.
868
-     *
869
-     */
870
-    public function set_messenger_is_executing()
871
-    {
872
-        $this->set_STS_ID(EEM_Message::status_messenger_executing);
873
-        $this->set_error_message(
874
-            esc_html__(
875
-                'A message with this status indicates that there was a problem that occurred while the message was being
13
+	/**
14
+	 * @deprecated 4.9.0  Added for backward compat with add-on's
15
+	 * @type null
16
+	 */
17
+	public $template_pack;
18
+
19
+	/**
20
+	 * @deprecated 4.9.0 Added for backward compat with add-on's
21
+	 * @type null
22
+	 */
23
+	public $template_variation;
24
+
25
+	/**
26
+	 * @deprecated 4.9.0 Added for backward compat with add-on's
27
+	 * @type string
28
+	 */
29
+	public $content = '';
30
+
31
+
32
+	/**
33
+	 * @type EE_messenger $_messenger
34
+	 */
35
+	protected $_messenger = null;
36
+
37
+	/**
38
+	 * @type EE_message_type $_message_type
39
+	 */
40
+	protected $_message_type = null;
41
+
42
+
43
+	/**
44
+	 * @param array  $props_n_values
45
+	 * @param string $timezone
46
+	 * @param array  $date_formats incoming date formats in an array.  First value is the date_format, second is time
47
+	 *                             format.
48
+	 * @return EE_Message
49
+	 */
50
+	public static function new_instance($props_n_values = array(), $timezone = '', $date_formats = array())
51
+	{
52
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__);
53
+		// if object doesn't exist, let's generate a unique token on instantiation so that its available even before saving to db.
54
+		if (! $has_object) {
55
+			EE_Registry::instance()->load_helper('URL');
56
+			$props_n_values['MSG_token'] = EEH_URL::generate_unique_token();
57
+		}
58
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
59
+	}
60
+
61
+
62
+	/**
63
+	 * @param array  $props_n_values
64
+	 * @param string $timezone
65
+	 * @return EE_Message
66
+	 */
67
+	public static function new_instance_from_db($props_n_values = array(), $timezone = '')
68
+	{
69
+		return new self($props_n_values, true, $timezone);
70
+	}
71
+
72
+
73
+	/**
74
+	 * Gets MSG_token
75
+	 *
76
+	 * @return int
77
+	 */
78
+	public function MSG_token()
79
+	{
80
+		return $this->get('MSG_token');
81
+	}
82
+
83
+
84
+	/**
85
+	 * Sets MSG_token
86
+	 *
87
+	 * @param int $MSG_token
88
+	 */
89
+	public function set_MSG_token($MSG_token)
90
+	{
91
+		$this->set('MSG_token', $MSG_token);
92
+	}
93
+
94
+
95
+	/**
96
+	 * Gets GRP_ID
97
+	 *
98
+	 * @return int
99
+	 */
100
+	public function GRP_ID()
101
+	{
102
+		return $this->get('GRP_ID');
103
+	}
104
+
105
+
106
+	/**
107
+	 * Sets GRP_ID
108
+	 *
109
+	 * @param int $GRP_ID
110
+	 */
111
+	public function set_GRP_ID($GRP_ID)
112
+	{
113
+		$this->set('GRP_ID', $GRP_ID);
114
+	}
115
+
116
+
117
+	/**
118
+	 * Gets TXN_ID
119
+	 *
120
+	 * @return int
121
+	 */
122
+	public function TXN_ID()
123
+	{
124
+		return $this->get('TXN_ID');
125
+	}
126
+
127
+
128
+	/**
129
+	 * Sets TXN_ID
130
+	 *
131
+	 * @param int $TXN_ID
132
+	 */
133
+	public function set_TXN_ID($TXN_ID)
134
+	{
135
+		$this->set('TXN_ID', $TXN_ID);
136
+	}
137
+
138
+
139
+	/**
140
+	 * Gets messenger
141
+	 *
142
+	 * @return string
143
+	 */
144
+	public function messenger()
145
+	{
146
+		return $this->get('MSG_messenger');
147
+	}
148
+
149
+
150
+	/**
151
+	 * Sets messenger
152
+	 *
153
+	 * @param string $messenger
154
+	 */
155
+	public function set_messenger($messenger)
156
+	{
157
+		$this->set('MSG_messenger', $messenger);
158
+	}
159
+
160
+
161
+	/**
162
+	 * Returns corresponding messenger object for the set messenger on this message
163
+	 *
164
+	 * @return EE_messenger | null
165
+	 */
166
+	public function messenger_object()
167
+	{
168
+		return $this->_messenger;
169
+	}
170
+
171
+
172
+	/**
173
+	 * Sets messenger
174
+	 *
175
+	 * @param EE_messenger $messenger
176
+	 */
177
+	public function set_messenger_object(EE_messenger $messenger)
178
+	{
179
+		$this->_messenger = $messenger;
180
+	}
181
+
182
+
183
+	/**
184
+	 * validates messenger
185
+	 *
186
+	 * @param bool $throw_exceptions
187
+	 * @return bool
188
+	 * @throws \EE_Error
189
+	 */
190
+	public function valid_messenger($throw_exceptions = false)
191
+	{
192
+		if ($this->_messenger instanceof EE_messenger) {
193
+			return true;
194
+		}
195
+		if ($throw_exceptions) {
196
+			throw new EE_Error(
197
+				sprintf(
198
+					__(
199
+						'The "%1$s" messenger set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
200
+						'event_espresso'
201
+					),
202
+					$this->messenger()
203
+				)
204
+			);
205
+		}
206
+		return false;
207
+	}
208
+
209
+
210
+	/**
211
+	 * This returns the set localized label for the messenger on this message.
212
+	 * Note, if unable to retrieve the EE_messenger object then will just return the messenger slug saved
213
+	 * with this message.
214
+	 *
215
+	 * @param   bool $plural whether to return the plural label or not.
216
+	 * @return string
217
+	 */
218
+	public function messenger_label($plural = false)
219
+	{
220
+		$label_type = $plural ? 'plural' : 'singular';
221
+		$messenger = $this->messenger_object();
222
+		return $messenger instanceof EE_messenger ? $messenger->label[ $label_type ] : $this->messenger();
223
+	}
224
+
225
+
226
+	/**
227
+	 * Gets message_type
228
+	 *
229
+	 * @return string
230
+	 */
231
+	public function message_type()
232
+	{
233
+		return $this->get('MSG_message_type');
234
+	}
235
+
236
+
237
+	/**
238
+	 * Sets message_type
239
+	 *
240
+	 * @param string $message_type
241
+	 */
242
+	public function set_message_type($message_type)
243
+	{
244
+		$this->set('MSG_message_type', $message_type);
245
+	}
246
+
247
+
248
+	/**
249
+	 * Returns the message type object for the set message type on this message
250
+	 *
251
+	 * @return EE_message_type | null
252
+	 */
253
+	public function message_type_object()
254
+	{
255
+		return $this->_message_type;
256
+	}
257
+
258
+
259
+	/**
260
+	 * Sets message_type
261
+	 *
262
+	 * @param EE_message_type $message_type
263
+	 * @param bool            $set_priority   This indicates whether to set the priority to whatever the priority is on
264
+	 *                                        the message type or not.
265
+	 */
266
+	public function set_message_type_object(EE_message_type $message_type, $set_priority = false)
267
+	{
268
+		$this->_message_type = $message_type;
269
+		if ($set_priority) {
270
+			$this->set_priority($this->_message_type->get_priority());
271
+		}
272
+	}
273
+
274
+
275
+	/**
276
+	 * validates message_type
277
+	 *
278
+	 * @param bool $throw_exceptions
279
+	 * @return bool
280
+	 * @throws \EE_Error
281
+	 */
282
+	public function valid_message_type($throw_exceptions = false)
283
+	{
284
+		if ($this->_message_type instanceof EE_message_type) {
285
+			return true;
286
+		}
287
+		if ($throw_exceptions) {
288
+			throw new EE_Error(
289
+				sprintf(
290
+					__(
291
+						'The %1$s message type set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
292
+						'event_espresso'
293
+					),
294
+					$this->message_type()
295
+				)
296
+			);
297
+		}
298
+		return false;
299
+	}
300
+
301
+
302
+	/**
303
+	 * validates messenger and message_type (that they are valid EE_messenger and EE_message_type objects).
304
+	 *
305
+	 * @param bool $throw_exceptions
306
+	 * @return bool
307
+	 * @throws \EE_Error
308
+	 */
309
+	public function is_valid($throw_exceptions = false)
310
+	{
311
+		if ($this->valid_messenger($throw_exceptions) && $this->valid_message_type($throw_exceptions)) {
312
+			return true;
313
+		}
314
+		return false;
315
+	}
316
+
317
+
318
+	/**
319
+	 * This validates whether the internal messenger and message type objects are valid for sending.
320
+	 * Three checks are done:
321
+	 * 1. There is a valid messenger object.
322
+	 * 2. There is a valid message type object.
323
+	 * 3. The message type object is active for the messenger.
324
+	 *
325
+	 * @throws EE_Error  But only if $throw_exceptions is set to true.
326
+	 * @param bool $throw_exceptions
327
+	 * @return bool
328
+	 */
329
+	public function is_valid_for_sending_or_generation($throw_exceptions = false)
330
+	{
331
+		$valid = false;
332
+		if ($this->is_valid($throw_exceptions)) {
333
+			/** @var EE_Message_Resource_Manager $message_resource_manager */
334
+			$message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
335
+			$valid = $message_resource_manager->is_message_type_active_for_messenger(
336
+				$this->messenger(),
337
+				$this->message_type()
338
+			);
339
+			if (! $valid && $throw_exceptions) {
340
+				throw new EE_Error(
341
+					sprintf(
342
+						__(
343
+							'The %1$s message type is not a valid message type for the %2$s messenger so it will not be sent.',
344
+							'event_espresso'
345
+						),
346
+						$this->message_type(),
347
+						$this->messenger()
348
+					)
349
+				);
350
+			}
351
+		}
352
+		return $valid;
353
+	}
354
+
355
+
356
+	/**
357
+	 * This returns the set localized label for the message type on this message.
358
+	 * Note, if unable to retrieve the EE_message_type object then will just return the message type slug saved
359
+	 * with this message.
360
+	 *
361
+	 * @param   bool $plural whether to return the plural label or not.
362
+	 * @return string
363
+	 */
364
+	public function message_type_label($plural = false)
365
+	{
366
+		$label_type = $plural ? 'plural' : 'singular';
367
+		$message_type = $this->message_type_object();
368
+		return $message_type instanceof EE_message_type
369
+			? $message_type->label[ $label_type ]
370
+			: str_replace(
371
+				'_',
372
+				' ',
373
+				$this->message_type()
374
+			);
375
+	}
376
+
377
+
378
+	/**
379
+	 * Gets context
380
+	 *
381
+	 * @return string
382
+	 */
383
+	public function context()
384
+	{
385
+		return $this->get('MSG_context');
386
+	}
387
+
388
+
389
+	/**
390
+	 * This returns the corresponding localized label for the given context slug, if possible from installed message
391
+	 * types. Otherwise, this will just return the set context slug on this object.
392
+	 *
393
+	 * @return string
394
+	 */
395
+	public function context_label()
396
+	{
397
+		/** @type EE_Message_Resource_Manager $message_resource_manager */
398
+		$message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
399
+		$contexts = $message_resource_manager->get_all_contexts();
400
+		return isset($contexts[ $this->context() ]) ? $contexts[ $this->context() ] : $this->context();
401
+	}
402
+
403
+
404
+	/**
405
+	 * Sets context
406
+	 *
407
+	 * @param string $context
408
+	 */
409
+	public function set_context($context)
410
+	{
411
+		$this->set('MSG_context', $context);
412
+	}
413
+
414
+
415
+	/**
416
+	 * Gets recipient_ID
417
+	 *
418
+	 * @return int
419
+	 */
420
+	public function recipient_ID()
421
+	{
422
+		return $this->get('MSG_recipient_ID');
423
+	}
424
+
425
+
426
+	/**
427
+	 * Sets recipient_ID
428
+	 *
429
+	 * @param string $recipient_ID
430
+	 */
431
+	public function set_recipient_ID($recipient_ID)
432
+	{
433
+		$this->set('MSG_recipient_ID', $recipient_ID);
434
+	}
435
+
436
+
437
+	/**
438
+	 * Gets recipient_type
439
+	 *
440
+	 * @return string
441
+	 */
442
+	public function recipient_type()
443
+	{
444
+		return $this->get('MSG_recipient_type');
445
+	}
446
+
447
+
448
+	/**
449
+	 * Return the related object matching the recipient type and ID.
450
+	 *
451
+	 * @return EE_Base_Class | null
452
+	 */
453
+	public function recipient_object()
454
+	{
455
+		if (! $this->recipient_type() || ! $this->recipient_ID()) {
456
+			return null;
457
+		}
458
+
459
+		return $this->get_first_related($this->recipient_type());
460
+	}
461
+
462
+
463
+	/**
464
+	 * Sets recipient_type
465
+	 *
466
+	 * @param string $recipient_type
467
+	 */
468
+	public function set_recipient_type($recipient_type)
469
+	{
470
+		$this->set('MSG_recipient_type', $recipient_type);
471
+	}
472
+
473
+
474
+	/**
475
+	 * Gets content
476
+	 *
477
+	 * @return string
478
+	 */
479
+	public function content()
480
+	{
481
+		return $this->get('MSG_content');
482
+	}
483
+
484
+
485
+	/**
486
+	 * Sets content
487
+	 *
488
+	 * @param string $content
489
+	 */
490
+	public function set_content($content)
491
+	{
492
+		$this->set('MSG_content', $content);
493
+	}
494
+
495
+
496
+	/**
497
+	 * Gets subject
498
+	 *
499
+	 * @return string
500
+	 */
501
+	public function subject()
502
+	{
503
+		return $this->get('MSG_subject');
504
+	}
505
+
506
+
507
+	/**
508
+	 * Sets subject
509
+	 *
510
+	 * @param string $subject
511
+	 */
512
+	public function set_subject($subject)
513
+	{
514
+		$this->set('MSG_subject', $subject);
515
+	}
516
+
517
+
518
+	/**
519
+	 * Gets to
520
+	 *
521
+	 * @return string
522
+	 */
523
+	public function to()
524
+	{
525
+		$to = $this->get('MSG_to');
526
+		return empty($to) ? __('No recipient', 'event_espresso') : $to;
527
+	}
528
+
529
+
530
+	/**
531
+	 * Sets to
532
+	 *
533
+	 * @param string $to
534
+	 */
535
+	public function set_to($to)
536
+	{
537
+		$this->set('MSG_to', $to);
538
+	}
539
+
540
+
541
+	/**
542
+	 * Gets from
543
+	 *
544
+	 * @return string
545
+	 */
546
+	public function from()
547
+	{
548
+		return $this->get('MSG_from');
549
+	}
550
+
551
+
552
+	/**
553
+	 * Sets from
554
+	 *
555
+	 * @param string $from
556
+	 */
557
+	public function set_from($from)
558
+	{
559
+		$this->set('MSG_from', $from);
560
+	}
561
+
562
+
563
+	/**
564
+	 * Gets priority
565
+	 *
566
+	 * @return int
567
+	 */
568
+	public function priority()
569
+	{
570
+		return $this->get('MSG_priority');
571
+	}
572
+
573
+
574
+	/**
575
+	 * Sets priority
576
+	 * Note.  Send Now Messengers always override any priority that may be set on a Message.  So
577
+	 * this method calls the send_now method to verify that.
578
+	 *
579
+	 * @param int $priority
580
+	 */
581
+	public function set_priority($priority)
582
+	{
583
+		$priority = $this->send_now() ? EEM_Message::priority_high : $priority;
584
+		parent::set('MSG_priority', $priority);
585
+	}
586
+
587
+
588
+	/**
589
+	 * Overrides parent::set method so we can capture any sets for priority.
590
+	 *
591
+	 * @param string $field_name
592
+	 * @param mixed  $field_value
593
+	 * @param bool   $use_default
594
+	 * @throws EE_Error
595
+	 * @see parent::set() for phpdocs
596
+	 */
597
+	public function set($field_name, $field_value, bool $use_default = false)
598
+	{
599
+		if ($field_name === 'MSG_priority') {
600
+			$this->set_priority($field_value);
601
+		}
602
+		parent::set($field_name, $field_value, $use_default);
603
+	}
604
+
605
+
606
+	/**
607
+	 * @return bool
608
+	 * @throws \EE_Error
609
+	 */
610
+	public function send_now()
611
+	{
612
+		$send_now = $this->valid_messenger() && $this->messenger_object()->send_now() ? EEM_Message::priority_high
613
+			: $this->priority();
614
+		return $send_now === EEM_Message::priority_high ? true : false;
615
+	}
616
+
617
+
618
+	/**
619
+	 * Gets STS_ID
620
+	 *
621
+	 * @return string
622
+	 */
623
+	public function STS_ID()
624
+	{
625
+		return $this->get('STS_ID');
626
+	}
627
+
628
+
629
+	/**
630
+	 * Sets STS_ID
631
+	 *
632
+	 * @param string $STS_ID
633
+	 */
634
+	public function set_STS_ID($STS_ID)
635
+	{
636
+		$this->set('STS_ID', $STS_ID);
637
+	}
638
+
639
+
640
+	/**
641
+	 * Gets created
642
+	 *
643
+	 * @return string
644
+	 */
645
+	public function created()
646
+	{
647
+		return $this->get('MSG_created');
648
+	}
649
+
650
+
651
+	/**
652
+	 * Sets created
653
+	 *
654
+	 * @param string $created
655
+	 */
656
+	public function set_created($created)
657
+	{
658
+		$this->set('MSG_created', $created);
659
+	}
660
+
661
+
662
+	/**
663
+	 * Gets modified
664
+	 *
665
+	 * @return string
666
+	 */
667
+	public function modified()
668
+	{
669
+		return $this->get('MSG_modified');
670
+	}
671
+
672
+
673
+	/**
674
+	 * Sets modified
675
+	 *
676
+	 * @param string $modified
677
+	 */
678
+	public function set_modified($modified)
679
+	{
680
+		$this->set('MSG_modified', $modified);
681
+	}
682
+
683
+
684
+	/**
685
+	 * Sets generation data for this message.
686
+	 *
687
+	 * @param mixed $data
688
+	 */
689
+	public function set_generation_data($data)
690
+	{
691
+		$this->set_field_or_extra_meta('MSG_generation_data', $data);
692
+	}
693
+
694
+
695
+	/**
696
+	 * Returns any set generation data for this message.
697
+	 *
698
+	 * @return mixed|null
699
+	 */
700
+	public function get_generation_data()
701
+	{
702
+		return $this->get_field_or_extra_meta('MSG_generation_data');
703
+	}
704
+
705
+
706
+	/**
707
+	 * Gets any error message.
708
+	 *
709
+	 * @return mixed|null
710
+	 */
711
+	public function error_message()
712
+	{
713
+		return $this->get_field_or_extra_meta('MSG_error');
714
+	}
715
+
716
+
717
+	/**
718
+	 * Sets an error message.
719
+	 *
720
+	 * @param $message
721
+	 * @return bool|int
722
+	 */
723
+	public function set_error_message($message)
724
+	{
725
+		return $this->set_field_or_extra_meta('MSG_error', $message);
726
+	}
727
+
728
+
729
+	/**
730
+	 * This retrieves the associated template pack with this message.
731
+	 *
732
+	 * @return EE_Messages_Template_Pack | null
733
+	 */
734
+	public function get_template_pack()
735
+	{
736
+		/**
737
+		 * This is deprecated functionality that will be removed eventually but included here now for backward compat.
738
+		 */
739
+		if (! empty($this->template_pack)) {
740
+			return $this->template_pack;
741
+		}
742
+		/** @type EE_Message_Template_Group $grp */
743
+		$grp = $this->get_first_related('Message_Template_Group');
744
+		// if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
745
+		if (! $grp instanceof EE_Message_Template_Group) {
746
+			$grp = EEM_Message_Template_Group::instance()->get_one(
747
+				array(
748
+					array(
749
+						'MTP_messenger'    => $this->messenger(),
750
+						'MTP_message_type' => $this->message_type(),
751
+						'MTP_is_global'    => true,
752
+					),
753
+				)
754
+			);
755
+		}
756
+
757
+		return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack() : null;
758
+	}
759
+
760
+
761
+	/**
762
+	 * Retrieves the variation used for generating this message.
763
+	 *
764
+	 * @return string
765
+	 */
766
+	public function get_template_pack_variation()
767
+	{
768
+		/**
769
+		 * This is deprecated functionality that will be removed eventually but included here now for backward compat.
770
+		 */
771
+		if (! empty($this->template_variation)) {
772
+			return $this->template_variation;
773
+		}
774
+
775
+		/** @type EE_Message_Template_Group $grp */
776
+		$grp = $this->get_first_related('Message_Template_Group');
777
+
778
+		// if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
779
+		if (! $grp instanceof EE_Message_Template_Group) {
780
+			$grp = EEM_Message_Template_Group::instance()->get_one(
781
+				array(
782
+					array(
783
+						'MTP_messenger'    => $this->messenger(),
784
+						'MTP_message_type' => $this->message_type(),
785
+						'MTP_is_global'    => true,
786
+					),
787
+				)
788
+			);
789
+		}
790
+
791
+		return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack_variation() : '';
792
+	}
793
+
794
+	/**
795
+	 * Return the link to the admin details for the object.
796
+	 *
797
+	 * @return string
798
+	 */
799
+	public function get_admin_details_link()
800
+	{
801
+		EE_Registry::instance()->load_helper('URL');
802
+		EE_Registry::instance()->load_helper('MSG_Template');
803
+		switch ($this->STS_ID()) {
804
+			case EEM_Message::status_failed:
805
+			case EEM_Message::status_debug_only:
806
+				return EEH_MSG_Template::generate_error_display_trigger($this);
807
+				break;
808
+
809
+			case EEM_Message::status_sent:
810
+				return EEH_MSG_Template::generate_browser_trigger($this);
811
+				break;
812
+
813
+			default:
814
+				return '';
815
+		}
816
+	}
817
+
818
+	/**
819
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
820
+	 *
821
+	 * @return string
822
+	 */
823
+	public function get_admin_edit_link()
824
+	{
825
+		return $this->get_admin_details_link();
826
+	}
827
+
828
+	/**
829
+	 * Returns the link to a settings page for the object.
830
+	 *
831
+	 * @return string
832
+	 */
833
+	public function get_admin_settings_link()
834
+	{
835
+		EE_Registry::instance()->load_helper('URL');
836
+		return EEH_URL::add_query_args_and_nonce(
837
+			array(
838
+				'page'   => 'espresso_messages',
839
+				'action' => 'settings',
840
+			),
841
+			admin_url('admin.php')
842
+		);
843
+	}
844
+
845
+	/**
846
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
847
+	 *
848
+	 * @return string
849
+	 */
850
+	public function get_admin_overview_link()
851
+	{
852
+		EE_Registry::instance()->load_helper('URL');
853
+		return EEH_URL::add_query_args_and_nonce(
854
+			array(
855
+				'page'   => 'espresso_messages',
856
+				'action' => 'default',
857
+			),
858
+			admin_url('admin.php')
859
+		);
860
+	}
861
+
862
+
863
+	/**
864
+	 * This sets the EEM_Message::status_messenger_executing class on the message and the appropriate error message for
865
+	 * it.
866
+	 * Note this also SAVES the current message object to the db because it adds an error message to accompany the
867
+	 * status.
868
+	 *
869
+	 */
870
+	public function set_messenger_is_executing()
871
+	{
872
+		$this->set_STS_ID(EEM_Message::status_messenger_executing);
873
+		$this->set_error_message(
874
+			esc_html__(
875
+				'A message with this status indicates that there was a problem that occurred while the message was being
876 876
                 processed by the messenger.  It is still possible that the message was sent successfully, but at some
877 877
                 point during the processing there was a failure.  This usually is indicative of a timeout issue with PHP 
878 878
                 or memory limits being reached.  If you see this repeatedly you may want to consider upgrading the memory 
879 879
                 available to PHP on your server.',
880
-                'event_espresso'
881
-            )
882
-        );
883
-    }
880
+				'event_espresso'
881
+			)
882
+		);
883
+	}
884 884
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Registration.class.php 1 patch
Indentation   +2113 added lines, -2113 removed lines patch added patch discarded remove patch
@@ -17,2117 +17,2117 @@
 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 = '', $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 = '')
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, bool $use_default = false)
119
-    {
120
-        switch ($field_name) {
121
-            case 'REG_code':
122
-                if (! empty($field_value) && $this->reg_code() === null) {
123
-                    $this->set_reg_code($field_value, $use_default);
124
-                }
125
-                break;
126
-            case 'STS_ID':
127
-                $this->set_status($field_value, $use_default);
128
-                break;
129
-            default:
130
-                parent::set($field_name, $field_value, $use_default);
131
-        }
132
-    }
133
-
134
-
135
-    /**
136
-     * Set Status ID
137
-     * updates the registration status and ALSO...
138
-     * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
139
-     * calls release_registration_space() if the reg status changes FROM approved to any other reg status
140
-     *
141
-     * @param string                $new_STS_ID
142
-     * @param boolean               $use_default
143
-     * @param ContextInterface|null $context
144
-     * @return bool
145
-     * @throws DomainException
146
-     * @throws EE_Error
147
-     * @throws EntityNotFoundException
148
-     * @throws InvalidArgumentException
149
-     * @throws InvalidDataTypeException
150
-     * @throws InvalidInterfaceException
151
-     * @throws ReflectionException
152
-     * @throws RuntimeException
153
-     * @throws UnexpectedEntityException
154
-     */
155
-    public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
156
-    {
157
-        // get current REG_Status
158
-        $old_STS_ID = $this->status_ID();
159
-        // if status has changed
160
-        if (
161
-            $old_STS_ID !== $new_STS_ID // and that status has actually changed
162
-            && ! empty($old_STS_ID) // and that old status is actually set
163
-            && ! empty($new_STS_ID) // as well as the new status
164
-            && $this->ID() // ensure registration is in the db
165
-        ) {
166
-            // update internal status first
167
-            parent::set('STS_ID', $new_STS_ID, $use_default);
168
-            // THEN handle other changes that occur when reg status changes
169
-            // TO approved
170
-            if ($new_STS_ID === EEM_Registration::status_id_approved) {
171
-                // reserve a space by incrementing ticket and datetime sold values
172
-                $this->reserveRegistrationSpace();
173
-                do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
174
-                // OR FROM  approved
175
-            } elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
176
-                // release a space by decrementing ticket and datetime sold values
177
-                $this->releaseRegistrationSpace();
178
-                do_action(
179
-                    'AHEE__EE_Registration__set_status__from_approved',
180
-                    $this,
181
-                    $old_STS_ID,
182
-                    $new_STS_ID,
183
-                    $context
184
-                );
185
-            }
186
-            // update status
187
-            parent::set('STS_ID', $new_STS_ID, $use_default);
188
-            $this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
189
-            if ($this->statusChangeUpdatesTransaction($context)) {
190
-                $this->updateTransactionAfterStatusChange();
191
-            }
192
-            do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
193
-            return true;
194
-        }
195
-        // even though the old value matches the new value, it's still good to
196
-        // allow the parent set method to have a say
197
-        parent::set('STS_ID', $new_STS_ID, $use_default);
198
-        return true;
199
-    }
200
-
201
-
202
-    /**
203
-     * update REGs and TXN when cancelled or declined registrations involved
204
-     *
205
-     * @param string                $new_STS_ID
206
-     * @param string                $old_STS_ID
207
-     * @param ContextInterface|null $context
208
-     * @throws EE_Error
209
-     * @throws InvalidArgumentException
210
-     * @throws InvalidDataTypeException
211
-     * @throws InvalidInterfaceException
212
-     * @throws ReflectionException
213
-     * @throws RuntimeException
214
-     */
215
-    private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
216
-    {
217
-        // these reg statuses should not be considered in any calculations involving monies owing
218
-        $closed_reg_statuses = EEM_Registration::closed_reg_statuses();
219
-        // true if registration has been cancelled or declined
220
-        $this->updateIfCanceled(
221
-            $closed_reg_statuses,
222
-            $new_STS_ID,
223
-            $old_STS_ID,
224
-            $context
225
-        );
226
-        $this->updateIfReinstated(
227
-            $closed_reg_statuses,
228
-            $new_STS_ID,
229
-            $old_STS_ID,
230
-            $context
231
-        );
232
-    }
233
-
234
-
235
-    /**
236
-     * update REGs and TXN when cancelled or declined registrations involved
237
-     *
238
-     * @param array                 $closed_reg_statuses
239
-     * @param string                $new_STS_ID
240
-     * @param string                $old_STS_ID
241
-     * @param ContextInterface|null $context
242
-     * @throws EE_Error
243
-     * @throws InvalidArgumentException
244
-     * @throws InvalidDataTypeException
245
-     * @throws InvalidInterfaceException
246
-     * @throws ReflectionException
247
-     * @throws RuntimeException
248
-     */
249
-    private function updateIfCanceled(
250
-        array $closed_reg_statuses,
251
-        $new_STS_ID,
252
-        $old_STS_ID,
253
-        ContextInterface $context = null
254
-    ) {
255
-        // true if registration has been cancelled or declined
256
-        if (
257
-            in_array($new_STS_ID, $closed_reg_statuses, true)
258
-            && ! in_array($old_STS_ID, $closed_reg_statuses, true)
259
-        ) {
260
-            /** @type EE_Registration_Processor $registration_processor */
261
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
262
-            /** @type EE_Transaction_Processor $transaction_processor */
263
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
264
-            // cancelled or declined registration
265
-            $registration_processor->update_registration_after_being_canceled_or_declined(
266
-                $this,
267
-                $closed_reg_statuses
268
-            );
269
-            $transaction_processor->update_transaction_after_canceled_or_declined_registration(
270
-                $this,
271
-                $closed_reg_statuses,
272
-                false
273
-            );
274
-            do_action(
275
-                'AHEE__EE_Registration__set_status__canceled_or_declined',
276
-                $this,
277
-                $old_STS_ID,
278
-                $new_STS_ID,
279
-                $context
280
-            );
281
-            return;
282
-        }
283
-    }
284
-
285
-
286
-    /**
287
-     * update REGs and TXN when cancelled or declined registrations involved
288
-     *
289
-     * @param array                 $closed_reg_statuses
290
-     * @param string                $new_STS_ID
291
-     * @param string                $old_STS_ID
292
-     * @param ContextInterface|null $context
293
-     * @throws EE_Error
294
-     * @throws InvalidArgumentException
295
-     * @throws InvalidDataTypeException
296
-     * @throws InvalidInterfaceException
297
-     * @throws ReflectionException
298
-     */
299
-    private function updateIfReinstated(
300
-        array $closed_reg_statuses,
301
-        $new_STS_ID,
302
-        $old_STS_ID,
303
-        ContextInterface $context = null
304
-    ) {
305
-        // true if reinstating cancelled or declined registration
306
-        if (
307
-            in_array($old_STS_ID, $closed_reg_statuses, true)
308
-            && ! in_array($new_STS_ID, $closed_reg_statuses, true)
309
-        ) {
310
-            /** @type EE_Registration_Processor $registration_processor */
311
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
312
-            /** @type EE_Transaction_Processor $transaction_processor */
313
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
314
-            // reinstating cancelled or declined registration
315
-            $registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
316
-                $this,
317
-                $closed_reg_statuses
318
-            );
319
-            $transaction_processor->update_transaction_after_reinstating_canceled_registration(
320
-                $this,
321
-                $closed_reg_statuses,
322
-                false
323
-            );
324
-            do_action(
325
-                'AHEE__EE_Registration__set_status__after_reinstated',
326
-                $this,
327
-                $old_STS_ID,
328
-                $new_STS_ID,
329
-                $context
330
-            );
331
-        }
332
-    }
333
-
334
-
335
-    /**
336
-     * @param ContextInterface|null $context
337
-     * @return bool
338
-     */
339
-    private function statusChangeUpdatesTransaction(ContextInterface $context = null)
340
-    {
341
-        $contexts_that_do_not_update_transaction = (array) apply_filters(
342
-            'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
343
-            array('spco_reg_step_attendee_information_process_registrations'),
344
-            $context,
345
-            $this
346
-        );
347
-        return ! (
348
-            $context instanceof ContextInterface
349
-            && in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
350
-        );
351
-    }
352
-
353
-
354
-    /**
355
-     * @throws EE_Error
356
-     * @throws EntityNotFoundException
357
-     * @throws InvalidArgumentException
358
-     * @throws InvalidDataTypeException
359
-     * @throws InvalidInterfaceException
360
-     * @throws ReflectionException
361
-     * @throws RuntimeException
362
-     */
363
-    private function updateTransactionAfterStatusChange()
364
-    {
365
-        /** @type EE_Transaction_Payments $transaction_payments */
366
-        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
367
-        $transaction_payments->recalculate_transaction_total($this->transaction(), false);
368
-        $this->transaction()->update_status_based_on_total_paid(true);
369
-    }
370
-
371
-
372
-    /**
373
-     *        get Status ID
374
-     */
375
-    public function status_ID()
376
-    {
377
-        return $this->get('STS_ID');
378
-    }
379
-
380
-
381
-    /**
382
-     * Gets the ticket this registration is for
383
-     *
384
-     * @param boolean $include_archived whether to include archived tickets or not.
385
-     *
386
-     * @return EE_Ticket|EE_Base_Class
387
-     * @throws EE_Error
388
-     */
389
-    public function ticket($include_archived = true)
390
-    {
391
-        $query_params = array();
392
-        if ($include_archived) {
393
-            $query_params['default_where_conditions'] = 'none';
394
-        }
395
-        return $this->get_first_related('Ticket', $query_params);
396
-    }
397
-
398
-
399
-    /**
400
-     * Gets the event this registration is for
401
-     *
402
-     * @return EE_Event
403
-     * @throws EE_Error
404
-     * @throws EntityNotFoundException
405
-     */
406
-    public function event()
407
-    {
408
-        $event = $this->get_first_related('Event');
409
-        if (! $event instanceof \EE_Event) {
410
-            throw new EntityNotFoundException('Event ID', $this->event_ID());
411
-        }
412
-        return $event;
413
-    }
414
-
415
-
416
-    /**
417
-     * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
418
-     * with the author of the event this registration is for.
419
-     *
420
-     * @since 4.5.0
421
-     * @return int
422
-     * @throws EE_Error
423
-     * @throws EntityNotFoundException
424
-     */
425
-    public function wp_user()
426
-    {
427
-        $event = $this->event();
428
-        if ($event instanceof EE_Event) {
429
-            return $event->wp_user();
430
-        }
431
-        return 0;
432
-    }
433
-
434
-
435
-    /**
436
-     * increments this registration's related ticket sold and corresponding datetime sold values
437
-     *
438
-     * @return void
439
-     * @throws DomainException
440
-     * @throws EE_Error
441
-     * @throws EntityNotFoundException
442
-     * @throws InvalidArgumentException
443
-     * @throws InvalidDataTypeException
444
-     * @throws InvalidInterfaceException
445
-     * @throws ReflectionException
446
-     * @throws UnexpectedEntityException
447
-     */
448
-    private function reserveRegistrationSpace()
449
-    {
450
-        // reserved ticket and datetime counts will be decremented as sold counts are incremented
451
-        // so stop tracking that this reg has a ticket reserved
452
-        $this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
453
-        $ticket = $this->ticket();
454
-        $ticket->increaseSold();
455
-        // possibly set event status to sold out
456
-        $this->event()->perform_sold_out_status_check();
457
-    }
458
-
459
-
460
-    /**
461
-     * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
462
-     *
463
-     * @return void
464
-     * @throws DomainException
465
-     * @throws EE_Error
466
-     * @throws EntityNotFoundException
467
-     * @throws InvalidArgumentException
468
-     * @throws InvalidDataTypeException
469
-     * @throws InvalidInterfaceException
470
-     * @throws ReflectionException
471
-     * @throws UnexpectedEntityException
472
-     */
473
-    private function releaseRegistrationSpace()
474
-    {
475
-        $ticket = $this->ticket();
476
-        $ticket->decreaseSold();
477
-        // possibly change event status from sold out back to previous status
478
-        $this->event()->perform_sold_out_status_check();
479
-    }
480
-
481
-
482
-    /**
483
-     * tracks this registration's ticket reservation in extra meta
484
-     * and can increment related ticket reserved and corresponding datetime reserved values
485
-     *
486
-     * @param bool $update_ticket if true, will increment ticket and datetime reserved count
487
-     * @return void
488
-     * @throws EE_Error
489
-     * @throws InvalidArgumentException
490
-     * @throws InvalidDataTypeException
491
-     * @throws InvalidInterfaceException
492
-     * @throws ReflectionException
493
-     */
494
-    public function reserve_ticket($update_ticket = false, $source = 'unknown')
495
-    {
496
-        // only reserve ticket if space is not currently reserved
497
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
498
-            $this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
499
-            // IMPORTANT !!!
500
-            // although checking $update_ticket first would be more efficient,
501
-            // we NEED to ALWAYS call update_extra_meta(), which is why that is done first
502
-            if (
503
-                $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true)
504
-                && $update_ticket
505
-            ) {
506
-                $ticket = $this->ticket();
507
-                $ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
508
-                $ticket->save();
509
-            }
510
-        }
511
-    }
512
-
513
-
514
-    /**
515
-     * stops tracking this registration's ticket reservation in extra meta
516
-     * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
517
-     *
518
-     * @param bool $update_ticket if true, will decrement ticket and datetime reserved count
519
-     * @return void
520
-     * @throws EE_Error
521
-     * @throws InvalidArgumentException
522
-     * @throws InvalidDataTypeException
523
-     * @throws InvalidInterfaceException
524
-     * @throws ReflectionException
525
-     */
526
-    public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
527
-    {
528
-        // only release ticket if space is currently reserved
529
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
530
-            $this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
531
-            // IMPORTANT !!!
532
-            // although checking $update_ticket first would be more efficient,
533
-            // we NEED to ALWAYS call update_extra_meta(), which is why that is done first
534
-            if (
535
-                $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false)
536
-                && $update_ticket
537
-            ) {
538
-                $ticket = $this->ticket();
539
-                $ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
540
-            }
541
-        }
542
-    }
543
-
544
-
545
-    /**
546
-     * Set Attendee ID
547
-     *
548
-     * @param        int $ATT_ID Attendee ID
549
-     * @throws EE_Error
550
-     * @throws RuntimeException
551
-     */
552
-    public function set_attendee_id($ATT_ID = 0)
553
-    {
554
-        $this->set('ATT_ID', $ATT_ID);
555
-    }
556
-
557
-
558
-    /**
559
-     *        Set Transaction ID
560
-     *
561
-     * @param        int $TXN_ID Transaction ID
562
-     * @throws EE_Error
563
-     * @throws RuntimeException
564
-     */
565
-    public function set_transaction_id($TXN_ID = 0)
566
-    {
567
-        $this->set('TXN_ID', $TXN_ID);
568
-    }
569
-
570
-
571
-    /**
572
-     *        Set Session
573
-     *
574
-     * @param    string $REG_session PHP Session ID
575
-     * @throws EE_Error
576
-     * @throws RuntimeException
577
-     */
578
-    public function set_session($REG_session = '')
579
-    {
580
-        $this->set('REG_session', $REG_session);
581
-    }
582
-
583
-
584
-    /**
585
-     *        Set Registration URL Link
586
-     *
587
-     * @param    string $REG_url_link Registration URL Link
588
-     * @throws EE_Error
589
-     * @throws RuntimeException
590
-     */
591
-    public function set_reg_url_link($REG_url_link = '')
592
-    {
593
-        $this->set('REG_url_link', $REG_url_link);
594
-    }
595
-
596
-
597
-    /**
598
-     *        Set Attendee Counter
599
-     *
600
-     * @param        int $REG_count Primary Attendee
601
-     * @throws EE_Error
602
-     * @throws RuntimeException
603
-     */
604
-    public function set_count($REG_count = 1)
605
-    {
606
-        $this->set('REG_count', $REG_count);
607
-    }
608
-
609
-
610
-    /**
611
-     *        Set Group Size
612
-     *
613
-     * @param        boolean $REG_group_size Group Registration
614
-     * @throws EE_Error
615
-     * @throws RuntimeException
616
-     */
617
-    public function set_group_size($REG_group_size = false)
618
-    {
619
-        $this->set('REG_group_size', $REG_group_size);
620
-    }
621
-
622
-
623
-    /**
624
-     *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
625
-     *    EEM_Registration::status_id_not_approved
626
-     *
627
-     * @return        boolean
628
-     */
629
-    public function is_not_approved()
630
-    {
631
-        return $this->status_ID() == EEM_Registration::status_id_not_approved ? true : false;
632
-    }
633
-
634
-
635
-    /**
636
-     *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
637
-     *    EEM_Registration::status_id_pending_payment
638
-     *
639
-     * @return        boolean
640
-     */
641
-    public function is_pending_payment()
642
-    {
643
-        return $this->status_ID() == EEM_Registration::status_id_pending_payment ? true : false;
644
-    }
645
-
646
-
647
-    /**
648
-     *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
649
-     *
650
-     * @return        boolean
651
-     */
652
-    public function is_approved()
653
-    {
654
-        return $this->status_ID() == EEM_Registration::status_id_approved ? true : false;
655
-    }
656
-
657
-
658
-    /**
659
-     *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
660
-     *
661
-     * @return        boolean
662
-     */
663
-    public function is_cancelled()
664
-    {
665
-        return $this->status_ID() == EEM_Registration::status_id_cancelled ? true : false;
666
-    }
667
-
668
-
669
-    /**
670
-     *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
671
-     *
672
-     * @return        boolean
673
-     */
674
-    public function is_declined()
675
-    {
676
-        return $this->status_ID() == EEM_Registration::status_id_declined ? true : false;
677
-    }
678
-
679
-
680
-    /**
681
-     *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
682
-     *    EEM_Registration::status_id_incomplete
683
-     *
684
-     * @return        boolean
685
-     */
686
-    public function is_incomplete()
687
-    {
688
-        return $this->status_ID() == EEM_Registration::status_id_incomplete ? true : false;
689
-    }
690
-
691
-
692
-    /**
693
-     *        Set Registration Date
694
-     *
695
-     * @param        mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
696
-     *                                                 Date
697
-     * @throws EE_Error
698
-     * @throws RuntimeException
699
-     */
700
-    public function set_reg_date($REG_date = false)
701
-    {
702
-        $this->set('REG_date', $REG_date);
703
-    }
704
-
705
-
706
-    /**
707
-     *    Set final price owing for this registration after all ticket/price modifications
708
-     *
709
-     * @access    public
710
-     * @param    float $REG_final_price
711
-     * @throws EE_Error
712
-     * @throws RuntimeException
713
-     */
714
-    public function set_final_price($REG_final_price = 0.00)
715
-    {
716
-        $this->set('REG_final_price', $REG_final_price);
717
-    }
718
-
719
-
720
-    /**
721
-     *    Set amount paid towards this registration's final price
722
-     *
723
-     * @access    public
724
-     * @param    float $REG_paid
725
-     * @throws EE_Error
726
-     * @throws RuntimeException
727
-     */
728
-    public function set_paid($REG_paid = 0.00)
729
-    {
730
-        $this->set('REG_paid', $REG_paid);
731
-    }
732
-
733
-
734
-    /**
735
-     *        Attendee Is Going
736
-     *
737
-     * @param        boolean $REG_att_is_going Attendee Is Going
738
-     * @throws EE_Error
739
-     * @throws RuntimeException
740
-     */
741
-    public function set_att_is_going($REG_att_is_going = false)
742
-    {
743
-        $this->set('REG_att_is_going', $REG_att_is_going);
744
-    }
745
-
746
-
747
-    /**
748
-     * Gets the related attendee
749
-     *
750
-     * @return EE_Attendee
751
-     * @throws EE_Error
752
-     */
753
-    public function attendee()
754
-    {
755
-        return $this->get_first_related('Attendee');
756
-    }
757
-
758
-    /**
759
-     * Gets the name of the attendee.
760
-     * @since $VID:$
761
-     * @param bool $apply_html_entities set to true if you want to use HTML entities.
762
-     * @return string
763
-     * @throws EE_Error
764
-     * @throws InvalidArgumentException
765
-     * @throws InvalidDataTypeException
766
-     * @throws InvalidInterfaceException
767
-     * @throws ReflectionException
768
-     */
769
-    public function attendeeName($apply_html_entities = false)
770
-    {
771
-        $attendee = $this->get_first_related('Attendee');
772
-        if ($attendee instanceof EE_Attendee) {
773
-            $attendee_name = $attendee->full_name($apply_html_entities);
774
-        } else {
775
-            $attendee_name = esc_html__('Unknown', 'event_espresso');
776
-        }
777
-        return $attendee_name;
778
-    }
779
-
780
-
781
-    /**
782
-     *        get Event ID
783
-     */
784
-    public function event_ID()
785
-    {
786
-        return $this->get('EVT_ID');
787
-    }
788
-
789
-
790
-    /**
791
-     *        get Event ID
792
-     */
793
-    public function event_name()
794
-    {
795
-        $event = $this->event_obj();
796
-        if ($event) {
797
-            return $event->name();
798
-        } else {
799
-            return null;
800
-        }
801
-    }
802
-
803
-
804
-    /**
805
-     * Fetches the event this registration is for
806
-     *
807
-     * @return EE_Event
808
-     * @throws EE_Error
809
-     */
810
-    public function event_obj()
811
-    {
812
-        return $this->get_first_related('Event');
813
-    }
814
-
815
-
816
-    /**
817
-     *        get Attendee ID
818
-     */
819
-    public function attendee_ID()
820
-    {
821
-        return $this->get('ATT_ID');
822
-    }
823
-
824
-
825
-    /**
826
-     *        get PHP Session ID
827
-     */
828
-    public function session_ID()
829
-    {
830
-        return $this->get('REG_session');
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets the string which represents the URL trigger for the receipt template in the message template system.
836
-     *
837
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
838
-     * @return string
839
-     */
840
-    public function receipt_url($messenger = 'html')
841
-    {
842
-
843
-        /**
844
-         * The below will be deprecated one version after this.  We check first if there is a custom receipt 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/receipt_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
-            return add_query_arg(array('receipt' => 'true'), $this->invoice_url('launch'));
860
-        }
861
-        return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
862
-    }
863
-
864
-
865
-    /**
866
-     * Gets the string which represents the URL trigger for the invoice template in the message template system.
867
-     *
868
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
869
-     * @return string
870
-     * @throws EE_Error
871
-     */
872
-    public function invoice_url($messenger = 'html')
873
-    {
874
-        /**
875
-         * The below will be deprecated one version after this.  We check first if there is a custom invoice template
876
-         * already in use on old system.  If there is then we just return the standard url for it.
877
-         *
878
-         * @since 4.5.0
879
-         */
880
-        $template_relative_path = 'modules/gateways/Invoice/lib/templates/invoice_body.template.php';
881
-        $has_custom = EEH_Template::locate_template(
882
-            $template_relative_path,
883
-            array(),
884
-            true,
885
-            true,
886
-            true
887
-        );
888
-
889
-        if ($has_custom) {
890
-            if ($messenger == 'html') {
891
-                return $this->invoice_url('launch');
892
-            }
893
-            $route = $messenger == 'download' || $messenger == 'pdf' ? 'download_invoice' : 'launch_invoice';
894
-
895
-            $query_args = array('ee' => $route, 'id' => $this->reg_url_link());
896
-            if ($messenger == 'html') {
897
-                $query_args['html'] = true;
898
-            }
899
-            return add_query_arg($query_args, get_permalink(EE_Registry::instance()->CFG->core->thank_you_page_id));
900
-        }
901
-        return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
902
-    }
903
-
904
-
905
-    /**
906
-     * get Registration URL Link
907
-     *
908
-     * @access public
909
-     * @return string
910
-     * @throws EE_Error
911
-     */
912
-    public function reg_url_link()
913
-    {
914
-        return (string) $this->get('REG_url_link');
915
-    }
916
-
917
-
918
-    /**
919
-     * Echoes out invoice_url()
920
-     *
921
-     * @param string $type 'download','launch', or 'html' (default is 'launch')
922
-     * @return void
923
-     * @throws EE_Error
924
-     */
925
-    public function e_invoice_url($type = 'launch')
926
-    {
927
-        echo $this->invoice_url($type);
928
-    }
929
-
930
-
931
-    /**
932
-     * Echoes out payment_overview_url
933
-     */
934
-    public function e_payment_overview_url()
935
-    {
936
-        echo $this->payment_overview_url();
937
-    }
938
-
939
-
940
-    /**
941
-     * Gets the URL for the checkout payment options reg step
942
-     * with this registration's REG_url_link added as a query parameter
943
-     *
944
-     * @param bool $clear_session Set to true when you want to clear the session on revisiting the
945
-     *                            payment overview url.
946
-     * @return string
947
-     * @throws InvalidInterfaceException
948
-     * @throws InvalidDataTypeException
949
-     * @throws EE_Error
950
-     * @throws InvalidArgumentException
951
-     */
952
-    public function payment_overview_url($clear_session = false)
953
-    {
954
-        return add_query_arg(
955
-            (array) apply_filters(
956
-                'FHEE__EE_Registration__payment_overview_url__query_args',
957
-                array(
958
-                    'e_reg_url_link' => $this->reg_url_link(),
959
-                    'step'           => 'payment_options',
960
-                    'revisit'        => true,
961
-                    'clear_session'  => (bool) $clear_session,
962
-                ),
963
-                $this
964
-            ),
965
-            EE_Registry::instance()->CFG->core->reg_page_url()
966
-        );
967
-    }
968
-
969
-
970
-    /**
971
-     * Gets the URL for the checkout attendee information reg step
972
-     * with this registration's REG_url_link added as a query parameter
973
-     *
974
-     * @return string
975
-     * @throws InvalidInterfaceException
976
-     * @throws InvalidDataTypeException
977
-     * @throws EE_Error
978
-     * @throws InvalidArgumentException
979
-     */
980
-    public function edit_attendee_information_url()
981
-    {
982
-        return add_query_arg(
983
-            (array) apply_filters(
984
-                'FHEE__EE_Registration__edit_attendee_information_url__query_args',
985
-                array(
986
-                    'e_reg_url_link' => $this->reg_url_link(),
987
-                    'step'           => 'attendee_information',
988
-                    'revisit'        => true,
989
-                ),
990
-                $this
991
-            ),
992
-            EE_Registry::instance()->CFG->core->reg_page_url()
993
-        );
994
-    }
995
-
996
-
997
-    /**
998
-     * Simply generates and returns the appropriate admin_url link to edit this registration
999
-     *
1000
-     * @return string
1001
-     * @throws EE_Error
1002
-     */
1003
-    public function get_admin_edit_url()
1004
-    {
1005
-        return EEH_URL::add_query_args_and_nonce(
1006
-            array(
1007
-                'page'    => 'espresso_registrations',
1008
-                'action'  => 'view_registration',
1009
-                '_REG_ID' => $this->ID(),
1010
-            ),
1011
-            admin_url('admin.php')
1012
-        );
1013
-    }
1014
-
1015
-
1016
-    /**
1017
-     *    is_primary_registrant?
1018
-     */
1019
-    public function is_primary_registrant()
1020
-    {
1021
-        return $this->get('REG_count') === 1 ? true : false;
1022
-    }
1023
-
1024
-
1025
-    /**
1026
-     * This returns the primary registration object for this registration group (which may be this object).
1027
-     *
1028
-     * @return EE_Registration
1029
-     * @throws EE_Error
1030
-     */
1031
-    public function get_primary_registration()
1032
-    {
1033
-        if ($this->is_primary_registrant()) {
1034
-            return $this;
1035
-        }
1036
-
1037
-        // k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1038
-        /** @var EE_Registration $primary_registrant */
1039
-        $primary_registrant = EEM_Registration::instance()->get_one(
1040
-            array(
1041
-                array(
1042
-                    'TXN_ID'    => $this->transaction_ID(),
1043
-                    'REG_count' => 1,
1044
-                ),
1045
-            )
1046
-        );
1047
-        return $primary_registrant;
1048
-    }
1049
-
1050
-
1051
-    /**
1052
-     *        get  Attendee Number
1053
-     *
1054
-     * @access        public
1055
-     */
1056
-    public function count()
1057
-    {
1058
-        return $this->get('REG_count');
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     *        get Group Size
1064
-     */
1065
-    public function group_size()
1066
-    {
1067
-        return $this->get('REG_group_size');
1068
-    }
1069
-
1070
-
1071
-    /**
1072
-     *        get Registration Date
1073
-     */
1074
-    public function date()
1075
-    {
1076
-        return $this->get('REG_date');
1077
-    }
1078
-
1079
-
1080
-    /**
1081
-     * gets a pretty date
1082
-     *
1083
-     * @param string $date_format
1084
-     * @param string $time_format
1085
-     * @return string
1086
-     * @throws EE_Error
1087
-     */
1088
-    public function pretty_date($date_format = null, $time_format = null)
1089
-    {
1090
-        return $this->get_datetime('REG_date', $date_format, $time_format);
1091
-    }
1092
-
1093
-
1094
-    /**
1095
-     * final_price
1096
-     * the registration's share of the transaction total, so that the
1097
-     * sum of all the transaction's REG_final_prices equal the transaction's total
1098
-     *
1099
-     * @return float
1100
-     * @throws EE_Error
1101
-     */
1102
-    public function final_price()
1103
-    {
1104
-        return $this->get('REG_final_price');
1105
-    }
1106
-
1107
-
1108
-    /**
1109
-     * pretty_final_price
1110
-     *  final price as formatted string, with correct decimal places and currency symbol
1111
-     *
1112
-     * @return string
1113
-     * @throws EE_Error
1114
-     */
1115
-    public function pretty_final_price()
1116
-    {
1117
-        return $this->get_pretty('REG_final_price');
1118
-    }
1119
-
1120
-
1121
-    /**
1122
-     * get paid (yeah)
1123
-     *
1124
-     * @return float
1125
-     * @throws EE_Error
1126
-     */
1127
-    public function paid()
1128
-    {
1129
-        return $this->get('REG_paid');
1130
-    }
1131
-
1132
-
1133
-    /**
1134
-     * pretty_paid
1135
-     *
1136
-     * @return float
1137
-     * @throws EE_Error
1138
-     */
1139
-    public function pretty_paid()
1140
-    {
1141
-        return $this->get_pretty('REG_paid');
1142
-    }
1143
-
1144
-
1145
-    /**
1146
-     * owes_monies_and_can_pay
1147
-     * whether or not this registration has monies owing and it's' status allows payment
1148
-     *
1149
-     * @param array $requires_payment
1150
-     * @return bool
1151
-     * @throws EE_Error
1152
-     */
1153
-    public function owes_monies_and_can_pay($requires_payment = array())
1154
-    {
1155
-        // these reg statuses require payment (if event is not free)
1156
-        $requires_payment = ! empty($requires_payment)
1157
-            ? $requires_payment
1158
-            : EEM_Registration::reg_statuses_that_allow_payment();
1159
-        if (
1160
-            in_array($this->status_ID(), $requires_payment) &&
1161
-            $this->final_price() != 0 &&
1162
-            $this->final_price() != $this->paid()
1163
-        ) {
1164
-            return true;
1165
-        } else {
1166
-            return false;
1167
-        }
1168
-    }
1169
-
1170
-
1171
-    /**
1172
-     * Prints out the return value of $this->pretty_status()
1173
-     *
1174
-     * @param bool $show_icons
1175
-     * @return void
1176
-     * @throws EE_Error
1177
-     */
1178
-    public function e_pretty_status($show_icons = false)
1179
-    {
1180
-        echo $this->pretty_status($show_icons);
1181
-    }
1182
-
1183
-
1184
-    /**
1185
-     * Returns a nice version of the status for displaying to customers
1186
-     *
1187
-     * @param bool $show_icons
1188
-     * @return string
1189
-     * @throws EE_Error
1190
-     */
1191
-    public function pretty_status($show_icons = false)
1192
-    {
1193
-        $status = EEM_Status::instance()->localized_status(
1194
-            array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
1195
-            false,
1196
-            'sentence'
1197
-        );
1198
-        $icon = '';
1199
-        switch ($this->status_ID()) {
1200
-            case EEM_Registration::status_id_approved:
1201
-                $icon = $show_icons
1202
-                    ? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1203
-                    : '';
1204
-                break;
1205
-            case EEM_Registration::status_id_pending_payment:
1206
-                $icon = $show_icons
1207
-                    ? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1208
-                    : '';
1209
-                break;
1210
-            case EEM_Registration::status_id_not_approved:
1211
-                $icon = $show_icons
1212
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1213
-                    : '';
1214
-                break;
1215
-            case EEM_Registration::status_id_cancelled:
1216
-                $icon = $show_icons
1217
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1218
-                    : '';
1219
-                break;
1220
-            case EEM_Registration::status_id_incomplete:
1221
-                $icon = $show_icons
1222
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1223
-                    : '';
1224
-                break;
1225
-            case EEM_Registration::status_id_declined:
1226
-                $icon = $show_icons
1227
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1228
-                    : '';
1229
-                break;
1230
-            case EEM_Registration::status_id_wait_list:
1231
-                $icon = $show_icons
1232
-                    ? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1233
-                    : '';
1234
-                break;
1235
-        }
1236
-        return $icon . $status[ $this->status_ID() ];
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     *        get Attendee Is Going
1242
-     */
1243
-    public function att_is_going()
1244
-    {
1245
-        return $this->get('REG_att_is_going');
1246
-    }
1247
-
1248
-
1249
-    /**
1250
-     * Gets related answers
1251
-     *
1252
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1253
-     * @return EE_Answer[]
1254
-     * @throws EE_Error
1255
-     */
1256
-    public function answers($query_params = null)
1257
-    {
1258
-        return $this->get_many_related('Answer', $query_params);
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Gets the registration's answer value to the specified question
1264
-     * (either the question's ID or a question object)
1265
-     *
1266
-     * @param EE_Question|int $question
1267
-     * @param bool            $pretty_value
1268
-     * @return array|string if pretty_value= true, the result will always be a string
1269
-     * (because the answer might be an array of answer values, so passing pretty_value=true
1270
-     * will convert it into some kind of string)
1271
-     * @throws EE_Error
1272
-     */
1273
-    public function answer_value_to_question($question, $pretty_value = true)
1274
-    {
1275
-        $question_id = EEM_Question::instance()->ensure_is_ID($question);
1276
-        return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1277
-    }
1278
-
1279
-
1280
-    /**
1281
-     * question_groups
1282
-     * returns an array of EE_Question_Group objects for this registration
1283
-     *
1284
-     * @return EE_Question_Group[]
1285
-     * @throws EE_Error
1286
-     * @throws InvalidArgumentException
1287
-     * @throws InvalidDataTypeException
1288
-     * @throws InvalidInterfaceException
1289
-     * @throws ReflectionException
1290
-     */
1291
-    public function question_groups()
1292
-    {
1293
-        return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1294
-    }
1295
-
1296
-
1297
-    /**
1298
-     * count_question_groups
1299
-     * returns a count of the number of EE_Question_Group objects for this registration
1300
-     *
1301
-     * @return int
1302
-     * @throws EE_Error
1303
-     * @throws EntityNotFoundException
1304
-     * @throws InvalidArgumentException
1305
-     * @throws InvalidDataTypeException
1306
-     * @throws InvalidInterfaceException
1307
-     * @throws ReflectionException
1308
-     */
1309
-    public function count_question_groups()
1310
-    {
1311
-        return EEM_Event::instance()->count_related(
1312
-            $this->event_ID(),
1313
-            'Question_Group',
1314
-            [
1315
-                [
1316
-                    'Event_Question_Group.'
1317
-                    . EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1318
-                ]
1319
-            ]
1320
-        );
1321
-    }
1322
-
1323
-
1324
-    /**
1325
-     * Returns the registration date in the 'standard' string format
1326
-     * (function may be improved in the future to allow for different formats and timezones)
1327
-     *
1328
-     * @return string
1329
-     * @throws EE_Error
1330
-     */
1331
-    public function reg_date()
1332
-    {
1333
-        return $this->get_datetime('REG_date');
1334
-    }
1335
-
1336
-
1337
-    /**
1338
-     * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1339
-     * the ticket this registration purchased, or the datetime they have registered
1340
-     * to attend)
1341
-     *
1342
-     * @return EE_Datetime_Ticket
1343
-     * @throws EE_Error
1344
-     */
1345
-    public function datetime_ticket()
1346
-    {
1347
-        return $this->get_first_related('Datetime_Ticket');
1348
-    }
1349
-
1350
-
1351
-    /**
1352
-     * Sets the registration's datetime_ticket.
1353
-     *
1354
-     * @param EE_Datetime_Ticket $datetime_ticket
1355
-     * @return EE_Datetime_Ticket
1356
-     * @throws EE_Error
1357
-     */
1358
-    public function set_datetime_ticket($datetime_ticket)
1359
-    {
1360
-        return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1361
-    }
1362
-
1363
-    /**
1364
-     * Gets deleted
1365
-     *
1366
-     * @return bool
1367
-     * @throws EE_Error
1368
-     */
1369
-    public function deleted()
1370
-    {
1371
-        return $this->get('REG_deleted');
1372
-    }
1373
-
1374
-    /**
1375
-     * Sets deleted
1376
-     *
1377
-     * @param boolean $deleted
1378
-     * @return bool
1379
-     * @throws EE_Error
1380
-     * @throws RuntimeException
1381
-     */
1382
-    public function set_deleted($deleted)
1383
-    {
1384
-        if ($deleted) {
1385
-            $this->delete();
1386
-        } else {
1387
-            $this->restore();
1388
-        }
1389
-    }
1390
-
1391
-
1392
-    /**
1393
-     * Get the status object of this object
1394
-     *
1395
-     * @return EE_Status
1396
-     * @throws EE_Error
1397
-     */
1398
-    public function status_obj()
1399
-    {
1400
-        return $this->get_first_related('Status');
1401
-    }
1402
-
1403
-
1404
-    /**
1405
-     * Returns the number of times this registration has checked into any of the datetimes
1406
-     * its available for
1407
-     *
1408
-     * @return int
1409
-     * @throws EE_Error
1410
-     */
1411
-    public function count_checkins()
1412
-    {
1413
-        return $this->get_model()->count_related($this, 'Checkin');
1414
-    }
1415
-
1416
-
1417
-    /**
1418
-     * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1419
-     * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1420
-     *
1421
-     * @return int
1422
-     * @throws EE_Error
1423
-     */
1424
-    public function count_checkins_not_checkedout()
1425
-    {
1426
-        return $this->get_model()->count_related($this, 'Checkin', array(array('CHK_in' => 1)));
1427
-    }
1428
-
1429
-
1430
-    /**
1431
-     * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1432
-     *
1433
-     * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1434
-     * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1435
-     *                                          consider registration status as well as datetime access.
1436
-     * @return bool
1437
-     * @throws EE_Error
1438
-     */
1439
-    public function can_checkin($DTT_OR_ID, $check_approved = true)
1440
-    {
1441
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1442
-
1443
-        // first check registration status
1444
-        if (($check_approved && ! $this->is_approved()) || ! $DTT_ID) {
1445
-            return false;
1446
-        }
1447
-        // is there a datetime ticket that matches this dtt_ID?
1448
-        if (
1449
-            ! (EEM_Datetime_Ticket::instance()->exists(
1450
-                array(
1451
-                array(
1452
-                    'TKT_ID' => $this->get('TKT_ID'),
1453
-                    'DTT_ID' => $DTT_ID,
1454
-                ),
1455
-                )
1456
-            ))
1457
-        ) {
1458
-            return false;
1459
-        }
1460
-
1461
-        // final check is against TKT_uses
1462
-        return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1463
-    }
1464
-
1465
-
1466
-    /**
1467
-     * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1468
-     * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1469
-     * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1470
-     * then return false.  Otherwise return true.
1471
-     *
1472
-     * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1473
-     * @return bool true means can checkin.  false means cannot checkin.
1474
-     * @throws EE_Error
1475
-     */
1476
-    public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1477
-    {
1478
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1479
-
1480
-        if (! $DTT_ID) {
1481
-            return false;
1482
-        }
1483
-
1484
-        $max_uses = $this->ticket() instanceof EE_Ticket ? $this->ticket()->uses() : EE_INF;
1485
-
1486
-        // if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1487
-        // check-in or not.
1488
-        if (! $max_uses || $max_uses === EE_INF) {
1489
-            return true;
1490
-        }
1491
-
1492
-        // does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1493
-        // go ahead and toggle.
1494
-        if (EEM_Checkin::instance()->exists(array(array('REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID)))) {
1495
-            return true;
1496
-        }
1497
-
1498
-        // made it here so the last check is whether the number of checkins per unique datetime on this registration
1499
-        // disallows further check-ins.
1500
-        $count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1501
-            array(
1502
-                array(
1503
-                    'REG_ID' => $this->ID(),
1504
-                    'CHK_in' => true,
1505
-                ),
1506
-            ),
1507
-            'DTT_ID',
1508
-            true
1509
-        );
1510
-        // checkins have already reached their max number of uses
1511
-        // so registrant can NOT checkin
1512
-        if ($count_unique_dtt_checkins >= $max_uses) {
1513
-            EE_Error::add_error(
1514
-                esc_html__(
1515
-                    'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1516
-                    'event_espresso'
1517
-                ),
1518
-                __FILE__,
1519
-                __FUNCTION__,
1520
-                __LINE__
1521
-            );
1522
-            return false;
1523
-        }
1524
-        return true;
1525
-    }
1526
-
1527
-
1528
-    /**
1529
-     * toggle Check-in status for this registration
1530
-     * Check-ins are toggled in the following order:
1531
-     * never checked in -> checked in
1532
-     * checked in -> checked out
1533
-     * checked out -> checked in
1534
-     *
1535
-     * @param  int $DTT_ID  include specific datetime to toggle Check-in for.
1536
-     *                      If not included or null, then it is assumed latest datetime is being toggled.
1537
-     * @param bool $verify  If true then can_checkin() is used to verify whether the person
1538
-     *                      can be checked in or not.  Otherwise this forces change in checkin status.
1539
-     * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1540
-     * @throws EE_Error
1541
-     */
1542
-    public function toggle_checkin_status($DTT_ID = null, $verify = false)
1543
-    {
1544
-        if (empty($DTT_ID)) {
1545
-            $datetime = $this->get_latest_related_datetime();
1546
-            $DTT_ID = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1547
-            // verify the registration can checkin for the given DTT_ID
1548
-        } elseif (! $this->can_checkin($DTT_ID, $verify)) {
1549
-            EE_Error::add_error(
1550
-                sprintf(
1551
-                    esc_html__(
1552
-                        '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',
1553
-                        'event_espresso'
1554
-                    ),
1555
-                    $this->ID(),
1556
-                    $DTT_ID
1557
-                ),
1558
-                __FILE__,
1559
-                __FUNCTION__,
1560
-                __LINE__
1561
-            );
1562
-            return false;
1563
-        }
1564
-        $status_paths = array(
1565
-            EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1566
-            EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1567
-            EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1568
-        );
1569
-        // start by getting the current status so we know what status we'll be changing to.
1570
-        $cur_status = $this->check_in_status_for_datetime($DTT_ID, null);
1571
-        $status_to = $status_paths[ $cur_status ];
1572
-        // database only records true for checked IN or false for checked OUT
1573
-        // no record ( null ) means checked in NEVER, but we obviously don't save that
1574
-        $new_status = $status_to === EE_Checkin::status_checked_in ? true : false;
1575
-        // add relation - note Check-ins are always creating new rows
1576
-        // because we are keeping track of Check-ins over time.
1577
-        // Eventually we'll probably want to show a list table
1578
-        // for the individual Check-ins so that they can be managed.
1579
-        $checkin = EE_Checkin::new_instance(
1580
-            array(
1581
-                'REG_ID' => $this->ID(),
1582
-                'DTT_ID' => $DTT_ID,
1583
-                'CHK_in' => $new_status,
1584
-            )
1585
-        );
1586
-        // if the record could not be saved then return false
1587
-        if ($checkin->save() === 0) {
1588
-            if (WP_DEBUG) {
1589
-                global $wpdb;
1590
-                $error = sprintf(
1591
-                    esc_html__(
1592
-                        'Registration check in update failed because of the following database error: %1$s%2$s',
1593
-                        'event_espresso'
1594
-                    ),
1595
-                    '<br />',
1596
-                    $wpdb->last_error
1597
-                );
1598
-            } else {
1599
-                $error = esc_html__(
1600
-                    'Registration check in update failed because of an unknown database error',
1601
-                    'event_espresso'
1602
-                );
1603
-            }
1604
-            EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1605
-            return false;
1606
-        }
1607
-        // Fire a checked_in and checkout_out action.
1608
-        $checked_status = $status_to === EE_Checkin::status_checked_in ? 'checked_in' : 'checked_out';
1609
-        do_action("AHEE__EE_Registration__toggle_checkin_status__{$checked_status}", $this, $DTT_ID);
1610
-        return $status_to;
1611
-    }
1612
-
1613
-
1614
-    /**
1615
-     * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1616
-     * "Latest" is defined by the `DTT_EVT_start` column.
1617
-     *
1618
-     * @return EE_Datetime|null
1619
-     * @throws EE_Error
1620
-     */
1621
-    public function get_latest_related_datetime()
1622
-    {
1623
-        return EEM_Datetime::instance()->get_one(
1624
-            array(
1625
-                array(
1626
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1627
-                ),
1628
-                'order_by' => array('DTT_EVT_start' => 'DESC'),
1629
-            )
1630
-        );
1631
-    }
1632
-
1633
-
1634
-    /**
1635
-     * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1636
-     * "Earliest" is defined by the `DTT_EVT_start` column.
1637
-     *
1638
-     * @throws EE_Error
1639
-     */
1640
-    public function get_earliest_related_datetime()
1641
-    {
1642
-        return EEM_Datetime::instance()->get_one(
1643
-            array(
1644
-                array(
1645
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1646
-                ),
1647
-                'order_by' => array('DTT_EVT_start' => 'ASC'),
1648
-            )
1649
-        );
1650
-    }
1651
-
1652
-
1653
-    /**
1654
-     * This method simply returns the check-in status for this registration and the given datetime.
1655
-     * If neither the datetime nor the checkin values are provided as arguments,
1656
-     * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1657
-     *
1658
-     * @param  int       $DTT_ID  The ID of the datetime we're checking against
1659
-     *                            (if empty we'll get the primary datetime for
1660
-     *                            this registration (via event) and use it's ID);
1661
-     * @param EE_Checkin $checkin If present, we use the given checkin object rather than the dtt_id.
1662
-     *
1663
-     * @return int                Integer representing Check-in status.
1664
-     * @throws EE_Error
1665
-     */
1666
-    public function check_in_status_for_datetime($DTT_ID = 0, $checkin = null)
1667
-    {
1668
-        $checkin_query_params = array(
1669
-            'order_by' => array('CHK_timestamp' => 'DESC'),
1670
-        );
1671
-
1672
-        if ($DTT_ID > 0) {
1673
-            $checkin_query_params[0] = array('DTT_ID' => $DTT_ID);
1674
-        }
1675
-
1676
-        // get checkin object (if exists)
1677
-        $checkin = $checkin instanceof EE_Checkin
1678
-            ? $checkin
1679
-            : $this->get_first_related('Checkin', $checkin_query_params);
1680
-        if ($checkin instanceof EE_Checkin) {
1681
-            if ($checkin->get('CHK_in')) {
1682
-                return EE_Checkin::status_checked_in; // checked in
1683
-            }
1684
-            return EE_Checkin::status_checked_out; // had checked in but is now checked out.
1685
-        }
1686
-        return EE_Checkin::status_checked_never; // never been checked in
1687
-    }
1688
-
1689
-
1690
-    /**
1691
-     * This method returns a localized message for the toggled Check-in message.
1692
-     *
1693
-     * @param  int $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1694
-     *                     then it is assumed Check-in for primary datetime was toggled.
1695
-     * @param bool $error  This just flags that you want an error message returned. This is put in so that the error
1696
-     *                     message can be customized with the attendee name.
1697
-     * @return string internationalized message
1698
-     * @throws EE_Error
1699
-     */
1700
-    public function get_checkin_msg($DTT_ID, $error = false)
1701
-    {
1702
-        // let's get the attendee first so we can include the name of the attendee
1703
-        $attendee = $this->get_first_related('Attendee');
1704
-        if ($attendee instanceof EE_Attendee) {
1705
-            if ($error) {
1706
-                return sprintf(__("%s's check-in status was not changed.", "event_espresso"), $attendee->full_name());
1707
-            }
1708
-            $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1709
-            // what is the status message going to be?
1710
-            switch ($cur_status) {
1711
-                case EE_Checkin::status_checked_never:
1712
-                    return sprintf(
1713
-                        __("%s has been removed from Check-in records", "event_espresso"),
1714
-                        $attendee->full_name()
1715
-                    );
1716
-                    break;
1717
-                case EE_Checkin::status_checked_in:
1718
-                    return sprintf(__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1719
-                    break;
1720
-                case EE_Checkin::status_checked_out:
1721
-                    return sprintf(__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1722
-                    break;
1723
-            }
1724
-        }
1725
-        return esc_html__("The check-in status could not be determined.", "event_espresso");
1726
-    }
1727
-
1728
-
1729
-    /**
1730
-     * Returns the related EE_Transaction to this registration
1731
-     *
1732
-     * @return EE_Transaction
1733
-     * @throws EE_Error
1734
-     * @throws EntityNotFoundException
1735
-     */
1736
-    public function transaction()
1737
-    {
1738
-        $transaction = $this->get_first_related('Transaction');
1739
-        if (! $transaction instanceof \EE_Transaction) {
1740
-            throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1741
-        }
1742
-        return $transaction;
1743
-    }
1744
-
1745
-
1746
-    /**
1747
-     *        get Registration Code
1748
-     */
1749
-    public function reg_code()
1750
-    {
1751
-        return $this->get('REG_code');
1752
-    }
1753
-
1754
-
1755
-    /**
1756
-     *        get Transaction ID
1757
-     */
1758
-    public function transaction_ID()
1759
-    {
1760
-        return $this->get('TXN_ID');
1761
-    }
1762
-
1763
-
1764
-    /**
1765
-     * @return int
1766
-     * @throws EE_Error
1767
-     */
1768
-    public function ticket_ID()
1769
-    {
1770
-        return $this->get('TKT_ID');
1771
-    }
1772
-
1773
-
1774
-    /**
1775
-     *        Set Registration Code
1776
-     *
1777
-     * @access    public
1778
-     * @param    string  $REG_code Registration Code
1779
-     * @param    boolean $use_default
1780
-     * @throws EE_Error
1781
-     */
1782
-    public function set_reg_code($REG_code, $use_default = false)
1783
-    {
1784
-        if (empty($REG_code)) {
1785
-            EE_Error::add_error(
1786
-                esc_html__('REG_code can not be empty.', 'event_espresso'),
1787
-                __FILE__,
1788
-                __FUNCTION__,
1789
-                __LINE__
1790
-            );
1791
-            return;
1792
-        }
1793
-        if (! $this->reg_code()) {
1794
-            parent::set('REG_code', $REG_code, $use_default);
1795
-        } else {
1796
-            EE_Error::doing_it_wrong(
1797
-                __CLASS__ . '::' . __FUNCTION__,
1798
-                esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
1799
-                '4.6.0'
1800
-            );
1801
-        }
1802
-    }
1803
-
1804
-
1805
-    /**
1806
-     * Returns all other registrations in the same group as this registrant who have the same ticket option.
1807
-     * Note, if you want to just get all registrations in the same transaction (group), use:
1808
-     *    $registration->transaction()->registrations();
1809
-     *
1810
-     * @since 4.5.0
1811
-     * @return EE_Registration[] or empty array if this isn't a group registration.
1812
-     * @throws EE_Error
1813
-     */
1814
-    public function get_all_other_registrations_in_group()
1815
-    {
1816
-        if ($this->group_size() < 2) {
1817
-            return array();
1818
-        }
1819
-
1820
-        $query[0] = array(
1821
-            'TXN_ID' => $this->transaction_ID(),
1822
-            'REG_ID' => array('!=', $this->ID()),
1823
-            'TKT_ID' => $this->ticket_ID(),
1824
-        );
1825
-        /** @var EE_Registration[] $registrations */
1826
-        $registrations = $this->get_model()->get_all($query);
1827
-        return $registrations;
1828
-    }
1829
-
1830
-    /**
1831
-     * Return the link to the admin details for the object.
1832
-     *
1833
-     * @return string
1834
-     * @throws EE_Error
1835
-     */
1836
-    public function get_admin_details_link()
1837
-    {
1838
-        EE_Registry::instance()->load_helper('URL');
1839
-        return EEH_URL::add_query_args_and_nonce(
1840
-            array(
1841
-                'page'    => 'espresso_registrations',
1842
-                'action'  => 'view_registration',
1843
-                '_REG_ID' => $this->ID(),
1844
-            ),
1845
-            admin_url('admin.php')
1846
-        );
1847
-    }
1848
-
1849
-    /**
1850
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
1851
-     *
1852
-     * @return string
1853
-     * @throws EE_Error
1854
-     */
1855
-    public function get_admin_edit_link()
1856
-    {
1857
-        return $this->get_admin_details_link();
1858
-    }
1859
-
1860
-    /**
1861
-     * Returns the link to a settings page for the object.
1862
-     *
1863
-     * @return string
1864
-     * @throws EE_Error
1865
-     */
1866
-    public function get_admin_settings_link()
1867
-    {
1868
-        return $this->get_admin_details_link();
1869
-    }
1870
-
1871
-    /**
1872
-     * Returns the link to the "overview" for the object (typically the "list table" view).
1873
-     *
1874
-     * @return string
1875
-     */
1876
-    public function get_admin_overview_link()
1877
-    {
1878
-        EE_Registry::instance()->load_helper('URL');
1879
-        return EEH_URL::add_query_args_and_nonce(
1880
-            array(
1881
-                'page' => 'espresso_registrations',
1882
-            ),
1883
-            admin_url('admin.php')
1884
-        );
1885
-    }
1886
-
1887
-
1888
-    /**
1889
-     * @param array $query_params
1890
-     *
1891
-     * @return \EE_Registration[]
1892
-     * @throws EE_Error
1893
-     */
1894
-    public function payments($query_params = array())
1895
-    {
1896
-        return $this->get_many_related('Payment', $query_params);
1897
-    }
1898
-
1899
-
1900
-    /**
1901
-     * @param array $query_params
1902
-     *
1903
-     * @return \EE_Registration_Payment[]
1904
-     * @throws EE_Error
1905
-     */
1906
-    public function registration_payments($query_params = array())
1907
-    {
1908
-        return $this->get_many_related('Registration_Payment', $query_params);
1909
-    }
1910
-
1911
-
1912
-    /**
1913
-     * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
1914
-     * Note: if there are no payments on the registration there will be no payment method returned.
1915
-     *
1916
-     * @return EE_Payment_Method|null
1917
-     */
1918
-    public function payment_method()
1919
-    {
1920
-        return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
1921
-    }
1922
-
1923
-
1924
-    /**
1925
-     * @return \EE_Line_Item
1926
-     * @throws EntityNotFoundException
1927
-     * @throws EE_Error
1928
-     */
1929
-    public function ticket_line_item()
1930
-    {
1931
-        $ticket = $this->ticket();
1932
-        $transaction = $this->transaction();
1933
-        $line_item = null;
1934
-        $ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
1935
-            $transaction->total_line_item(),
1936
-            'Ticket',
1937
-            array($ticket->ID())
1938
-        );
1939
-        foreach ($ticket_line_items as $ticket_line_item) {
1940
-            if (
1941
-                $ticket_line_item instanceof \EE_Line_Item
1942
-                && $ticket_line_item->OBJ_type() === 'Ticket'
1943
-                && $ticket_line_item->OBJ_ID() === $ticket->ID()
1944
-            ) {
1945
-                $line_item = $ticket_line_item;
1946
-                break;
1947
-            }
1948
-        }
1949
-        if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
1950
-            throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
1951
-        }
1952
-        return $line_item;
1953
-    }
1954
-
1955
-
1956
-    /**
1957
-     * Soft Deletes this model object.
1958
-     *
1959
-     * @return boolean | int
1960
-     * @throws RuntimeException
1961
-     * @throws EE_Error
1962
-     */
1963
-    public function delete()
1964
-    {
1965
-        if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
1966
-            $this->set_status(EEM_Registration::status_id_cancelled);
1967
-        }
1968
-        return parent::delete();
1969
-    }
1970
-
1971
-
1972
-    /**
1973
-     * Restores whatever the previous status was on a registration before it was trashed (if possible)
1974
-     *
1975
-     * @throws EE_Error
1976
-     * @throws RuntimeException
1977
-     */
1978
-    public function restore()
1979
-    {
1980
-        $previous_status = $this->get_extra_meta(
1981
-            EE_Registration::PRE_TRASH_REG_STATUS_KEY,
1982
-            true,
1983
-            EEM_Registration::status_id_cancelled
1984
-        );
1985
-        if ($previous_status) {
1986
-            $this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
1987
-            $this->set_status($previous_status);
1988
-        }
1989
-        return parent::restore();
1990
-    }
1991
-
1992
-
1993
-    /**
1994
-     * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
1995
-     *
1996
-     * @param  boolean $trigger_set_status_logic EE_Registration::set_status() can trigger additional logic
1997
-     *                                           depending on whether the reg status changes to or from "Approved"
1998
-     * @return boolean whether the Registration status was updated
1999
-     * @throws EE_Error
2000
-     * @throws RuntimeException
2001
-     */
2002
-    public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
2003
-    {
2004
-        $paid = $this->paid();
2005
-        $price = $this->final_price();
2006
-        switch (true) {
2007
-            // overpaid or paid
2008
-            case EEH_Money::compare_floats($paid, $price, '>'):
2009
-            case EEH_Money::compare_floats($paid, $price):
2010
-                $new_status = EEM_Registration::status_id_approved;
2011
-                break;
2012
-            //  underpaid
2013
-            case EEH_Money::compare_floats($paid, $price, '<'):
2014
-                $new_status = EEM_Registration::status_id_pending_payment;
2015
-                break;
2016
-            // uhhh Houston...
2017
-            default:
2018
-                throw new RuntimeException(
2019
-                    esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2020
-                );
2021
-        }
2022
-        if ($new_status !== $this->status_ID()) {
2023
-            if ($trigger_set_status_logic) {
2024
-                return $this->set_status($new_status);
2025
-            }
2026
-            parent::set('STS_ID', $new_status);
2027
-            return true;
2028
-        }
2029
-        return false;
2030
-    }
2031
-
2032
-
2033
-    /*************************** DEPRECATED ***************************/
2034
-
2035
-
2036
-    /**
2037
-     * @deprecated
2038
-     * @since     4.7.0
2039
-     * @access    public
2040
-     */
2041
-    public function price_paid()
2042
-    {
2043
-        EE_Error::doing_it_wrong(
2044
-            'EE_Registration::price_paid()',
2045
-            esc_html__(
2046
-                'This method is deprecated, please use EE_Registration::final_price() instead.',
2047
-                'event_espresso'
2048
-            ),
2049
-            '4.7.0'
2050
-        );
2051
-        return $this->final_price();
2052
-    }
2053
-
2054
-
2055
-    /**
2056
-     * @deprecated
2057
-     * @since     4.7.0
2058
-     * @access    public
2059
-     * @param    float $REG_final_price
2060
-     * @throws EE_Error
2061
-     * @throws RuntimeException
2062
-     */
2063
-    public function set_price_paid($REG_final_price = 0.00)
2064
-    {
2065
-        EE_Error::doing_it_wrong(
2066
-            'EE_Registration::set_price_paid()',
2067
-            esc_html__(
2068
-                'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2069
-                'event_espresso'
2070
-            ),
2071
-            '4.7.0'
2072
-        );
2073
-        $this->set_final_price($REG_final_price);
2074
-    }
2075
-
2076
-
2077
-    /**
2078
-     * @deprecated
2079
-     * @since 4.7.0
2080
-     * @return string
2081
-     * @throws EE_Error
2082
-     */
2083
-    public function pretty_price_paid()
2084
-    {
2085
-        EE_Error::doing_it_wrong(
2086
-            'EE_Registration::pretty_price_paid()',
2087
-            esc_html__(
2088
-                'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2089
-                'event_espresso'
2090
-            ),
2091
-            '4.7.0'
2092
-        );
2093
-        return $this->pretty_final_price();
2094
-    }
2095
-
2096
-
2097
-    /**
2098
-     * Gets the primary datetime related to this registration via the related Event to this registration
2099
-     *
2100
-     * @deprecated 4.9.17
2101
-     * @return EE_Datetime
2102
-     * @throws EE_Error
2103
-     * @throws EntityNotFoundException
2104
-     */
2105
-    public function get_related_primary_datetime()
2106
-    {
2107
-        EE_Error::doing_it_wrong(
2108
-            __METHOD__,
2109
-            esc_html__(
2110
-                'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2111
-                'event_espresso'
2112
-            ),
2113
-            '4.9.17',
2114
-            '5.0.0'
2115
-        );
2116
-        return $this->event()->primary_datetime();
2117
-    }
2118
-
2119
-    /**
2120
-     * Returns the contact's name (or "Unknown" if there is no contact.)
2121
-     * @since $VID:$
2122
-     * @return string
2123
-     * @throws EE_Error
2124
-     * @throws InvalidArgumentException
2125
-     * @throws InvalidDataTypeException
2126
-     * @throws InvalidInterfaceException
2127
-     * @throws ReflectionException
2128
-     */
2129
-    public function name()
2130
-    {
2131
-        return $this->attendeeName();
2132
-    }
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 = '', $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 = '')
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, bool $use_default = false)
119
+	{
120
+		switch ($field_name) {
121
+			case 'REG_code':
122
+				if (! empty($field_value) && $this->reg_code() === null) {
123
+					$this->set_reg_code($field_value, $use_default);
124
+				}
125
+				break;
126
+			case 'STS_ID':
127
+				$this->set_status($field_value, $use_default);
128
+				break;
129
+			default:
130
+				parent::set($field_name, $field_value, $use_default);
131
+		}
132
+	}
133
+
134
+
135
+	/**
136
+	 * Set Status ID
137
+	 * updates the registration status and ALSO...
138
+	 * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
139
+	 * calls release_registration_space() if the reg status changes FROM approved to any other reg status
140
+	 *
141
+	 * @param string                $new_STS_ID
142
+	 * @param boolean               $use_default
143
+	 * @param ContextInterface|null $context
144
+	 * @return bool
145
+	 * @throws DomainException
146
+	 * @throws EE_Error
147
+	 * @throws EntityNotFoundException
148
+	 * @throws InvalidArgumentException
149
+	 * @throws InvalidDataTypeException
150
+	 * @throws InvalidInterfaceException
151
+	 * @throws ReflectionException
152
+	 * @throws RuntimeException
153
+	 * @throws UnexpectedEntityException
154
+	 */
155
+	public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
156
+	{
157
+		// get current REG_Status
158
+		$old_STS_ID = $this->status_ID();
159
+		// if status has changed
160
+		if (
161
+			$old_STS_ID !== $new_STS_ID // and that status has actually changed
162
+			&& ! empty($old_STS_ID) // and that old status is actually set
163
+			&& ! empty($new_STS_ID) // as well as the new status
164
+			&& $this->ID() // ensure registration is in the db
165
+		) {
166
+			// update internal status first
167
+			parent::set('STS_ID', $new_STS_ID, $use_default);
168
+			// THEN handle other changes that occur when reg status changes
169
+			// TO approved
170
+			if ($new_STS_ID === EEM_Registration::status_id_approved) {
171
+				// reserve a space by incrementing ticket and datetime sold values
172
+				$this->reserveRegistrationSpace();
173
+				do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
174
+				// OR FROM  approved
175
+			} elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
176
+				// release a space by decrementing ticket and datetime sold values
177
+				$this->releaseRegistrationSpace();
178
+				do_action(
179
+					'AHEE__EE_Registration__set_status__from_approved',
180
+					$this,
181
+					$old_STS_ID,
182
+					$new_STS_ID,
183
+					$context
184
+				);
185
+			}
186
+			// update status
187
+			parent::set('STS_ID', $new_STS_ID, $use_default);
188
+			$this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
189
+			if ($this->statusChangeUpdatesTransaction($context)) {
190
+				$this->updateTransactionAfterStatusChange();
191
+			}
192
+			do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
193
+			return true;
194
+		}
195
+		// even though the old value matches the new value, it's still good to
196
+		// allow the parent set method to have a say
197
+		parent::set('STS_ID', $new_STS_ID, $use_default);
198
+		return true;
199
+	}
200
+
201
+
202
+	/**
203
+	 * update REGs and TXN when cancelled or declined registrations involved
204
+	 *
205
+	 * @param string                $new_STS_ID
206
+	 * @param string                $old_STS_ID
207
+	 * @param ContextInterface|null $context
208
+	 * @throws EE_Error
209
+	 * @throws InvalidArgumentException
210
+	 * @throws InvalidDataTypeException
211
+	 * @throws InvalidInterfaceException
212
+	 * @throws ReflectionException
213
+	 * @throws RuntimeException
214
+	 */
215
+	private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
216
+	{
217
+		// these reg statuses should not be considered in any calculations involving monies owing
218
+		$closed_reg_statuses = EEM_Registration::closed_reg_statuses();
219
+		// true if registration has been cancelled or declined
220
+		$this->updateIfCanceled(
221
+			$closed_reg_statuses,
222
+			$new_STS_ID,
223
+			$old_STS_ID,
224
+			$context
225
+		);
226
+		$this->updateIfReinstated(
227
+			$closed_reg_statuses,
228
+			$new_STS_ID,
229
+			$old_STS_ID,
230
+			$context
231
+		);
232
+	}
233
+
234
+
235
+	/**
236
+	 * update REGs and TXN when cancelled or declined registrations involved
237
+	 *
238
+	 * @param array                 $closed_reg_statuses
239
+	 * @param string                $new_STS_ID
240
+	 * @param string                $old_STS_ID
241
+	 * @param ContextInterface|null $context
242
+	 * @throws EE_Error
243
+	 * @throws InvalidArgumentException
244
+	 * @throws InvalidDataTypeException
245
+	 * @throws InvalidInterfaceException
246
+	 * @throws ReflectionException
247
+	 * @throws RuntimeException
248
+	 */
249
+	private function updateIfCanceled(
250
+		array $closed_reg_statuses,
251
+		$new_STS_ID,
252
+		$old_STS_ID,
253
+		ContextInterface $context = null
254
+	) {
255
+		// true if registration has been cancelled or declined
256
+		if (
257
+			in_array($new_STS_ID, $closed_reg_statuses, true)
258
+			&& ! in_array($old_STS_ID, $closed_reg_statuses, true)
259
+		) {
260
+			/** @type EE_Registration_Processor $registration_processor */
261
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
262
+			/** @type EE_Transaction_Processor $transaction_processor */
263
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
264
+			// cancelled or declined registration
265
+			$registration_processor->update_registration_after_being_canceled_or_declined(
266
+				$this,
267
+				$closed_reg_statuses
268
+			);
269
+			$transaction_processor->update_transaction_after_canceled_or_declined_registration(
270
+				$this,
271
+				$closed_reg_statuses,
272
+				false
273
+			);
274
+			do_action(
275
+				'AHEE__EE_Registration__set_status__canceled_or_declined',
276
+				$this,
277
+				$old_STS_ID,
278
+				$new_STS_ID,
279
+				$context
280
+			);
281
+			return;
282
+		}
283
+	}
284
+
285
+
286
+	/**
287
+	 * update REGs and TXN when cancelled or declined registrations involved
288
+	 *
289
+	 * @param array                 $closed_reg_statuses
290
+	 * @param string                $new_STS_ID
291
+	 * @param string                $old_STS_ID
292
+	 * @param ContextInterface|null $context
293
+	 * @throws EE_Error
294
+	 * @throws InvalidArgumentException
295
+	 * @throws InvalidDataTypeException
296
+	 * @throws InvalidInterfaceException
297
+	 * @throws ReflectionException
298
+	 */
299
+	private function updateIfReinstated(
300
+		array $closed_reg_statuses,
301
+		$new_STS_ID,
302
+		$old_STS_ID,
303
+		ContextInterface $context = null
304
+	) {
305
+		// true if reinstating cancelled or declined registration
306
+		if (
307
+			in_array($old_STS_ID, $closed_reg_statuses, true)
308
+			&& ! in_array($new_STS_ID, $closed_reg_statuses, true)
309
+		) {
310
+			/** @type EE_Registration_Processor $registration_processor */
311
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
312
+			/** @type EE_Transaction_Processor $transaction_processor */
313
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
314
+			// reinstating cancelled or declined registration
315
+			$registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
316
+				$this,
317
+				$closed_reg_statuses
318
+			);
319
+			$transaction_processor->update_transaction_after_reinstating_canceled_registration(
320
+				$this,
321
+				$closed_reg_statuses,
322
+				false
323
+			);
324
+			do_action(
325
+				'AHEE__EE_Registration__set_status__after_reinstated',
326
+				$this,
327
+				$old_STS_ID,
328
+				$new_STS_ID,
329
+				$context
330
+			);
331
+		}
332
+	}
333
+
334
+
335
+	/**
336
+	 * @param ContextInterface|null $context
337
+	 * @return bool
338
+	 */
339
+	private function statusChangeUpdatesTransaction(ContextInterface $context = null)
340
+	{
341
+		$contexts_that_do_not_update_transaction = (array) apply_filters(
342
+			'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
343
+			array('spco_reg_step_attendee_information_process_registrations'),
344
+			$context,
345
+			$this
346
+		);
347
+		return ! (
348
+			$context instanceof ContextInterface
349
+			&& in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
350
+		);
351
+	}
352
+
353
+
354
+	/**
355
+	 * @throws EE_Error
356
+	 * @throws EntityNotFoundException
357
+	 * @throws InvalidArgumentException
358
+	 * @throws InvalidDataTypeException
359
+	 * @throws InvalidInterfaceException
360
+	 * @throws ReflectionException
361
+	 * @throws RuntimeException
362
+	 */
363
+	private function updateTransactionAfterStatusChange()
364
+	{
365
+		/** @type EE_Transaction_Payments $transaction_payments */
366
+		$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
367
+		$transaction_payments->recalculate_transaction_total($this->transaction(), false);
368
+		$this->transaction()->update_status_based_on_total_paid(true);
369
+	}
370
+
371
+
372
+	/**
373
+	 *        get Status ID
374
+	 */
375
+	public function status_ID()
376
+	{
377
+		return $this->get('STS_ID');
378
+	}
379
+
380
+
381
+	/**
382
+	 * Gets the ticket this registration is for
383
+	 *
384
+	 * @param boolean $include_archived whether to include archived tickets or not.
385
+	 *
386
+	 * @return EE_Ticket|EE_Base_Class
387
+	 * @throws EE_Error
388
+	 */
389
+	public function ticket($include_archived = true)
390
+	{
391
+		$query_params = array();
392
+		if ($include_archived) {
393
+			$query_params['default_where_conditions'] = 'none';
394
+		}
395
+		return $this->get_first_related('Ticket', $query_params);
396
+	}
397
+
398
+
399
+	/**
400
+	 * Gets the event this registration is for
401
+	 *
402
+	 * @return EE_Event
403
+	 * @throws EE_Error
404
+	 * @throws EntityNotFoundException
405
+	 */
406
+	public function event()
407
+	{
408
+		$event = $this->get_first_related('Event');
409
+		if (! $event instanceof \EE_Event) {
410
+			throw new EntityNotFoundException('Event ID', $this->event_ID());
411
+		}
412
+		return $event;
413
+	}
414
+
415
+
416
+	/**
417
+	 * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
418
+	 * with the author of the event this registration is for.
419
+	 *
420
+	 * @since 4.5.0
421
+	 * @return int
422
+	 * @throws EE_Error
423
+	 * @throws EntityNotFoundException
424
+	 */
425
+	public function wp_user()
426
+	{
427
+		$event = $this->event();
428
+		if ($event instanceof EE_Event) {
429
+			return $event->wp_user();
430
+		}
431
+		return 0;
432
+	}
433
+
434
+
435
+	/**
436
+	 * increments this registration's related ticket sold and corresponding datetime sold values
437
+	 *
438
+	 * @return void
439
+	 * @throws DomainException
440
+	 * @throws EE_Error
441
+	 * @throws EntityNotFoundException
442
+	 * @throws InvalidArgumentException
443
+	 * @throws InvalidDataTypeException
444
+	 * @throws InvalidInterfaceException
445
+	 * @throws ReflectionException
446
+	 * @throws UnexpectedEntityException
447
+	 */
448
+	private function reserveRegistrationSpace()
449
+	{
450
+		// reserved ticket and datetime counts will be decremented as sold counts are incremented
451
+		// so stop tracking that this reg has a ticket reserved
452
+		$this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
453
+		$ticket = $this->ticket();
454
+		$ticket->increaseSold();
455
+		// possibly set event status to sold out
456
+		$this->event()->perform_sold_out_status_check();
457
+	}
458
+
459
+
460
+	/**
461
+	 * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
462
+	 *
463
+	 * @return void
464
+	 * @throws DomainException
465
+	 * @throws EE_Error
466
+	 * @throws EntityNotFoundException
467
+	 * @throws InvalidArgumentException
468
+	 * @throws InvalidDataTypeException
469
+	 * @throws InvalidInterfaceException
470
+	 * @throws ReflectionException
471
+	 * @throws UnexpectedEntityException
472
+	 */
473
+	private function releaseRegistrationSpace()
474
+	{
475
+		$ticket = $this->ticket();
476
+		$ticket->decreaseSold();
477
+		// possibly change event status from sold out back to previous status
478
+		$this->event()->perform_sold_out_status_check();
479
+	}
480
+
481
+
482
+	/**
483
+	 * tracks this registration's ticket reservation in extra meta
484
+	 * and can increment related ticket reserved and corresponding datetime reserved values
485
+	 *
486
+	 * @param bool $update_ticket if true, will increment ticket and datetime reserved count
487
+	 * @return void
488
+	 * @throws EE_Error
489
+	 * @throws InvalidArgumentException
490
+	 * @throws InvalidDataTypeException
491
+	 * @throws InvalidInterfaceException
492
+	 * @throws ReflectionException
493
+	 */
494
+	public function reserve_ticket($update_ticket = false, $source = 'unknown')
495
+	{
496
+		// only reserve ticket if space is not currently reserved
497
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
498
+			$this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
499
+			// IMPORTANT !!!
500
+			// although checking $update_ticket first would be more efficient,
501
+			// we NEED to ALWAYS call update_extra_meta(), which is why that is done first
502
+			if (
503
+				$this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true)
504
+				&& $update_ticket
505
+			) {
506
+				$ticket = $this->ticket();
507
+				$ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
508
+				$ticket->save();
509
+			}
510
+		}
511
+	}
512
+
513
+
514
+	/**
515
+	 * stops tracking this registration's ticket reservation in extra meta
516
+	 * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
517
+	 *
518
+	 * @param bool $update_ticket if true, will decrement ticket and datetime reserved count
519
+	 * @return void
520
+	 * @throws EE_Error
521
+	 * @throws InvalidArgumentException
522
+	 * @throws InvalidDataTypeException
523
+	 * @throws InvalidInterfaceException
524
+	 * @throws ReflectionException
525
+	 */
526
+	public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
527
+	{
528
+		// only release ticket if space is currently reserved
529
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
530
+			$this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
531
+			// IMPORTANT !!!
532
+			// although checking $update_ticket first would be more efficient,
533
+			// we NEED to ALWAYS call update_extra_meta(), which is why that is done first
534
+			if (
535
+				$this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false)
536
+				&& $update_ticket
537
+			) {
538
+				$ticket = $this->ticket();
539
+				$ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
540
+			}
541
+		}
542
+	}
543
+
544
+
545
+	/**
546
+	 * Set Attendee ID
547
+	 *
548
+	 * @param        int $ATT_ID Attendee ID
549
+	 * @throws EE_Error
550
+	 * @throws RuntimeException
551
+	 */
552
+	public function set_attendee_id($ATT_ID = 0)
553
+	{
554
+		$this->set('ATT_ID', $ATT_ID);
555
+	}
556
+
557
+
558
+	/**
559
+	 *        Set Transaction ID
560
+	 *
561
+	 * @param        int $TXN_ID Transaction ID
562
+	 * @throws EE_Error
563
+	 * @throws RuntimeException
564
+	 */
565
+	public function set_transaction_id($TXN_ID = 0)
566
+	{
567
+		$this->set('TXN_ID', $TXN_ID);
568
+	}
569
+
570
+
571
+	/**
572
+	 *        Set Session
573
+	 *
574
+	 * @param    string $REG_session PHP Session ID
575
+	 * @throws EE_Error
576
+	 * @throws RuntimeException
577
+	 */
578
+	public function set_session($REG_session = '')
579
+	{
580
+		$this->set('REG_session', $REG_session);
581
+	}
582
+
583
+
584
+	/**
585
+	 *        Set Registration URL Link
586
+	 *
587
+	 * @param    string $REG_url_link Registration URL Link
588
+	 * @throws EE_Error
589
+	 * @throws RuntimeException
590
+	 */
591
+	public function set_reg_url_link($REG_url_link = '')
592
+	{
593
+		$this->set('REG_url_link', $REG_url_link);
594
+	}
595
+
596
+
597
+	/**
598
+	 *        Set Attendee Counter
599
+	 *
600
+	 * @param        int $REG_count Primary Attendee
601
+	 * @throws EE_Error
602
+	 * @throws RuntimeException
603
+	 */
604
+	public function set_count($REG_count = 1)
605
+	{
606
+		$this->set('REG_count', $REG_count);
607
+	}
608
+
609
+
610
+	/**
611
+	 *        Set Group Size
612
+	 *
613
+	 * @param        boolean $REG_group_size Group Registration
614
+	 * @throws EE_Error
615
+	 * @throws RuntimeException
616
+	 */
617
+	public function set_group_size($REG_group_size = false)
618
+	{
619
+		$this->set('REG_group_size', $REG_group_size);
620
+	}
621
+
622
+
623
+	/**
624
+	 *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
625
+	 *    EEM_Registration::status_id_not_approved
626
+	 *
627
+	 * @return        boolean
628
+	 */
629
+	public function is_not_approved()
630
+	{
631
+		return $this->status_ID() == EEM_Registration::status_id_not_approved ? true : false;
632
+	}
633
+
634
+
635
+	/**
636
+	 *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
637
+	 *    EEM_Registration::status_id_pending_payment
638
+	 *
639
+	 * @return        boolean
640
+	 */
641
+	public function is_pending_payment()
642
+	{
643
+		return $this->status_ID() == EEM_Registration::status_id_pending_payment ? true : false;
644
+	}
645
+
646
+
647
+	/**
648
+	 *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
649
+	 *
650
+	 * @return        boolean
651
+	 */
652
+	public function is_approved()
653
+	{
654
+		return $this->status_ID() == EEM_Registration::status_id_approved ? true : false;
655
+	}
656
+
657
+
658
+	/**
659
+	 *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
660
+	 *
661
+	 * @return        boolean
662
+	 */
663
+	public function is_cancelled()
664
+	{
665
+		return $this->status_ID() == EEM_Registration::status_id_cancelled ? true : false;
666
+	}
667
+
668
+
669
+	/**
670
+	 *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
671
+	 *
672
+	 * @return        boolean
673
+	 */
674
+	public function is_declined()
675
+	{
676
+		return $this->status_ID() == EEM_Registration::status_id_declined ? true : false;
677
+	}
678
+
679
+
680
+	/**
681
+	 *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
682
+	 *    EEM_Registration::status_id_incomplete
683
+	 *
684
+	 * @return        boolean
685
+	 */
686
+	public function is_incomplete()
687
+	{
688
+		return $this->status_ID() == EEM_Registration::status_id_incomplete ? true : false;
689
+	}
690
+
691
+
692
+	/**
693
+	 *        Set Registration Date
694
+	 *
695
+	 * @param        mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
696
+	 *                                                 Date
697
+	 * @throws EE_Error
698
+	 * @throws RuntimeException
699
+	 */
700
+	public function set_reg_date($REG_date = false)
701
+	{
702
+		$this->set('REG_date', $REG_date);
703
+	}
704
+
705
+
706
+	/**
707
+	 *    Set final price owing for this registration after all ticket/price modifications
708
+	 *
709
+	 * @access    public
710
+	 * @param    float $REG_final_price
711
+	 * @throws EE_Error
712
+	 * @throws RuntimeException
713
+	 */
714
+	public function set_final_price($REG_final_price = 0.00)
715
+	{
716
+		$this->set('REG_final_price', $REG_final_price);
717
+	}
718
+
719
+
720
+	/**
721
+	 *    Set amount paid towards this registration's final price
722
+	 *
723
+	 * @access    public
724
+	 * @param    float $REG_paid
725
+	 * @throws EE_Error
726
+	 * @throws RuntimeException
727
+	 */
728
+	public function set_paid($REG_paid = 0.00)
729
+	{
730
+		$this->set('REG_paid', $REG_paid);
731
+	}
732
+
733
+
734
+	/**
735
+	 *        Attendee Is Going
736
+	 *
737
+	 * @param        boolean $REG_att_is_going Attendee Is Going
738
+	 * @throws EE_Error
739
+	 * @throws RuntimeException
740
+	 */
741
+	public function set_att_is_going($REG_att_is_going = false)
742
+	{
743
+		$this->set('REG_att_is_going', $REG_att_is_going);
744
+	}
745
+
746
+
747
+	/**
748
+	 * Gets the related attendee
749
+	 *
750
+	 * @return EE_Attendee
751
+	 * @throws EE_Error
752
+	 */
753
+	public function attendee()
754
+	{
755
+		return $this->get_first_related('Attendee');
756
+	}
757
+
758
+	/**
759
+	 * Gets the name of the attendee.
760
+	 * @since $VID:$
761
+	 * @param bool $apply_html_entities set to true if you want to use HTML entities.
762
+	 * @return string
763
+	 * @throws EE_Error
764
+	 * @throws InvalidArgumentException
765
+	 * @throws InvalidDataTypeException
766
+	 * @throws InvalidInterfaceException
767
+	 * @throws ReflectionException
768
+	 */
769
+	public function attendeeName($apply_html_entities = false)
770
+	{
771
+		$attendee = $this->get_first_related('Attendee');
772
+		if ($attendee instanceof EE_Attendee) {
773
+			$attendee_name = $attendee->full_name($apply_html_entities);
774
+		} else {
775
+			$attendee_name = esc_html__('Unknown', 'event_espresso');
776
+		}
777
+		return $attendee_name;
778
+	}
779
+
780
+
781
+	/**
782
+	 *        get Event ID
783
+	 */
784
+	public function event_ID()
785
+	{
786
+		return $this->get('EVT_ID');
787
+	}
788
+
789
+
790
+	/**
791
+	 *        get Event ID
792
+	 */
793
+	public function event_name()
794
+	{
795
+		$event = $this->event_obj();
796
+		if ($event) {
797
+			return $event->name();
798
+		} else {
799
+			return null;
800
+		}
801
+	}
802
+
803
+
804
+	/**
805
+	 * Fetches the event this registration is for
806
+	 *
807
+	 * @return EE_Event
808
+	 * @throws EE_Error
809
+	 */
810
+	public function event_obj()
811
+	{
812
+		return $this->get_first_related('Event');
813
+	}
814
+
815
+
816
+	/**
817
+	 *        get Attendee ID
818
+	 */
819
+	public function attendee_ID()
820
+	{
821
+		return $this->get('ATT_ID');
822
+	}
823
+
824
+
825
+	/**
826
+	 *        get PHP Session ID
827
+	 */
828
+	public function session_ID()
829
+	{
830
+		return $this->get('REG_session');
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets the string which represents the URL trigger for the receipt template in the message template system.
836
+	 *
837
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
838
+	 * @return string
839
+	 */
840
+	public function receipt_url($messenger = 'html')
841
+	{
842
+
843
+		/**
844
+		 * The below will be deprecated one version after this.  We check first if there is a custom receipt 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/receipt_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
+			return add_query_arg(array('receipt' => 'true'), $this->invoice_url('launch'));
860
+		}
861
+		return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
862
+	}
863
+
864
+
865
+	/**
866
+	 * Gets the string which represents the URL trigger for the invoice template in the message template system.
867
+	 *
868
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
869
+	 * @return string
870
+	 * @throws EE_Error
871
+	 */
872
+	public function invoice_url($messenger = 'html')
873
+	{
874
+		/**
875
+		 * The below will be deprecated one version after this.  We check first if there is a custom invoice template
876
+		 * already in use on old system.  If there is then we just return the standard url for it.
877
+		 *
878
+		 * @since 4.5.0
879
+		 */
880
+		$template_relative_path = 'modules/gateways/Invoice/lib/templates/invoice_body.template.php';
881
+		$has_custom = EEH_Template::locate_template(
882
+			$template_relative_path,
883
+			array(),
884
+			true,
885
+			true,
886
+			true
887
+		);
888
+
889
+		if ($has_custom) {
890
+			if ($messenger == 'html') {
891
+				return $this->invoice_url('launch');
892
+			}
893
+			$route = $messenger == 'download' || $messenger == 'pdf' ? 'download_invoice' : 'launch_invoice';
894
+
895
+			$query_args = array('ee' => $route, 'id' => $this->reg_url_link());
896
+			if ($messenger == 'html') {
897
+				$query_args['html'] = true;
898
+			}
899
+			return add_query_arg($query_args, get_permalink(EE_Registry::instance()->CFG->core->thank_you_page_id));
900
+		}
901
+		return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
902
+	}
903
+
904
+
905
+	/**
906
+	 * get Registration URL Link
907
+	 *
908
+	 * @access public
909
+	 * @return string
910
+	 * @throws EE_Error
911
+	 */
912
+	public function reg_url_link()
913
+	{
914
+		return (string) $this->get('REG_url_link');
915
+	}
916
+
917
+
918
+	/**
919
+	 * Echoes out invoice_url()
920
+	 *
921
+	 * @param string $type 'download','launch', or 'html' (default is 'launch')
922
+	 * @return void
923
+	 * @throws EE_Error
924
+	 */
925
+	public function e_invoice_url($type = 'launch')
926
+	{
927
+		echo $this->invoice_url($type);
928
+	}
929
+
930
+
931
+	/**
932
+	 * Echoes out payment_overview_url
933
+	 */
934
+	public function e_payment_overview_url()
935
+	{
936
+		echo $this->payment_overview_url();
937
+	}
938
+
939
+
940
+	/**
941
+	 * Gets the URL for the checkout payment options reg step
942
+	 * with this registration's REG_url_link added as a query parameter
943
+	 *
944
+	 * @param bool $clear_session Set to true when you want to clear the session on revisiting the
945
+	 *                            payment overview url.
946
+	 * @return string
947
+	 * @throws InvalidInterfaceException
948
+	 * @throws InvalidDataTypeException
949
+	 * @throws EE_Error
950
+	 * @throws InvalidArgumentException
951
+	 */
952
+	public function payment_overview_url($clear_session = false)
953
+	{
954
+		return add_query_arg(
955
+			(array) apply_filters(
956
+				'FHEE__EE_Registration__payment_overview_url__query_args',
957
+				array(
958
+					'e_reg_url_link' => $this->reg_url_link(),
959
+					'step'           => 'payment_options',
960
+					'revisit'        => true,
961
+					'clear_session'  => (bool) $clear_session,
962
+				),
963
+				$this
964
+			),
965
+			EE_Registry::instance()->CFG->core->reg_page_url()
966
+		);
967
+	}
968
+
969
+
970
+	/**
971
+	 * Gets the URL for the checkout attendee information reg step
972
+	 * with this registration's REG_url_link added as a query parameter
973
+	 *
974
+	 * @return string
975
+	 * @throws InvalidInterfaceException
976
+	 * @throws InvalidDataTypeException
977
+	 * @throws EE_Error
978
+	 * @throws InvalidArgumentException
979
+	 */
980
+	public function edit_attendee_information_url()
981
+	{
982
+		return add_query_arg(
983
+			(array) apply_filters(
984
+				'FHEE__EE_Registration__edit_attendee_information_url__query_args',
985
+				array(
986
+					'e_reg_url_link' => $this->reg_url_link(),
987
+					'step'           => 'attendee_information',
988
+					'revisit'        => true,
989
+				),
990
+				$this
991
+			),
992
+			EE_Registry::instance()->CFG->core->reg_page_url()
993
+		);
994
+	}
995
+
996
+
997
+	/**
998
+	 * Simply generates and returns the appropriate admin_url link to edit this registration
999
+	 *
1000
+	 * @return string
1001
+	 * @throws EE_Error
1002
+	 */
1003
+	public function get_admin_edit_url()
1004
+	{
1005
+		return EEH_URL::add_query_args_and_nonce(
1006
+			array(
1007
+				'page'    => 'espresso_registrations',
1008
+				'action'  => 'view_registration',
1009
+				'_REG_ID' => $this->ID(),
1010
+			),
1011
+			admin_url('admin.php')
1012
+		);
1013
+	}
1014
+
1015
+
1016
+	/**
1017
+	 *    is_primary_registrant?
1018
+	 */
1019
+	public function is_primary_registrant()
1020
+	{
1021
+		return $this->get('REG_count') === 1 ? true : false;
1022
+	}
1023
+
1024
+
1025
+	/**
1026
+	 * This returns the primary registration object for this registration group (which may be this object).
1027
+	 *
1028
+	 * @return EE_Registration
1029
+	 * @throws EE_Error
1030
+	 */
1031
+	public function get_primary_registration()
1032
+	{
1033
+		if ($this->is_primary_registrant()) {
1034
+			return $this;
1035
+		}
1036
+
1037
+		// k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1038
+		/** @var EE_Registration $primary_registrant */
1039
+		$primary_registrant = EEM_Registration::instance()->get_one(
1040
+			array(
1041
+				array(
1042
+					'TXN_ID'    => $this->transaction_ID(),
1043
+					'REG_count' => 1,
1044
+				),
1045
+			)
1046
+		);
1047
+		return $primary_registrant;
1048
+	}
1049
+
1050
+
1051
+	/**
1052
+	 *        get  Attendee Number
1053
+	 *
1054
+	 * @access        public
1055
+	 */
1056
+	public function count()
1057
+	{
1058
+		return $this->get('REG_count');
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 *        get Group Size
1064
+	 */
1065
+	public function group_size()
1066
+	{
1067
+		return $this->get('REG_group_size');
1068
+	}
1069
+
1070
+
1071
+	/**
1072
+	 *        get Registration Date
1073
+	 */
1074
+	public function date()
1075
+	{
1076
+		return $this->get('REG_date');
1077
+	}
1078
+
1079
+
1080
+	/**
1081
+	 * gets a pretty date
1082
+	 *
1083
+	 * @param string $date_format
1084
+	 * @param string $time_format
1085
+	 * @return string
1086
+	 * @throws EE_Error
1087
+	 */
1088
+	public function pretty_date($date_format = null, $time_format = null)
1089
+	{
1090
+		return $this->get_datetime('REG_date', $date_format, $time_format);
1091
+	}
1092
+
1093
+
1094
+	/**
1095
+	 * final_price
1096
+	 * the registration's share of the transaction total, so that the
1097
+	 * sum of all the transaction's REG_final_prices equal the transaction's total
1098
+	 *
1099
+	 * @return float
1100
+	 * @throws EE_Error
1101
+	 */
1102
+	public function final_price()
1103
+	{
1104
+		return $this->get('REG_final_price');
1105
+	}
1106
+
1107
+
1108
+	/**
1109
+	 * pretty_final_price
1110
+	 *  final price as formatted string, with correct decimal places and currency symbol
1111
+	 *
1112
+	 * @return string
1113
+	 * @throws EE_Error
1114
+	 */
1115
+	public function pretty_final_price()
1116
+	{
1117
+		return $this->get_pretty('REG_final_price');
1118
+	}
1119
+
1120
+
1121
+	/**
1122
+	 * get paid (yeah)
1123
+	 *
1124
+	 * @return float
1125
+	 * @throws EE_Error
1126
+	 */
1127
+	public function paid()
1128
+	{
1129
+		return $this->get('REG_paid');
1130
+	}
1131
+
1132
+
1133
+	/**
1134
+	 * pretty_paid
1135
+	 *
1136
+	 * @return float
1137
+	 * @throws EE_Error
1138
+	 */
1139
+	public function pretty_paid()
1140
+	{
1141
+		return $this->get_pretty('REG_paid');
1142
+	}
1143
+
1144
+
1145
+	/**
1146
+	 * owes_monies_and_can_pay
1147
+	 * whether or not this registration has monies owing and it's' status allows payment
1148
+	 *
1149
+	 * @param array $requires_payment
1150
+	 * @return bool
1151
+	 * @throws EE_Error
1152
+	 */
1153
+	public function owes_monies_and_can_pay($requires_payment = array())
1154
+	{
1155
+		// these reg statuses require payment (if event is not free)
1156
+		$requires_payment = ! empty($requires_payment)
1157
+			? $requires_payment
1158
+			: EEM_Registration::reg_statuses_that_allow_payment();
1159
+		if (
1160
+			in_array($this->status_ID(), $requires_payment) &&
1161
+			$this->final_price() != 0 &&
1162
+			$this->final_price() != $this->paid()
1163
+		) {
1164
+			return true;
1165
+		} else {
1166
+			return false;
1167
+		}
1168
+	}
1169
+
1170
+
1171
+	/**
1172
+	 * Prints out the return value of $this->pretty_status()
1173
+	 *
1174
+	 * @param bool $show_icons
1175
+	 * @return void
1176
+	 * @throws EE_Error
1177
+	 */
1178
+	public function e_pretty_status($show_icons = false)
1179
+	{
1180
+		echo $this->pretty_status($show_icons);
1181
+	}
1182
+
1183
+
1184
+	/**
1185
+	 * Returns a nice version of the status for displaying to customers
1186
+	 *
1187
+	 * @param bool $show_icons
1188
+	 * @return string
1189
+	 * @throws EE_Error
1190
+	 */
1191
+	public function pretty_status($show_icons = false)
1192
+	{
1193
+		$status = EEM_Status::instance()->localized_status(
1194
+			array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
1195
+			false,
1196
+			'sentence'
1197
+		);
1198
+		$icon = '';
1199
+		switch ($this->status_ID()) {
1200
+			case EEM_Registration::status_id_approved:
1201
+				$icon = $show_icons
1202
+					? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1203
+					: '';
1204
+				break;
1205
+			case EEM_Registration::status_id_pending_payment:
1206
+				$icon = $show_icons
1207
+					? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1208
+					: '';
1209
+				break;
1210
+			case EEM_Registration::status_id_not_approved:
1211
+				$icon = $show_icons
1212
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1213
+					: '';
1214
+				break;
1215
+			case EEM_Registration::status_id_cancelled:
1216
+				$icon = $show_icons
1217
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1218
+					: '';
1219
+				break;
1220
+			case EEM_Registration::status_id_incomplete:
1221
+				$icon = $show_icons
1222
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1223
+					: '';
1224
+				break;
1225
+			case EEM_Registration::status_id_declined:
1226
+				$icon = $show_icons
1227
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1228
+					: '';
1229
+				break;
1230
+			case EEM_Registration::status_id_wait_list:
1231
+				$icon = $show_icons
1232
+					? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1233
+					: '';
1234
+				break;
1235
+		}
1236
+		return $icon . $status[ $this->status_ID() ];
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 *        get Attendee Is Going
1242
+	 */
1243
+	public function att_is_going()
1244
+	{
1245
+		return $this->get('REG_att_is_going');
1246
+	}
1247
+
1248
+
1249
+	/**
1250
+	 * Gets related answers
1251
+	 *
1252
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1253
+	 * @return EE_Answer[]
1254
+	 * @throws EE_Error
1255
+	 */
1256
+	public function answers($query_params = null)
1257
+	{
1258
+		return $this->get_many_related('Answer', $query_params);
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Gets the registration's answer value to the specified question
1264
+	 * (either the question's ID or a question object)
1265
+	 *
1266
+	 * @param EE_Question|int $question
1267
+	 * @param bool            $pretty_value
1268
+	 * @return array|string if pretty_value= true, the result will always be a string
1269
+	 * (because the answer might be an array of answer values, so passing pretty_value=true
1270
+	 * will convert it into some kind of string)
1271
+	 * @throws EE_Error
1272
+	 */
1273
+	public function answer_value_to_question($question, $pretty_value = true)
1274
+	{
1275
+		$question_id = EEM_Question::instance()->ensure_is_ID($question);
1276
+		return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1277
+	}
1278
+
1279
+
1280
+	/**
1281
+	 * question_groups
1282
+	 * returns an array of EE_Question_Group objects for this registration
1283
+	 *
1284
+	 * @return EE_Question_Group[]
1285
+	 * @throws EE_Error
1286
+	 * @throws InvalidArgumentException
1287
+	 * @throws InvalidDataTypeException
1288
+	 * @throws InvalidInterfaceException
1289
+	 * @throws ReflectionException
1290
+	 */
1291
+	public function question_groups()
1292
+	{
1293
+		return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1294
+	}
1295
+
1296
+
1297
+	/**
1298
+	 * count_question_groups
1299
+	 * returns a count of the number of EE_Question_Group objects for this registration
1300
+	 *
1301
+	 * @return int
1302
+	 * @throws EE_Error
1303
+	 * @throws EntityNotFoundException
1304
+	 * @throws InvalidArgumentException
1305
+	 * @throws InvalidDataTypeException
1306
+	 * @throws InvalidInterfaceException
1307
+	 * @throws ReflectionException
1308
+	 */
1309
+	public function count_question_groups()
1310
+	{
1311
+		return EEM_Event::instance()->count_related(
1312
+			$this->event_ID(),
1313
+			'Question_Group',
1314
+			[
1315
+				[
1316
+					'Event_Question_Group.'
1317
+					. EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1318
+				]
1319
+			]
1320
+		);
1321
+	}
1322
+
1323
+
1324
+	/**
1325
+	 * Returns the registration date in the 'standard' string format
1326
+	 * (function may be improved in the future to allow for different formats and timezones)
1327
+	 *
1328
+	 * @return string
1329
+	 * @throws EE_Error
1330
+	 */
1331
+	public function reg_date()
1332
+	{
1333
+		return $this->get_datetime('REG_date');
1334
+	}
1335
+
1336
+
1337
+	/**
1338
+	 * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1339
+	 * the ticket this registration purchased, or the datetime they have registered
1340
+	 * to attend)
1341
+	 *
1342
+	 * @return EE_Datetime_Ticket
1343
+	 * @throws EE_Error
1344
+	 */
1345
+	public function datetime_ticket()
1346
+	{
1347
+		return $this->get_first_related('Datetime_Ticket');
1348
+	}
1349
+
1350
+
1351
+	/**
1352
+	 * Sets the registration's datetime_ticket.
1353
+	 *
1354
+	 * @param EE_Datetime_Ticket $datetime_ticket
1355
+	 * @return EE_Datetime_Ticket
1356
+	 * @throws EE_Error
1357
+	 */
1358
+	public function set_datetime_ticket($datetime_ticket)
1359
+	{
1360
+		return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1361
+	}
1362
+
1363
+	/**
1364
+	 * Gets deleted
1365
+	 *
1366
+	 * @return bool
1367
+	 * @throws EE_Error
1368
+	 */
1369
+	public function deleted()
1370
+	{
1371
+		return $this->get('REG_deleted');
1372
+	}
1373
+
1374
+	/**
1375
+	 * Sets deleted
1376
+	 *
1377
+	 * @param boolean $deleted
1378
+	 * @return bool
1379
+	 * @throws EE_Error
1380
+	 * @throws RuntimeException
1381
+	 */
1382
+	public function set_deleted($deleted)
1383
+	{
1384
+		if ($deleted) {
1385
+			$this->delete();
1386
+		} else {
1387
+			$this->restore();
1388
+		}
1389
+	}
1390
+
1391
+
1392
+	/**
1393
+	 * Get the status object of this object
1394
+	 *
1395
+	 * @return EE_Status
1396
+	 * @throws EE_Error
1397
+	 */
1398
+	public function status_obj()
1399
+	{
1400
+		return $this->get_first_related('Status');
1401
+	}
1402
+
1403
+
1404
+	/**
1405
+	 * Returns the number of times this registration has checked into any of the datetimes
1406
+	 * its available for
1407
+	 *
1408
+	 * @return int
1409
+	 * @throws EE_Error
1410
+	 */
1411
+	public function count_checkins()
1412
+	{
1413
+		return $this->get_model()->count_related($this, 'Checkin');
1414
+	}
1415
+
1416
+
1417
+	/**
1418
+	 * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1419
+	 * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1420
+	 *
1421
+	 * @return int
1422
+	 * @throws EE_Error
1423
+	 */
1424
+	public function count_checkins_not_checkedout()
1425
+	{
1426
+		return $this->get_model()->count_related($this, 'Checkin', array(array('CHK_in' => 1)));
1427
+	}
1428
+
1429
+
1430
+	/**
1431
+	 * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1432
+	 *
1433
+	 * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1434
+	 * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1435
+	 *                                          consider registration status as well as datetime access.
1436
+	 * @return bool
1437
+	 * @throws EE_Error
1438
+	 */
1439
+	public function can_checkin($DTT_OR_ID, $check_approved = true)
1440
+	{
1441
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1442
+
1443
+		// first check registration status
1444
+		if (($check_approved && ! $this->is_approved()) || ! $DTT_ID) {
1445
+			return false;
1446
+		}
1447
+		// is there a datetime ticket that matches this dtt_ID?
1448
+		if (
1449
+			! (EEM_Datetime_Ticket::instance()->exists(
1450
+				array(
1451
+				array(
1452
+					'TKT_ID' => $this->get('TKT_ID'),
1453
+					'DTT_ID' => $DTT_ID,
1454
+				),
1455
+				)
1456
+			))
1457
+		) {
1458
+			return false;
1459
+		}
1460
+
1461
+		// final check is against TKT_uses
1462
+		return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1463
+	}
1464
+
1465
+
1466
+	/**
1467
+	 * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1468
+	 * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1469
+	 * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1470
+	 * then return false.  Otherwise return true.
1471
+	 *
1472
+	 * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1473
+	 * @return bool true means can checkin.  false means cannot checkin.
1474
+	 * @throws EE_Error
1475
+	 */
1476
+	public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1477
+	{
1478
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1479
+
1480
+		if (! $DTT_ID) {
1481
+			return false;
1482
+		}
1483
+
1484
+		$max_uses = $this->ticket() instanceof EE_Ticket ? $this->ticket()->uses() : EE_INF;
1485
+
1486
+		// if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1487
+		// check-in or not.
1488
+		if (! $max_uses || $max_uses === EE_INF) {
1489
+			return true;
1490
+		}
1491
+
1492
+		// does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1493
+		// go ahead and toggle.
1494
+		if (EEM_Checkin::instance()->exists(array(array('REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID)))) {
1495
+			return true;
1496
+		}
1497
+
1498
+		// made it here so the last check is whether the number of checkins per unique datetime on this registration
1499
+		// disallows further check-ins.
1500
+		$count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1501
+			array(
1502
+				array(
1503
+					'REG_ID' => $this->ID(),
1504
+					'CHK_in' => true,
1505
+				),
1506
+			),
1507
+			'DTT_ID',
1508
+			true
1509
+		);
1510
+		// checkins have already reached their max number of uses
1511
+		// so registrant can NOT checkin
1512
+		if ($count_unique_dtt_checkins >= $max_uses) {
1513
+			EE_Error::add_error(
1514
+				esc_html__(
1515
+					'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1516
+					'event_espresso'
1517
+				),
1518
+				__FILE__,
1519
+				__FUNCTION__,
1520
+				__LINE__
1521
+			);
1522
+			return false;
1523
+		}
1524
+		return true;
1525
+	}
1526
+
1527
+
1528
+	/**
1529
+	 * toggle Check-in status for this registration
1530
+	 * Check-ins are toggled in the following order:
1531
+	 * never checked in -> checked in
1532
+	 * checked in -> checked out
1533
+	 * checked out -> checked in
1534
+	 *
1535
+	 * @param  int $DTT_ID  include specific datetime to toggle Check-in for.
1536
+	 *                      If not included or null, then it is assumed latest datetime is being toggled.
1537
+	 * @param bool $verify  If true then can_checkin() is used to verify whether the person
1538
+	 *                      can be checked in or not.  Otherwise this forces change in checkin status.
1539
+	 * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1540
+	 * @throws EE_Error
1541
+	 */
1542
+	public function toggle_checkin_status($DTT_ID = null, $verify = false)
1543
+	{
1544
+		if (empty($DTT_ID)) {
1545
+			$datetime = $this->get_latest_related_datetime();
1546
+			$DTT_ID = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1547
+			// verify the registration can checkin for the given DTT_ID
1548
+		} elseif (! $this->can_checkin($DTT_ID, $verify)) {
1549
+			EE_Error::add_error(
1550
+				sprintf(
1551
+					esc_html__(
1552
+						'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',
1553
+						'event_espresso'
1554
+					),
1555
+					$this->ID(),
1556
+					$DTT_ID
1557
+				),
1558
+				__FILE__,
1559
+				__FUNCTION__,
1560
+				__LINE__
1561
+			);
1562
+			return false;
1563
+		}
1564
+		$status_paths = array(
1565
+			EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1566
+			EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1567
+			EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1568
+		);
1569
+		// start by getting the current status so we know what status we'll be changing to.
1570
+		$cur_status = $this->check_in_status_for_datetime($DTT_ID, null);
1571
+		$status_to = $status_paths[ $cur_status ];
1572
+		// database only records true for checked IN or false for checked OUT
1573
+		// no record ( null ) means checked in NEVER, but we obviously don't save that
1574
+		$new_status = $status_to === EE_Checkin::status_checked_in ? true : false;
1575
+		// add relation - note Check-ins are always creating new rows
1576
+		// because we are keeping track of Check-ins over time.
1577
+		// Eventually we'll probably want to show a list table
1578
+		// for the individual Check-ins so that they can be managed.
1579
+		$checkin = EE_Checkin::new_instance(
1580
+			array(
1581
+				'REG_ID' => $this->ID(),
1582
+				'DTT_ID' => $DTT_ID,
1583
+				'CHK_in' => $new_status,
1584
+			)
1585
+		);
1586
+		// if the record could not be saved then return false
1587
+		if ($checkin->save() === 0) {
1588
+			if (WP_DEBUG) {
1589
+				global $wpdb;
1590
+				$error = sprintf(
1591
+					esc_html__(
1592
+						'Registration check in update failed because of the following database error: %1$s%2$s',
1593
+						'event_espresso'
1594
+					),
1595
+					'<br />',
1596
+					$wpdb->last_error
1597
+				);
1598
+			} else {
1599
+				$error = esc_html__(
1600
+					'Registration check in update failed because of an unknown database error',
1601
+					'event_espresso'
1602
+				);
1603
+			}
1604
+			EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1605
+			return false;
1606
+		}
1607
+		// Fire a checked_in and checkout_out action.
1608
+		$checked_status = $status_to === EE_Checkin::status_checked_in ? 'checked_in' : 'checked_out';
1609
+		do_action("AHEE__EE_Registration__toggle_checkin_status__{$checked_status}", $this, $DTT_ID);
1610
+		return $status_to;
1611
+	}
1612
+
1613
+
1614
+	/**
1615
+	 * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1616
+	 * "Latest" is defined by the `DTT_EVT_start` column.
1617
+	 *
1618
+	 * @return EE_Datetime|null
1619
+	 * @throws EE_Error
1620
+	 */
1621
+	public function get_latest_related_datetime()
1622
+	{
1623
+		return EEM_Datetime::instance()->get_one(
1624
+			array(
1625
+				array(
1626
+					'Ticket.Registration.REG_ID' => $this->ID(),
1627
+				),
1628
+				'order_by' => array('DTT_EVT_start' => 'DESC'),
1629
+			)
1630
+		);
1631
+	}
1632
+
1633
+
1634
+	/**
1635
+	 * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1636
+	 * "Earliest" is defined by the `DTT_EVT_start` column.
1637
+	 *
1638
+	 * @throws EE_Error
1639
+	 */
1640
+	public function get_earliest_related_datetime()
1641
+	{
1642
+		return EEM_Datetime::instance()->get_one(
1643
+			array(
1644
+				array(
1645
+					'Ticket.Registration.REG_ID' => $this->ID(),
1646
+				),
1647
+				'order_by' => array('DTT_EVT_start' => 'ASC'),
1648
+			)
1649
+		);
1650
+	}
1651
+
1652
+
1653
+	/**
1654
+	 * This method simply returns the check-in status for this registration and the given datetime.
1655
+	 * If neither the datetime nor the checkin values are provided as arguments,
1656
+	 * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1657
+	 *
1658
+	 * @param  int       $DTT_ID  The ID of the datetime we're checking against
1659
+	 *                            (if empty we'll get the primary datetime for
1660
+	 *                            this registration (via event) and use it's ID);
1661
+	 * @param EE_Checkin $checkin If present, we use the given checkin object rather than the dtt_id.
1662
+	 *
1663
+	 * @return int                Integer representing Check-in status.
1664
+	 * @throws EE_Error
1665
+	 */
1666
+	public function check_in_status_for_datetime($DTT_ID = 0, $checkin = null)
1667
+	{
1668
+		$checkin_query_params = array(
1669
+			'order_by' => array('CHK_timestamp' => 'DESC'),
1670
+		);
1671
+
1672
+		if ($DTT_ID > 0) {
1673
+			$checkin_query_params[0] = array('DTT_ID' => $DTT_ID);
1674
+		}
1675
+
1676
+		// get checkin object (if exists)
1677
+		$checkin = $checkin instanceof EE_Checkin
1678
+			? $checkin
1679
+			: $this->get_first_related('Checkin', $checkin_query_params);
1680
+		if ($checkin instanceof EE_Checkin) {
1681
+			if ($checkin->get('CHK_in')) {
1682
+				return EE_Checkin::status_checked_in; // checked in
1683
+			}
1684
+			return EE_Checkin::status_checked_out; // had checked in but is now checked out.
1685
+		}
1686
+		return EE_Checkin::status_checked_never; // never been checked in
1687
+	}
1688
+
1689
+
1690
+	/**
1691
+	 * This method returns a localized message for the toggled Check-in message.
1692
+	 *
1693
+	 * @param  int $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1694
+	 *                     then it is assumed Check-in for primary datetime was toggled.
1695
+	 * @param bool $error  This just flags that you want an error message returned. This is put in so that the error
1696
+	 *                     message can be customized with the attendee name.
1697
+	 * @return string internationalized message
1698
+	 * @throws EE_Error
1699
+	 */
1700
+	public function get_checkin_msg($DTT_ID, $error = false)
1701
+	{
1702
+		// let's get the attendee first so we can include the name of the attendee
1703
+		$attendee = $this->get_first_related('Attendee');
1704
+		if ($attendee instanceof EE_Attendee) {
1705
+			if ($error) {
1706
+				return sprintf(__("%s's check-in status was not changed.", "event_espresso"), $attendee->full_name());
1707
+			}
1708
+			$cur_status = $this->check_in_status_for_datetime($DTT_ID);
1709
+			// what is the status message going to be?
1710
+			switch ($cur_status) {
1711
+				case EE_Checkin::status_checked_never:
1712
+					return sprintf(
1713
+						__("%s has been removed from Check-in records", "event_espresso"),
1714
+						$attendee->full_name()
1715
+					);
1716
+					break;
1717
+				case EE_Checkin::status_checked_in:
1718
+					return sprintf(__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1719
+					break;
1720
+				case EE_Checkin::status_checked_out:
1721
+					return sprintf(__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1722
+					break;
1723
+			}
1724
+		}
1725
+		return esc_html__("The check-in status could not be determined.", "event_espresso");
1726
+	}
1727
+
1728
+
1729
+	/**
1730
+	 * Returns the related EE_Transaction to this registration
1731
+	 *
1732
+	 * @return EE_Transaction
1733
+	 * @throws EE_Error
1734
+	 * @throws EntityNotFoundException
1735
+	 */
1736
+	public function transaction()
1737
+	{
1738
+		$transaction = $this->get_first_related('Transaction');
1739
+		if (! $transaction instanceof \EE_Transaction) {
1740
+			throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1741
+		}
1742
+		return $transaction;
1743
+	}
1744
+
1745
+
1746
+	/**
1747
+	 *        get Registration Code
1748
+	 */
1749
+	public function reg_code()
1750
+	{
1751
+		return $this->get('REG_code');
1752
+	}
1753
+
1754
+
1755
+	/**
1756
+	 *        get Transaction ID
1757
+	 */
1758
+	public function transaction_ID()
1759
+	{
1760
+		return $this->get('TXN_ID');
1761
+	}
1762
+
1763
+
1764
+	/**
1765
+	 * @return int
1766
+	 * @throws EE_Error
1767
+	 */
1768
+	public function ticket_ID()
1769
+	{
1770
+		return $this->get('TKT_ID');
1771
+	}
1772
+
1773
+
1774
+	/**
1775
+	 *        Set Registration Code
1776
+	 *
1777
+	 * @access    public
1778
+	 * @param    string  $REG_code Registration Code
1779
+	 * @param    boolean $use_default
1780
+	 * @throws EE_Error
1781
+	 */
1782
+	public function set_reg_code($REG_code, $use_default = false)
1783
+	{
1784
+		if (empty($REG_code)) {
1785
+			EE_Error::add_error(
1786
+				esc_html__('REG_code can not be empty.', 'event_espresso'),
1787
+				__FILE__,
1788
+				__FUNCTION__,
1789
+				__LINE__
1790
+			);
1791
+			return;
1792
+		}
1793
+		if (! $this->reg_code()) {
1794
+			parent::set('REG_code', $REG_code, $use_default);
1795
+		} else {
1796
+			EE_Error::doing_it_wrong(
1797
+				__CLASS__ . '::' . __FUNCTION__,
1798
+				esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
1799
+				'4.6.0'
1800
+			);
1801
+		}
1802
+	}
1803
+
1804
+
1805
+	/**
1806
+	 * Returns all other registrations in the same group as this registrant who have the same ticket option.
1807
+	 * Note, if you want to just get all registrations in the same transaction (group), use:
1808
+	 *    $registration->transaction()->registrations();
1809
+	 *
1810
+	 * @since 4.5.0
1811
+	 * @return EE_Registration[] or empty array if this isn't a group registration.
1812
+	 * @throws EE_Error
1813
+	 */
1814
+	public function get_all_other_registrations_in_group()
1815
+	{
1816
+		if ($this->group_size() < 2) {
1817
+			return array();
1818
+		}
1819
+
1820
+		$query[0] = array(
1821
+			'TXN_ID' => $this->transaction_ID(),
1822
+			'REG_ID' => array('!=', $this->ID()),
1823
+			'TKT_ID' => $this->ticket_ID(),
1824
+		);
1825
+		/** @var EE_Registration[] $registrations */
1826
+		$registrations = $this->get_model()->get_all($query);
1827
+		return $registrations;
1828
+	}
1829
+
1830
+	/**
1831
+	 * Return the link to the admin details for the object.
1832
+	 *
1833
+	 * @return string
1834
+	 * @throws EE_Error
1835
+	 */
1836
+	public function get_admin_details_link()
1837
+	{
1838
+		EE_Registry::instance()->load_helper('URL');
1839
+		return EEH_URL::add_query_args_and_nonce(
1840
+			array(
1841
+				'page'    => 'espresso_registrations',
1842
+				'action'  => 'view_registration',
1843
+				'_REG_ID' => $this->ID(),
1844
+			),
1845
+			admin_url('admin.php')
1846
+		);
1847
+	}
1848
+
1849
+	/**
1850
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
1851
+	 *
1852
+	 * @return string
1853
+	 * @throws EE_Error
1854
+	 */
1855
+	public function get_admin_edit_link()
1856
+	{
1857
+		return $this->get_admin_details_link();
1858
+	}
1859
+
1860
+	/**
1861
+	 * Returns the link to a settings page for the object.
1862
+	 *
1863
+	 * @return string
1864
+	 * @throws EE_Error
1865
+	 */
1866
+	public function get_admin_settings_link()
1867
+	{
1868
+		return $this->get_admin_details_link();
1869
+	}
1870
+
1871
+	/**
1872
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
1873
+	 *
1874
+	 * @return string
1875
+	 */
1876
+	public function get_admin_overview_link()
1877
+	{
1878
+		EE_Registry::instance()->load_helper('URL');
1879
+		return EEH_URL::add_query_args_and_nonce(
1880
+			array(
1881
+				'page' => 'espresso_registrations',
1882
+			),
1883
+			admin_url('admin.php')
1884
+		);
1885
+	}
1886
+
1887
+
1888
+	/**
1889
+	 * @param array $query_params
1890
+	 *
1891
+	 * @return \EE_Registration[]
1892
+	 * @throws EE_Error
1893
+	 */
1894
+	public function payments($query_params = array())
1895
+	{
1896
+		return $this->get_many_related('Payment', $query_params);
1897
+	}
1898
+
1899
+
1900
+	/**
1901
+	 * @param array $query_params
1902
+	 *
1903
+	 * @return \EE_Registration_Payment[]
1904
+	 * @throws EE_Error
1905
+	 */
1906
+	public function registration_payments($query_params = array())
1907
+	{
1908
+		return $this->get_many_related('Registration_Payment', $query_params);
1909
+	}
1910
+
1911
+
1912
+	/**
1913
+	 * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
1914
+	 * Note: if there are no payments on the registration there will be no payment method returned.
1915
+	 *
1916
+	 * @return EE_Payment_Method|null
1917
+	 */
1918
+	public function payment_method()
1919
+	{
1920
+		return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
1921
+	}
1922
+
1923
+
1924
+	/**
1925
+	 * @return \EE_Line_Item
1926
+	 * @throws EntityNotFoundException
1927
+	 * @throws EE_Error
1928
+	 */
1929
+	public function ticket_line_item()
1930
+	{
1931
+		$ticket = $this->ticket();
1932
+		$transaction = $this->transaction();
1933
+		$line_item = null;
1934
+		$ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
1935
+			$transaction->total_line_item(),
1936
+			'Ticket',
1937
+			array($ticket->ID())
1938
+		);
1939
+		foreach ($ticket_line_items as $ticket_line_item) {
1940
+			if (
1941
+				$ticket_line_item instanceof \EE_Line_Item
1942
+				&& $ticket_line_item->OBJ_type() === 'Ticket'
1943
+				&& $ticket_line_item->OBJ_ID() === $ticket->ID()
1944
+			) {
1945
+				$line_item = $ticket_line_item;
1946
+				break;
1947
+			}
1948
+		}
1949
+		if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
1950
+			throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
1951
+		}
1952
+		return $line_item;
1953
+	}
1954
+
1955
+
1956
+	/**
1957
+	 * Soft Deletes this model object.
1958
+	 *
1959
+	 * @return boolean | int
1960
+	 * @throws RuntimeException
1961
+	 * @throws EE_Error
1962
+	 */
1963
+	public function delete()
1964
+	{
1965
+		if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
1966
+			$this->set_status(EEM_Registration::status_id_cancelled);
1967
+		}
1968
+		return parent::delete();
1969
+	}
1970
+
1971
+
1972
+	/**
1973
+	 * Restores whatever the previous status was on a registration before it was trashed (if possible)
1974
+	 *
1975
+	 * @throws EE_Error
1976
+	 * @throws RuntimeException
1977
+	 */
1978
+	public function restore()
1979
+	{
1980
+		$previous_status = $this->get_extra_meta(
1981
+			EE_Registration::PRE_TRASH_REG_STATUS_KEY,
1982
+			true,
1983
+			EEM_Registration::status_id_cancelled
1984
+		);
1985
+		if ($previous_status) {
1986
+			$this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
1987
+			$this->set_status($previous_status);
1988
+		}
1989
+		return parent::restore();
1990
+	}
1991
+
1992
+
1993
+	/**
1994
+	 * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
1995
+	 *
1996
+	 * @param  boolean $trigger_set_status_logic EE_Registration::set_status() can trigger additional logic
1997
+	 *                                           depending on whether the reg status changes to or from "Approved"
1998
+	 * @return boolean whether the Registration status was updated
1999
+	 * @throws EE_Error
2000
+	 * @throws RuntimeException
2001
+	 */
2002
+	public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
2003
+	{
2004
+		$paid = $this->paid();
2005
+		$price = $this->final_price();
2006
+		switch (true) {
2007
+			// overpaid or paid
2008
+			case EEH_Money::compare_floats($paid, $price, '>'):
2009
+			case EEH_Money::compare_floats($paid, $price):
2010
+				$new_status = EEM_Registration::status_id_approved;
2011
+				break;
2012
+			//  underpaid
2013
+			case EEH_Money::compare_floats($paid, $price, '<'):
2014
+				$new_status = EEM_Registration::status_id_pending_payment;
2015
+				break;
2016
+			// uhhh Houston...
2017
+			default:
2018
+				throw new RuntimeException(
2019
+					esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2020
+				);
2021
+		}
2022
+		if ($new_status !== $this->status_ID()) {
2023
+			if ($trigger_set_status_logic) {
2024
+				return $this->set_status($new_status);
2025
+			}
2026
+			parent::set('STS_ID', $new_status);
2027
+			return true;
2028
+		}
2029
+		return false;
2030
+	}
2031
+
2032
+
2033
+	/*************************** DEPRECATED ***************************/
2034
+
2035
+
2036
+	/**
2037
+	 * @deprecated
2038
+	 * @since     4.7.0
2039
+	 * @access    public
2040
+	 */
2041
+	public function price_paid()
2042
+	{
2043
+		EE_Error::doing_it_wrong(
2044
+			'EE_Registration::price_paid()',
2045
+			esc_html__(
2046
+				'This method is deprecated, please use EE_Registration::final_price() instead.',
2047
+				'event_espresso'
2048
+			),
2049
+			'4.7.0'
2050
+		);
2051
+		return $this->final_price();
2052
+	}
2053
+
2054
+
2055
+	/**
2056
+	 * @deprecated
2057
+	 * @since     4.7.0
2058
+	 * @access    public
2059
+	 * @param    float $REG_final_price
2060
+	 * @throws EE_Error
2061
+	 * @throws RuntimeException
2062
+	 */
2063
+	public function set_price_paid($REG_final_price = 0.00)
2064
+	{
2065
+		EE_Error::doing_it_wrong(
2066
+			'EE_Registration::set_price_paid()',
2067
+			esc_html__(
2068
+				'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2069
+				'event_espresso'
2070
+			),
2071
+			'4.7.0'
2072
+		);
2073
+		$this->set_final_price($REG_final_price);
2074
+	}
2075
+
2076
+
2077
+	/**
2078
+	 * @deprecated
2079
+	 * @since 4.7.0
2080
+	 * @return string
2081
+	 * @throws EE_Error
2082
+	 */
2083
+	public function pretty_price_paid()
2084
+	{
2085
+		EE_Error::doing_it_wrong(
2086
+			'EE_Registration::pretty_price_paid()',
2087
+			esc_html__(
2088
+				'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2089
+				'event_espresso'
2090
+			),
2091
+			'4.7.0'
2092
+		);
2093
+		return $this->pretty_final_price();
2094
+	}
2095
+
2096
+
2097
+	/**
2098
+	 * Gets the primary datetime related to this registration via the related Event to this registration
2099
+	 *
2100
+	 * @deprecated 4.9.17
2101
+	 * @return EE_Datetime
2102
+	 * @throws EE_Error
2103
+	 * @throws EntityNotFoundException
2104
+	 */
2105
+	public function get_related_primary_datetime()
2106
+	{
2107
+		EE_Error::doing_it_wrong(
2108
+			__METHOD__,
2109
+			esc_html__(
2110
+				'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2111
+				'event_espresso'
2112
+			),
2113
+			'4.9.17',
2114
+			'5.0.0'
2115
+		);
2116
+		return $this->event()->primary_datetime();
2117
+	}
2118
+
2119
+	/**
2120
+	 * Returns the contact's name (or "Unknown" if there is no contact.)
2121
+	 * @since $VID:$
2122
+	 * @return string
2123
+	 * @throws EE_Error
2124
+	 * @throws InvalidArgumentException
2125
+	 * @throws InvalidDataTypeException
2126
+	 * @throws InvalidInterfaceException
2127
+	 * @throws ReflectionException
2128
+	 */
2129
+	public function name()
2130
+	{
2131
+		return $this->attendeeName();
2132
+	}
2133 2133
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 2 patches
Indentation   +3383 added lines, -3383 removed lines patch added patch discarded remove patch
@@ -13,3399 +13,3399 @@
 block discarded – undo
13 13
 abstract class EE_Base_Class
14 14
 {
15 15
 
16
-    /**
17
-     * @var boolean indicating whether or not this model object is intended to ever be saved
18
-     * For example, we might create model objects intended to only be used for the duration
19
-     * of this request and to be thrown away, and if they were accidentally saved
20
-     * it would be a bug.
21
-     */
22
-    protected $_allow_persist = true;
23
-
24
-    /**
25
-     * This property is for holding a cached array of object properties indexed by property name as the key.
26
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
27
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
28
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
29
-     *
30
-     * @var array
31
-     */
32
-    protected $_cached_properties = [];
33
-
34
-    /**
35
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
36
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
37
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
38
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
39
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
40
-     * array as:
41
-     * array(
42
-     *  'Registration_Count' => 24
43
-     * );
44
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
45
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
46
-     * info)
47
-     *
48
-     * @var array
49
-     */
50
-    protected $custom_selection_results = [];
51
-
52
-    /**
53
-     * date format
54
-     * pattern or format for displaying dates
55
-     *
56
-     * @var string $_dt_frmt
57
-     */
58
-    protected $_dt_frmt;
59
-
60
-    /**
61
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
62
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
63
-     *
64
-     * @var array
65
-     */
66
-    protected $_fields = [];
67
-
68
-    /**
69
-     * @var boolean indicating whether or not this model object's properties have changed since construction
70
-     */
71
-    protected $_has_changes = false;
72
-
73
-    /**
74
-     * @var EEM_Base
75
-     */
76
-    protected $_model;
77
-
78
-
79
-    /**
80
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
81
-     * single
82
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
83
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
84
-     * all others have an array)
85
-     *
86
-     * @var array
87
-     */
88
-    protected $_model_relations = [];
89
-
90
-    /**
91
-     * This is an array of the original properties and values provided during construction
92
-     * of this model object. (keys are model field names, values are their values).
93
-     * This list is important to remember so that when we are merging data from the db, we know
94
-     * which values to override and which to not override.
95
-     *
96
-     * @var array
97
-     */
98
-    protected $_props_n_values_provided_in_constructor;
99
-
100
-    /**
101
-     * Timezone
102
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
103
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
104
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
105
-     * access to it.
106
-     *
107
-     * @var string
108
-     */
109
-    protected $_timezone = '';
110
-
111
-    /**
112
-     * time format
113
-     * pattern or format for displaying time
114
-     *
115
-     * @var string $_tm_frmt
116
-     */
117
-    protected $_tm_frmt;
118
-
119
-
120
-    /**
121
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
122
-     * play nice
123
-     *
124
-     * @param array   $field_values                            where each key is a field (ie, array key in the 2nd
125
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
126
-     *                                                         TXN_amount, QST_name, etc) and values are their values
127
-     * @param bool $set_from_db                             a flag for setting if the class is instantiated by the
128
-     *                                                         corresponding db model or not.
129
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
130
-     *                                                         be in when instantiating a EE_Base_Class object.
131
-     * @param array   $date_formats                            An array of date formats to set on construct where first
132
-     *                                                         value is the date_format and second value is the time
133
-     *                                                         format.
134
-     * @throws InvalidArgumentException
135
-     * @throws InvalidInterfaceException
136
-     * @throws InvalidDataTypeException
137
-     * @throws EE_Error
138
-     * @throws ReflectionException
139
-     */
140
-    protected function __construct(
141
-        array $field_values = [],
142
-        bool $set_from_db = false,
143
-        string $timezone = '',
144
-        array $date_formats = []
145
-    ) {
146
-        // ensure $fieldValues is an array
147
-        $field_values = is_array($field_values) ? $field_values : [$field_values];
148
-        $className    = get_class($this);
149
-        do_action("AHEE__{$className}__construct", $this, $field_values);
150
-        // remember what values were passed to this constructor
151
-        $this->_props_n_values_provided_in_constructor = $field_values;
152
-        $this->setDateAndTimeFormats($date_formats);
153
-        $this->_model = $this->get_model($timezone);
154
-        $model_fields = $this->_model->field_settings();
155
-        $this->validateFieldValues($model_fields, $field_values);
156
-        $this->setFieldValues($model_fields, $field_values, $set_from_db);
157
-        // remember in entity mapper
158
-        if (! $set_from_db && $this->_model->has_primary_key_field() && $this->ID()) {
159
-            $this->_model->add_to_entity_map($this);
160
-        }
161
-        // setup all the relations
162
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
163
-            $this->_model_relations[ $relation_name ] = $relation_obj instanceof EE_Belongs_To_Relation
164
-                ? null
165
-                : [];
166
-        }
167
-        /**
168
-         * Action done at the end of each model object construction
169
-         *
170
-         * @param EE_Base_Class $this the model object just created
171
-         */
172
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
173
-    }
174
-
175
-
176
-    /**
177
-     * for getting a model while instantiated.
178
-     *
179
-     * @param string $timezone
180
-     * @return EEM_Base | EEM_CPT_Base
181
-     * @throws EE_Error
182
-     * @throws ReflectionException
183
-     */
184
-    public function get_model(string $timezone = '')
185
-    {
186
-        if (! $this->_model) {
187
-            $timezone     = $timezone ?? $this->_timezone;
188
-            $modelName    = self::_get_model_classname(get_class($this));
189
-            $this->_model = self::_get_model_instance_with_name($modelName, $timezone);
190
-            $this->set_timezone($timezone);
191
-        }
192
-        return $this->_model;
193
-    }
194
-
195
-
196
-    /**
197
-     * Overrides parent because parent expects old models.
198
-     * This also doesn't do any validation, and won't work for serialized arrays
199
-     *
200
-     * @param string $field_name
201
-     * @param mixed  $field_value_from_db
202
-     * @throws InvalidArgumentException
203
-     * @throws InvalidInterfaceException
204
-     * @throws InvalidDataTypeException
205
-     * @throws EE_Error
206
-     */
207
-    public function set_from_db(string $field_name, $field_value_from_db)
208
-    {
209
-        $field_obj = $this->_model->field_settings_for($field_name);
210
-        if ($field_obj instanceof EE_Model_Field_Base) {
211
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
212
-            // eg, a CPT model object could have an entry in the posts table, but no
213
-            // entry in the meta table. Meaning that all its columns in the meta table
214
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
215
-
216
-            if ($field_value_from_db === null) {
217
-                if ($field_obj->is_nullable()) {
218
-                    // if the field allows nulls, then let it be null
219
-                    $field_value = null;
220
-                } else {
221
-                    $field_value = $field_obj->get_default_value();
222
-                }
223
-            } else {
224
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
225
-            }
226
-            $this->_fields[ $field_name ] = $field_value;
227
-            $this->_clear_cached_property($field_name);
228
-        }
229
-    }
230
-
231
-
232
-    /**
233
-     * Overrides parent because parent expects old models.
234
-     * This also doesn't do any validation, and won't work for serialized arrays
235
-     *
236
-     * @param string $field_name
237
-     * @param mixed  $field_value
238
-     * @param bool   $use_default
239
-     * @throws InvalidArgumentException
240
-     * @throws InvalidInterfaceException
241
-     * @throws InvalidDataTypeException
242
-     * @throws EE_Error
243
-     * @throws ReflectionException
244
-     * @throws ReflectionException
245
-     * @throws ReflectionException
246
-     */
247
-    public function set(string $field_name, $field_value, bool $use_default = false)
248
-    {
249
-        // if not using default and nothing has changed, and object has already been setup (has ID),
250
-        // then don't do anything
251
-        if (
252
-            ! $use_default
253
-            && $this->_fields[ $field_name ] === $field_value
254
-            && $this->ID()
255
-        ) {
256
-            return;
257
-        }
258
-        $this->_has_changes = true;
259
-        $field_obj          = $this->_model->field_settings_for($field_name);
260
-        if ($field_obj instanceof EE_Model_Field_Base) {
261
-            if ($field_obj instanceof EE_Datetime_Field) {
262
-                $field_obj->set_timezone($this->_timezone);
263
-                $field_obj->set_date_format($this->_dt_frmt);
264
-                $field_obj->set_time_format($this->_tm_frmt);
265
-            }
266
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
267
-            // should the value be null?
268
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
269
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
270
-                /**
271
-                 * To save having to refactor all the models, if a default value is used for a
272
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
273
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
274
-                 * object.
275
-                 *
276
-                 * @since 4.6.10+
277
-                 */
278
-                if (
279
-                    $field_obj instanceof EE_Datetime_Field
280
-                    && $this->_fields[ $field_name ] !== null
281
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
282
-                ) {
283
-                    empty($this->_fields[ $field_name ])
284
-                        ? $this->set($field_name, time())
285
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
286
-                }
287
-            } else {
288
-                $this->_fields[ $field_name ] = $holder_of_value;
289
-            }
290
-            // if we're not in the constructor...
291
-            // now check if what we set was a primary key
292
-            if (
293
-                // note: props_n_values_provided_in_constructor is only set at the END of the constructor
294
-                $this->_props_n_values_provided_in_constructor
295
-                && $field_value
296
-                && $field_name === $this->_model->primary_key_name()
297
-            ) {
298
-                // if so, we want all this object's fields to be filled either with
299
-                // what we've explicitly set on this model
300
-                // or what we have in the db
301
-                // echo "setting primary key!";
302
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
303
-                $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
304
-                foreach ($fields_on_model as $field_obj) {
305
-                    if (
306
-                        ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
307
-                        && $field_obj->get_name() !== $field_name
308
-                    ) {
309
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
310
-                    }
311
-                }
312
-                // oh this model object has an ID? well make sure its in the entity mapper
313
-                $this->_model->add_to_entity_map($this);
314
-            }
315
-            // let's unset any cache for this field_name from the $_cached_properties property.
316
-            $this->_clear_cached_property($field_name);
317
-        } else {
318
-            throw new EE_Error(
319
-                sprintf(
320
-                    esc_html__(
321
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
322
-                        'event_espresso'
323
-                    ),
324
-                    $field_name
325
-                )
326
-            );
327
-        }
328
-    }
329
-
330
-
331
-    /**
332
-     * Gets the value of the primary key.
333
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
334
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
335
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
336
-     *
337
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
338
-     * @throws InvalidArgumentException
339
-     * @throws InvalidInterfaceException
340
-     * @throws InvalidDataTypeException
341
-     * @throws EE_Error
342
-     */
343
-    public function ID()
344
-    {
345
-        // now that we know the name of the variable, use a variable variable to get its value and return its
346
-        if ($this->_model->has_primary_key_field()) {
347
-            return $this->_fields[ $this->_model->primary_key_name() ];
348
-        }
349
-        return $this->_model->get_index_primary_key_string($this->_fields);
350
-    }
351
-
352
-
353
-    /**
354
-     * If a model name is provided (eg Registration), gets the model classname for that model.
355
-     * Also works if a model class's classname is provided (eg EE_Registration).
356
-     *
357
-     * @param string $model_name
358
-     * @return string like EEM_Attendee
359
-     */
360
-    private static function _get_model_classname(string $model_name = ''): string
361
-    {
362
-        return strpos($model_name, 'EE_') === 0
363
-            ? str_replace('EE_', 'EEM_', $model_name)
364
-            : 'EEM_' . $model_name;
365
-    }
366
-
367
-
368
-    /**
369
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
370
-     *
371
-     * @param string $model_classname
372
-     * @param string $timezone
373
-     * @return EEM_Base
374
-     * @throws ReflectionException
375
-     * @throws InvalidArgumentException
376
-     * @throws InvalidInterfaceException
377
-     * @throws InvalidDataTypeException
378
-     * @throws EE_Error
379
-     */
380
-    protected static function _get_model_instance_with_name(string $model_classname, string $timezone = ''): EEM_Base
381
-    {
382
-        $model_classname = str_replace('EEM_', '', $model_classname);
383
-        $model           = EE_Registry::instance()->load_model($model_classname);
384
-        $model->set_timezone($timezone);
385
-        return $model;
386
-    }
387
-
388
-
389
-    /**
390
-     * This just clears out ONE property if it exists in the cache
391
-     *
392
-     * @param string $property_name the property to remove if it exists (from the _cached_properties array)
393
-     * @return void
394
-     */
395
-    protected function _clear_cached_property(string $property_name)
396
-    {
397
-        unset($this->_cached_properties[ $property_name ]);
398
-    }
399
-
400
-
401
-    /**
402
-     * Gets the EEM_*_Model for this class
403
-     *
404
-     * @param string $classname
405
-     * @param string $timezone
406
-     * @return EEM_Base
407
-     * @throws InvalidArgumentException
408
-     * @throws InvalidInterfaceException
409
-     * @throws InvalidDataTypeException
410
-     * @throws EE_Error
411
-     * @throws ReflectionException
412
-     */
413
-    protected static function _get_model(string $classname, string $timezone = ''): EEM_Base
414
-    {
415
-        // find model for this class
416
-        if (! $classname) {
417
-            throw new EE_Error(
418
-                sprintf(
419
-                    esc_html__(
420
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
421
-                        'event_espresso'
422
-                    ),
423
-                    $classname
424
-                )
425
-            );
426
-        }
427
-        $modelName = self::_get_model_classname($classname);
428
-        return self::_get_model_instance_with_name($modelName, $timezone);
429
-    }
430
-
431
-
432
-    /**
433
-     * verifies that the specified field is of the correct type
434
-     *
435
-     * @param string $field_name
436
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
437
-     *                                (in cases where the same property may be used for different outputs
438
-     *                                - i.e. datetime, money etc.)
439
-     * @return mixed
440
-     * @throws InvalidArgumentException
441
-     * @throws InvalidInterfaceException
442
-     * @throws InvalidDataTypeException
443
-     * @throws EE_Error
444
-     */
445
-    public function get(string $field_name, string $extra_cache_ref = '')
446
-    {
447
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
448
-    }
449
-
450
-
451
-    /**
452
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
453
-     * This also SETS the cache if we return the actual property!
454
-     *
455
-     * @param string $field_name       the name of the property we're trying to retrieve
456
-     * @param bool   $pretty
457
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
458
-     *                                 (in cases where the same property may be used for different outputs
459
-     *                                 - i.e. datetime, money etc.)
460
-     *                                 It can also accept certain pre-defined "schema" strings
461
-     *                                 to define how to output the property.
462
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
463
-     * @return mixed                   whatever the value for the property is we're retrieving
464
-     * @throws InvalidArgumentException
465
-     * @throws InvalidInterfaceException
466
-     * @throws InvalidDataTypeException
467
-     * @throws EE_Error
468
-     */
469
-    protected function _get_cached_property(string $field_name, bool $pretty = false, string $extra_cache_ref = '')
470
-    {
471
-        // verify the field exists
472
-        $this->_model->field_settings_for($field_name);
473
-        $cache_type = $pretty ? 'pretty' : 'standard';
474
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
475
-        if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
476
-            return $this->_cached_properties[ $field_name ][ $cache_type ];
477
-        }
478
-        $value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
479
-        $this->_set_cached_property($field_name, $value, $cache_type);
480
-        return $value;
481
-    }
482
-
483
-
484
-    /**
485
-     * If the cache didn't fetch the needed item, this fetches it.
486
-     *
487
-     * @param string $field_name
488
-     * @param bool   $pretty
489
-     * @param string $extra_cache_ref
490
-     * @return mixed
491
-     * @throws InvalidArgumentException
492
-     * @throws InvalidInterfaceException
493
-     * @throws InvalidDataTypeException
494
-     * @throws EE_Error
495
-     */
496
-    protected function _get_fresh_property(string $field_name, bool $pretty = false, string $extra_cache_ref = '')
497
-    {
498
-        $field_obj = $this->_model->field_settings_for($field_name);
499
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
500
-        if ($field_obj instanceof EE_Datetime_Field) {
501
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
502
-        }
503
-        if (! isset($this->_fields[ $field_name ])) {
504
-            $this->_fields[ $field_name ] = null;
505
-        }
506
-        return $pretty
507
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
508
-            : $field_obj->prepare_for_get($this->_fields[ $field_name ]);
509
-    }
510
-
511
-
512
-    /**
513
-     * For adding an item to the cached_properties property.
514
-     *
515
-     * @param string      $field_name the property item the corresponding value is for.
516
-     * @param mixed       $value      The value we are caching.
517
-     * @param string $cache_type
518
-     * @return void
519
-     * @throws InvalidArgumentException
520
-     * @throws InvalidInterfaceException
521
-     * @throws InvalidDataTypeException
522
-     * @throws EE_Error
523
-     */
524
-    protected function _set_cached_property(string $field_name, $value, string $cache_type = '')
525
-    {
526
-        // first make sure this property exists
527
-        $this->_model->field_settings_for($field_name);
528
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
529
-        $this->_cached_properties[ $field_name ][ $cache_type ] = $value;
530
-    }
531
-
532
-
533
-    /**
534
-     * set timezone, formats, and output for EE_Datetime_Field objects
535
-     *
536
-     * @param EE_Datetime_Field $datetime_field
537
-     * @param bool              $pretty
538
-     * @param null              $date_or_time
539
-     * @return void
540
-     * @throws InvalidArgumentException
541
-     * @throws InvalidInterfaceException
542
-     * @throws InvalidDataTypeException
543
-     */
544
-    protected function _prepare_datetime_field(
545
-        EE_Datetime_Field $datetime_field,
546
-        bool $pretty = false,
547
-        $date_or_time = null
548
-    ) {
549
-        $datetime_field->set_timezone($this->_timezone);
550
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
551
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
552
-        // set the output returned
553
-        switch ($date_or_time) {
554
-            case 'D':
555
-                $datetime_field->set_date_time_output('date');
556
-                break;
557
-            case 'T':
558
-                $datetime_field->set_date_time_output('time');
559
-                break;
560
-            default:
561
-                $datetime_field->set_date_time_output();
562
-        }
563
-    }
564
-
565
-
566
-    /**
567
-     * @param $props_n_values
568
-     * @param $classname
569
-     * @return EE_Base_Class|null
570
-     * @throws ReflectionException
571
-     * @throws InvalidArgumentException
572
-     * @throws InvalidInterfaceException
573
-     * @throws InvalidDataTypeException
574
-     * @throws EE_Error
575
-     * @throws ReflectionException
576
-     */
577
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname): ?EE_Base_Class
578
-    {
579
-        // TODO: will not work for Term_Relationships because they have no PK!
580
-        $primary_id_ref = self::_get_primary_key_name($classname);
581
-        if (
582
-            array_key_exists($primary_id_ref, $props_n_values)
583
-            && ! empty($props_n_values[ $primary_id_ref ])
584
-        ) {
585
-            $id = $props_n_values[ $primary_id_ref ];
586
-            return self::_get_model($classname)->get_from_entity_map($id);
587
-        }
588
-        return null;
589
-    }
590
-
591
-
592
-    /**
593
-     * returns the name of the primary key attribute
594
-     *
595
-     * @param null $classname
596
-     * @return string
597
-     * @throws InvalidArgumentException
598
-     * @throws InvalidInterfaceException
599
-     * @throws InvalidDataTypeException
600
-     * @throws EE_Error
601
-     * @throws ReflectionException
602
-     * @throws ReflectionException
603
-     * @throws ReflectionException
604
-     */
605
-    protected static function _get_primary_key_name($classname = null): string
606
-    {
607
-        if (! $classname) {
608
-            throw new EE_Error(
609
-                sprintf(
610
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
611
-                    $classname
612
-                )
613
-            );
614
-        }
615
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
616
-    }
617
-
618
-
619
-    /**
620
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
621
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
622
-     * 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
623
-     * we return false.
624
-     *
625
-     * @param array  $props_n_values    incoming array of properties and their values
626
-     * @param string $classname         the classname of the child class
627
-     * @param string $timezone
628
-     * @param array  $date_formats      incoming date_formats in an array where the first value is the
629
-     *                                  date_format and the second value is the time format
630
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|null
631
-     * @throws InvalidArgumentException
632
-     * @throws InvalidInterfaceException
633
-     * @throws InvalidDataTypeException
634
-     * @throws EE_Error
635
-     * @throws ReflectionException
636
-     * @throws ReflectionException
637
-     * @throws ReflectionException
638
-     */
639
-    protected static function _check_for_object(
640
-        array $props_n_values, string $classname, string $timezone = '', array $date_formats = []
641
-    ) {
642
-        $existing = null;
643
-        $model    = self::_get_model($classname, $timezone);
644
-        if ($model->has_primary_key_field()) {
645
-            $primary_id_ref = self::_get_primary_key_name($classname);
646
-            if (
647
-                array_key_exists($primary_id_ref, $props_n_values)
648
-                && ! empty($props_n_values[ $primary_id_ref ])
649
-            ) {
650
-                $existing = $model->get_one_by_ID($props_n_values[ $primary_id_ref ]);
651
-            }
652
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
653
-            // no primary key on this model, but there's still a matching item in the DB
654
-            $existing = $model->get_one_by_ID(
655
-                $model->get_index_primary_key_string($props_n_values)
656
-            );
657
-        }
658
-        if ($existing) {
659
-            $has_date_formats = ! empty($date_formats) && is_array($date_formats)
660
-                                && isset($date_formats[0], $date_formats[1]);
661
-            $date_format      = $has_date_formats ? $date_formats[0] : (string) get_option('date_format', 'Y-m-d');
662
-            $time_format      = $has_date_formats ? $date_formats[1] : (string) get_option('time_format', 'g:i a');
663
-            // set date formats before setting values
664
-            $existing->set_date_format($date_format);
665
-            $existing->set_time_format($time_format);
666
-            foreach ($props_n_values as $property => $field_value) {
667
-                $existing->set($property, $field_value);
668
-            }
669
-            return $existing;
670
-        }
671
-        return null;
672
-    }
673
-
674
-
675
-    /**
676
-     * This sets the internal date format to what is sent in to be used as the new default for the class
677
-     * internally instead of wp set date format options
678
-     *
679
-     * @param string $format should be a format recognizable by PHP date() functions.
680
-     * @since 4.6
681
-     */
682
-    public function set_date_format($format)
683
-    {
684
-        if ($format !== $this->_dt_frmt) {
685
-            $this->_dt_frmt = $format;
686
-            // clear cached_properties because they won't be relevant now.
687
-            $this->_clear_cached_properties();
688
-        }
689
-    }
690
-
691
-
692
-    /**
693
-     * This sets the internal time format string to what is sent in to be used as the new default for the
694
-     * class internally instead of wp set time format options.
695
-     *
696
-     * @param string $format should be a format recognizable by PHP date() functions.
697
-     * @since 4.6
698
-     */
699
-    public function set_time_format($format)
700
-    {
701
-        if ($format !== $this->_tm_frmt) {
702
-            $this->_tm_frmt = $format;
703
-            // clear cached_properties because they won't be relevant now.
704
-            $this->_clear_cached_properties();
705
-        }
706
-    }
707
-
708
-
709
-    /**
710
-     * This just takes care of clearing out the cached_properties
711
-     *
712
-     * @return void
713
-     */
714
-    protected function _clear_cached_properties()
715
-    {
716
-        $this->_cached_properties = [];
717
-    }
718
-
719
-
720
-    /**
721
-     * Sets whether or not this model object should be allowed to be saved to the DB.
722
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
723
-     * you got new information that somehow made you change your mind.
724
-     *
725
-     * @param boolean $allow_persist
726
-     * @return boolean
727
-     */
728
-    public function set_allow_persist($allow_persist)
729
-    {
730
-        return $this->_allow_persist = $allow_persist;
731
-    }
732
-
733
-
734
-    /**
735
-     * Gets the field's original value when this object was constructed during this request.
736
-     * This can be helpful when determining if a model object has changed or not
737
-     *
738
-     * @param string $field_name
739
-     * @return mixed|null
740
-     * @throws InvalidArgumentException
741
-     * @throws InvalidInterfaceException
742
-     * @throws InvalidDataTypeException
743
-     * @throws EE_Error
744
-     */
745
-    public function get_original(string $field_name)
746
-    {
747
-        if (
748
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
749
-            && $field_settings = $this->_model->field_settings_for($field_name)
750
-        ) {
751
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
752
-        }
753
-        return null;
754
-    }
755
-
756
-
757
-    /**
758
-     * @param EE_Base_Class $obj
759
-     * @return string
760
-     */
761
-    public function get_class($obj)
762
-    {
763
-        return get_class($obj);
764
-    }
765
-
766
-
767
-    /**
768
-     * Set custom select values for model.
769
-     *
770
-     * @param array $custom_select_values
771
-     */
772
-    public function setCustomSelectsValues(array $custom_select_values)
773
-    {
774
-        $this->custom_selection_results = $custom_select_values;
775
-    }
776
-
777
-
778
-    /**
779
-     * Returns the custom select value for the provided alias if its set.
780
-     * If not set, returns null.
781
-     *
782
-     * @param string $alias
783
-     * @return string|int|float|null
784
-     */
785
-    public function getCustomSelect($alias)
786
-    {
787
-        return isset($this->custom_selection_results[ $alias ])
788
-            ? $this->custom_selection_results[ $alias ]
789
-            : null;
790
-    }
791
-
792
-
793
-    /**
794
-     * This sets the field value on the db column if it exists for the given $column_name or
795
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
796
-     *
797
-     * @param string $field_name  Must be the exact column name.
798
-     * @param mixed  $field_value The value to set.
799
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
800
-     * @throws InvalidArgumentException
801
-     * @throws InvalidInterfaceException
802
-     * @throws InvalidDataTypeException
803
-     * @throws EE_Error
804
-     * @throws ReflectionException
805
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
806
-     */
807
-    public function set_field_or_extra_meta(string $field_name, $field_value)
808
-    {
809
-        if ($this->_model->has_field($field_name)) {
810
-            $this->set($field_name, $field_value);
811
-            return true;
812
-        }
813
-        // ensure this object is saved first so that extra meta can be properly related.
814
-        $this->save();
815
-        return $this->update_extra_meta($field_name, $field_value);
816
-    }
817
-
818
-
819
-    /**
820
-     *        Saves this object to the database. An array may be supplied to set some values on this
821
-     * object just before saving.
822
-     *
823
-     * @param array $set_cols_n_values keys are field names, values are their new values,
824
-     *                                 if provided during the save() method
825
-     *                                 (often client code will change the fields' values before calling save)
826
-     * @return int                     1 on a successful update or the ID of the new entry on insert;
827
-     *                                 0 on failure or if the model object isn't allowed to persist
828
-     *                                 (as determined by EE_Base_Class::allow_persist())
829
-     * @throws InvalidInterfaceException
830
-     * @throws InvalidDataTypeException
831
-     * @throws EE_Error
832
-     * @throws InvalidArgumentException
833
-     * @throws ReflectionException
834
-     * @throws ReflectionException
835
-     * @throws ReflectionException
836
-     */
837
-    public function save($set_cols_n_values = [])
838
-    {
839
-        /**
840
-         * Filters the fields we're about to save on the model object
841
-         *
842
-         * @param array         $set_cols_n_values
843
-         * @param EE_Base_Class $model_object
844
-         */
845
-        $set_cols_n_values = (array) apply_filters(
846
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
847
-            $set_cols_n_values,
848
-            $this
849
-        );
850
-        // set attributes as provided in $set_cols_n_values
851
-        foreach ($set_cols_n_values as $column => $value) {
852
-            $this->set($column, $value);
853
-        }
854
-        // no changes ? then don't do anything
855
-        if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
856
-            return 0;
857
-        }
858
-        /**
859
-         * Saving a model object.
860
-         * Before we perform a save, this action is fired.
861
-         *
862
-         * @param EE_Base_Class $model_object the model object about to be saved.
863
-         */
864
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
865
-        if (! $this->allow_persist()) {
866
-            return 0;
867
-        }
868
-        // now get current attribute values
869
-        $save_cols_n_values = $this->_fields;
870
-        // if the object already has an ID, update it. Otherwise, insert it
871
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
872
-        // They have been
873
-        $old_assumption_concerning_value_preparation = $this->_model
874
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
875
-        $this->_model->assume_values_already_prepared_by_model_object(true);
876
-        // does this model have an autoincrement PK?
877
-        if ($this->_model->has_primary_key_field()) {
878
-            if ($this->_model->get_primary_key_field()->is_auto_increment()) {
879
-                // ok check if it's set, if so: update; if not, insert
880
-                if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
881
-                    $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
882
-                } else {
883
-                    unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
884
-                    $results = $this->_model->insert($save_cols_n_values);
885
-                    if ($results) {
886
-                        // if successful, set the primary key
887
-                        // but don't use the normal SET method, because it will check if
888
-                        // an item with the same ID exists in the mapper & db, then
889
-                        // will find it in the db (because we just added it) and THAT object
890
-                        // will get added to the mapper before we can add this one!
891
-                        // but if we just avoid using the SET method, all that headache can be avoided
892
-                        $pk_field_name                   = $this->_model->primary_key_name();
893
-                        $this->_fields[ $pk_field_name ] = $results;
894
-                        $this->_clear_cached_property($pk_field_name);
895
-                        $this->_model->add_to_entity_map($this);
896
-                        $this->_update_cached_related_model_objs_fks();
897
-                    }
898
-                }
899
-            } else {
900
-                // PK is NOT auto-increment
901
-                // so check if one like it already exists in the db
902
-                if ($this->_model->exists_by_ID($this->ID())) {
903
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
904
-                        throw new EE_Error(
905
-                            sprintf(
906
-                                esc_html__(
907
-                                    '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',
908
-                                    'event_espresso'
909
-                                ),
910
-                                get_class($this),
911
-                                get_class($this->_model) . '::instance()->add_to_entity_map()',
912
-                                get_class($this->_model) . '::instance()->get_one_by_ID()',
913
-                                '<br />'
914
-                            )
915
-                        );
916
-                    }
917
-                    $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
918
-                } else {
919
-                    $results = $this->_model->insert($save_cols_n_values);
920
-                    $this->_update_cached_related_model_objs_fks();
921
-                }
922
-            }
923
-        } else {
924
-            // there is NO primary key
925
-            $already_in_db = false;
926
-            foreach ($this->_model->unique_indexes() as $index) {
927
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
928
-                if ($this->_model->exists([$uniqueness_where_params])) {
929
-                    $already_in_db = true;
930
-                }
931
-            }
932
-            if ($already_in_db) {
933
-                $combined_pk_fields_n_values = array_intersect_key(
934
-                    $save_cols_n_values,
935
-                    $this->_model->get_combined_primary_key_fields()
936
-                );
937
-                $results                     = $this->_model->update(
938
-                    $save_cols_n_values,
939
-                    $combined_pk_fields_n_values
940
-                );
941
-            } else {
942
-                $results = $this->_model->insert($save_cols_n_values);
943
-            }
944
-        }
945
-        // restore the old assumption about values being prepared by the model object
946
-        $this->_model->assume_values_already_prepared_by_model_object(
947
-            $old_assumption_concerning_value_preparation
948
-        );
949
-        /**
950
-         * After saving the model object this action is called
951
-         *
952
-         * @param EE_Base_Class $model_object which was just saved
953
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
954
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
955
-         */
956
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
957
-        $this->_has_changes = false;
958
-        return $results;
959
-    }
960
-
961
-
962
-    /**
963
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
964
-     * A $previous_value can be specified in case there are many meta rows with the same key
965
-     *
966
-     * @param string $meta_key
967
-     * @param mixed  $meta_value
968
-     * @param mixed  $previous_value
969
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
970
-     *                  NOTE: if the values haven't changed, returns 0
971
-     * @throws InvalidArgumentException
972
-     * @throws InvalidInterfaceException
973
-     * @throws InvalidDataTypeException
974
-     * @throws EE_Error
975
-     * @throws ReflectionException
976
-     */
977
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
978
-    {
979
-        $query_params = [
980
-            [
981
-                'EXM_key'  => $meta_key,
982
-                'OBJ_ID'   => $this->ID(),
983
-                'EXM_type' => $this->_model->get_this_model_name(),
984
-            ],
985
-        ];
986
-        if ($previous_value !== null) {
987
-            $query_params[0]['EXM_value'] = $meta_value;
988
-        }
989
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
990
-        if (! $existing_rows_like_that) {
991
-            return $this->add_extra_meta($meta_key, $meta_value);
992
-        }
993
-        foreach ($existing_rows_like_that as $existing_row) {
994
-            $existing_row->save(['EXM_value' => $meta_value]);
995
-        }
996
-        return count($existing_rows_like_that);
997
-    }
998
-
999
-
1000
-    /**
1001
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
1002
-     *
1003
-     * @return boolean
1004
-     */
1005
-    public function allow_persist()
1006
-    {
1007
-        return $this->_allow_persist;
1008
-    }
1009
-
1010
-
1011
-    /**
1012
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1013
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1014
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1015
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1016
-     * 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
1017
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1018
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1019
-     *
1020
-     * @return void
1021
-     * @throws ReflectionException
1022
-     * @throws InvalidArgumentException
1023
-     * @throws InvalidInterfaceException
1024
-     * @throws InvalidDataTypeException
1025
-     * @throws EE_Error
1026
-     */
1027
-    protected function _update_cached_related_model_objs_fks()
1028
-    {
1029
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1030
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1031
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1032
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1033
-                        $this->_model->get_this_model_name()
1034
-                    );
1035
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1036
-                    if ($related_model_obj_in_cache->ID()) {
1037
-                        $related_model_obj_in_cache->save();
1038
-                    }
1039
-                }
1040
-            }
1041
-        }
1042
-    }
1043
-
1044
-
1045
-    /**
1046
-     * in_entity_map
1047
-     * Checks if this model object has been proven to already be in the entity map
1048
-     *
1049
-     * @return boolean
1050
-     * @throws InvalidArgumentException
1051
-     * @throws InvalidInterfaceException
1052
-     * @throws InvalidDataTypeException
1053
-     * @throws EE_Error
1054
-     */
1055
-    public function in_entity_map()
1056
-    {
1057
-        // well, if we looked, did we find it in the entity map?
1058
-        return $this->ID() && $this->_model->get_from_entity_map($this->ID()) === $this;
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
1064
-     * no other extra meta for this model object have the same key. Returns TRUE if the
1065
-     * extra meta row was entered, false if not
1066
-     *
1067
-     * @param string  $meta_key
1068
-     * @param mixed   $meta_value
1069
-     * @param boolean $unique
1070
-     * @return boolean
1071
-     * @throws InvalidArgumentException
1072
-     * @throws InvalidInterfaceException
1073
-     * @throws InvalidDataTypeException
1074
-     * @throws EE_Error
1075
-     * @throws ReflectionException
1076
-     * @throws ReflectionException
1077
-     */
1078
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
1079
-    {
1080
-        if ($unique) {
1081
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
1082
-                [
1083
-                    [
1084
-                        'EXM_key'  => $meta_key,
1085
-                        'OBJ_ID'   => $this->ID(),
1086
-                        'EXM_type' => $this->_model->get_this_model_name(),
1087
-                    ],
1088
-                ]
1089
-            );
1090
-            if ($existing_extra_meta) {
1091
-                return false;
1092
-            }
1093
-        }
1094
-        $new_extra_meta = EE_Extra_Meta::new_instance(
1095
-            [
1096
-                'EXM_key'   => $meta_key,
1097
-                'EXM_value' => $meta_value,
1098
-                'OBJ_ID'    => $this->ID(),
1099
-                'EXM_type'  => $this->_model->get_this_model_name(),
1100
-            ]
1101
-        );
1102
-        $new_extra_meta->save();
1103
-        return true;
1104
-    }
1105
-
1106
-
1107
-    /**
1108
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1109
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1110
-     *
1111
-     * @param string $relationName
1112
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
1113
-     * @throws InvalidArgumentException
1114
-     * @throws InvalidInterfaceException
1115
-     * @throws InvalidDataTypeException
1116
-     * @throws EE_Error
1117
-     * @throws ReflectionException
1118
-     */
1119
-    public function get_all_from_cache($relationName)
1120
-    {
1121
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
1122
-        // if the result is not an array, but exists, make it an array
1123
-        $objects = is_array($objects) ? $objects : [$objects];
1124
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
1125
-        // basically, if this model object was stored in the session, and these cached model objects
1126
-        // already have IDs, let's make sure they're in their model's entity mapper
1127
-        // otherwise we will have duplicates next time we call
1128
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
1129
-        $related_model = EE_Registry::instance()->load_model($relationName);
1130
-        foreach ($objects as $model_object) {
1131
-            if ($related_model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
1132
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
1133
-                if ($model_object->ID()) {
1134
-                    $related_model->add_to_entity_map($model_object);
1135
-                }
1136
-            } else {
1137
-                throw new EE_Error(
1138
-                    sprintf(
1139
-                        esc_html__(
1140
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
1141
-                            'event_espresso'
1142
-                        ),
1143
-                        $relationName,
1144
-                        gettype($model_object)
1145
-                    )
1146
-                );
1147
-            }
1148
-        }
1149
-        $this->updateTimezoneOnRelated($objects);
1150
-        return $objects;
1151
-    }
1152
-
1153
-
1154
-    /**
1155
-     * This retrieves the value of the db column set on this class or if that's not present
1156
-     * it will attempt to retrieve from extra_meta if found.
1157
-     * Example Usage:
1158
-     * Via EE_Message child class:
1159
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
1160
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
1161
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
1162
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
1163
-     * value for those extra fields dynamically via the EE_message object.
1164
-     *
1165
-     * @param string $field_name expecting the fully qualified field name.
1166
-     * @return mixed|null  value for the field if found.  null if not found.
1167
-     * @throws ReflectionException
1168
-     * @throws InvalidArgumentException
1169
-     * @throws InvalidInterfaceException
1170
-     * @throws InvalidDataTypeException
1171
-     * @throws EE_Error
1172
-     */
1173
-    public function get_field_or_extra_meta(string $field_name)
1174
-    {
1175
-        // if this isn't a column in the main table, then see if it is in the extra meta.
1176
-        return $this->_model->has_field($field_name)
1177
-            ? $this->get($field_name)
1178
-            : $this->get_extra_meta($field_name, true);
1179
-    }
1180
-
1181
-
1182
-    /**
1183
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
1184
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
1185
-     * You can specify $default is case you haven't found the extra meta
1186
-     *
1187
-     * @param string  $meta_key
1188
-     * @param boolean $single
1189
-     * @param mixed   $default if we don't find anything, what should we return?
1190
-     * @return mixed single value if $single; array if ! $single
1191
-     * @throws ReflectionException
1192
-     * @throws InvalidArgumentException
1193
-     * @throws InvalidInterfaceException
1194
-     * @throws InvalidDataTypeException
1195
-     * @throws EE_Error
1196
-     */
1197
-    public function get_extra_meta($meta_key, $single = false, $default = null)
1198
-    {
1199
-        if ($single) {
1200
-            $result = $this->get_first_related(
1201
-                'Extra_Meta',
1202
-                [['EXM_key' => $meta_key]]
1203
-            );
1204
-            if ($result instanceof EE_Extra_Meta) {
1205
-                return $result->value();
1206
-            }
1207
-        } else {
1208
-            $results = $this->get_many_related(
1209
-                'Extra_Meta',
1210
-                [['EXM_key' => $meta_key]]
1211
-            );
1212
-            if ($results) {
1213
-                $values = [];
1214
-                foreach ($results as $result) {
1215
-                    if ($result instanceof EE_Extra_Meta) {
1216
-                        $values[ $result->ID() ] = $result->value();
1217
-                    }
1218
-                }
1219
-                return $values;
1220
-            }
1221
-        }
1222
-        // if nothing discovered yet return default.
1223
-        return apply_filters(
1224
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
1225
-            $default,
1226
-            $meta_key,
1227
-            $single,
1228
-            $this
1229
-        );
1230
-    }
1231
-
1232
-
1233
-    /**
1234
-     * Gets the first (ie, one) related model object of the specified type.
1235
-     *
1236
-     * @param string $relationName key in the model's _model_relations array
1237
-     * @param array  $query_params
1238
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1239
-     * @return EE_Base_Class (not an array, a single object)
1240
-     * @throws ReflectionException
1241
-     * @throws InvalidArgumentException
1242
-     * @throws InvalidInterfaceException
1243
-     * @throws InvalidDataTypeException
1244
-     * @throws EE_Error
1245
-     */
1246
-    public function get_first_related($relationName, $query_params = [])
1247
-    {
1248
-        $model_relation = $this->_model->related_settings_for($relationName);
1249
-        if (! $this->ID()) {
1250
-            // this doesn't exist in the DB,
1251
-            // but maybe the relation type is "belongs to" and the related object might
1252
-            if ($model_relation instanceof EE_Belongs_To_Relation) {
1253
-                return $this->_model->get_first_related(
1254
-                    $this,
1255
-                    $relationName,
1256
-                    $query_params
1257
-                );
1258
-            }
1259
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
1260
-            // just get what's cached on this object
1261
-            return $this->get_one_from_cache($relationName);
1262
-        }
1263
-        // this exists in the DB, get from the cache OR the DB
1264
-        // if they've provided some query parameters, don't bother trying to cache the result
1265
-        // also make sure we're not caching the result of get_first_related
1266
-        // on a relation which should have an array of objects (because the cache might have an array of objects)
1267
-        if (
1268
-            $query_params
1269
-            || ! $model_relation instanceof EE_Belongs_To_Relation
1270
-        ) {
1271
-            return $this->_model->get_first_related(
1272
-                $this,
1273
-                $relationName,
1274
-                $query_params
1275
-            );
1276
-        }
1277
-        // check if we've already cached the result of this query
1278
-        $cached_result = $this->get_one_from_cache($relationName);
1279
-        if ($cached_result) {
1280
-            return $cached_result;
1281
-        }
1282
-        $related_model_object = $this->_model->get_first_related(
1283
-            $this,
1284
-            $relationName,
1285
-            $query_params
1286
-        );
1287
-        $this->cache($relationName, $related_model_object);
1288
-        return $related_model_object;
1289
-    }
1290
-
1291
-
1292
-    /**
1293
-     * Gets all the related model objects of the specified type. Eg, if the current class if
1294
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
1295
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
1296
-     * because we want to get even deleted items etc.
1297
-     *
1298
-     * @param string $relationName key in the model's _model_relations array
1299
-     * @param array  $query_params
1300
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1301
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
1302
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
1303
-     *                             results if you want IDs
1304
-     * @throws ReflectionException
1305
-     * @throws InvalidArgumentException
1306
-     * @throws InvalidInterfaceException
1307
-     * @throws InvalidDataTypeException
1308
-     * @throws EE_Error
1309
-     */
1310
-    public function get_many_related($relationName, $query_params = [])
1311
-    {
1312
-        if (! $this->ID()) {
1313
-            // this doesn't exist in the DB, so just get the related things from the cache
1314
-            return $this->get_all_from_cache($relationName);
1315
-        }
1316
-        // this exists in the DB, so get the related things from either the cache or the DB
1317
-        // if there are query parameters, forget about caching the related model objects.
1318
-        if ($query_params) {
1319
-            return $this->_model->get_all_related(
1320
-                $this,
1321
-                $relationName,
1322
-                $query_params
1323
-            );
1324
-        }
1325
-        // did we already cache the result of this query?
1326
-        $cached_results = $this->get_all_from_cache($relationName);
1327
-        if ($cached_results) {
1328
-            return $cached_results;
1329
-        }
1330
-        $related_model_objects = $this->_model->get_all_related(
1331
-            $this,
1332
-            $relationName,
1333
-            $query_params
1334
-        );
1335
-        // if no query parameters were passed, then we got all the related model objects
1336
-        // for that relation. We can cache them then.
1337
-        foreach ($related_model_objects as $related_model_object) {
1338
-            $this->cache($relationName, $related_model_object);
1339
-        }
1340
-        $this->updateTimezoneOnRelated($related_model_objects);
1341
-        return $related_model_objects;
1342
-    }
1343
-
1344
-
1345
-    /**
1346
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1347
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1348
-     *
1349
-     * @param string $relationName
1350
-     * @return EE_Base_Class
1351
-     */
1352
-    public function get_one_from_cache($relationName)
1353
-    {
1354
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
1355
-            ? $this->_model_relations[ $relationName ]
1356
-            : null;
1357
-        if (is_array($cached_array_or_object)) {
1358
-            $cached_array_or_object = array_shift($cached_array_or_object);
1359
-        }
1360
-        $this->updateTimezoneOnRelated($cached_array_or_object);
1361
-        return $cached_array_or_object;
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * cache
1367
-     * stores the passed model object on the current model object.
1368
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
1369
-     *
1370
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
1371
-     *                                       'Registration' associated with this model object
1372
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
1373
-     *                                       that could be a payment or a registration)
1374
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
1375
-     *                                       items which will be stored in an array on this object
1376
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
1377
-     *                                       related thing, no array)
1378
-     * @throws InvalidArgumentException
1379
-     * @throws InvalidInterfaceException
1380
-     * @throws InvalidDataTypeException
1381
-     * @throws EE_Error
1382
-     */
1383
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
1384
-    {
1385
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
1386
-        if (! $object_to_cache instanceof EE_Base_Class) {
1387
-            return false;
1388
-        }
1389
-        // also get "how" the object is related, or throw an error
1390
-        if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1391
-            throw new EE_Error(
1392
-                sprintf(
1393
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
1394
-                    $relationName,
1395
-                    get_class($this)
1396
-                )
1397
-            );
1398
-        }
1399
-        // how many things are related ?
1400
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1401
-            // if it's a "belongs to" relationship, then there's only one related model object
1402
-            // eg, if this is a registration, there's only 1 attendee for it
1403
-            // so for these model objects just set it to be cached
1404
-            $this->_model_relations[ $relationName ] = $object_to_cache;
1405
-            return true;
1406
-        }
1407
-        // otherwise, this is the "many" side of a one to many relationship,
1408
-        // so we'll add the object to the array of related objects for that type.
1409
-        // eg: if this is an event, there are many registrations for that event,
1410
-        // so we cache the registrations in an array
1411
-        if (! is_array($this->_model_relations[ $relationName ])) {
1412
-            // if for some reason, the cached item is a model object,
1413
-            // then stick that in the array, otherwise start with an empty array
1414
-            $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ] instanceof EE_Base_Class
1415
-                ? [$this->_model_relations[ $relationName ]]
1416
-                : [];
1417
-        }
1418
-        // first check for a cache_id which is normally empty
1419
-        if (! empty($cache_id)) {
1420
-            // if the cache_id exists, then it means we are purposely trying to cache this
1421
-            // with a known key that can then be used to retrieve the object later on
1422
-            $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
1423
-            return $cache_id;
1424
-        }
1425
-        if ($object_to_cache->ID()) {
1426
-            // OR the cached object originally came from the db, so let's just use it's PK for an ID
1427
-            $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
1428
-            return $object_to_cache->ID();
1429
-        }
1430
-        // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
1431
-        $this->_model_relations[ $relationName ][] = $object_to_cache;
1432
-        // move the internal pointer to the end of the array
1433
-        end($this->_model_relations[ $relationName ]);
1434
-        // and grab the key so that we can return it
1435
-        return key($this->_model_relations[ $relationName ]);
1436
-    }
1437
-
1438
-
1439
-    /**
1440
-     * This just returns whatever is set for the current timezone.
1441
-     *
1442
-     * @return string timezone string
1443
-     */
1444
-    public function get_timezone()
1445
-    {
1446
-        if (empty($this->_timezone)) {
1447
-            $this->set_timezone();
1448
-        }
1449
-        return $this->_timezone;
1450
-    }
1451
-
1452
-
1453
-    /**
1454
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
1455
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
1456
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
1457
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
1458
-     *
1459
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
1460
-     * @param bool   $only_if_not_set if true and $this->_timezone already has a value, then will not do anything
1461
-     * @return void
1462
-     */
1463
-    public function set_timezone(string $timezone = '', $only_if_not_set = false)
1464
-    {
1465
-        static $set_in_progress = false;
1466
-        // don't update the timezone if it's already set ?
1467
-        if (($only_if_not_set && $this->_timezone !== '') ) {
1468
-            return;
1469
-        }
1470
-        $valid_timezone = ! empty($timezone) && $this->_timezone && $timezone !== $this->_timezone
1471
-            ? EEH_DTT_Helper::get_valid_timezone_string($timezone)
1472
-            : $this->_timezone;
1473
-        // do NOT set the timezone if we are already in the process of setting the timezone
1474
-        // OR the existing timezone is already set and the incoming value is nothing (which gets set to default TZ)
1475
-        // OR the existing timezone is already set and the validated value is the same as the existing timezone
1476
-        if (
1477
-            $set_in_progress
1478
-            || (
1479
-                ! empty($this->_timezone)
1480
-                && (
1481
-                    empty($timezone) || $valid_timezone === $this->_timezone
1482
-                )
1483
-            )
1484
-        ) {
1485
-            return;
1486
-        }
1487
-        $set_in_progress = true;
1488
-        $this->_timezone = $valid_timezone ? $valid_timezone : EEH_DTT_Helper::get_valid_timezone_string();
1489
-        $TZ = new DateTimeZone($this->_timezone);
1490
-        // make sure we clear all cached properties because they won't be relevant now
1491
-        $this->_clear_cached_properties();
1492
-        // make sure we update field settings and the date for all EE_Datetime_Fields
1493
-        $model_fields = $this->_model->field_settings();
1494
-        foreach ($model_fields as $field_name => $field_obj) {
1495
-            if ($field_obj instanceof EE_Datetime_Field) {
1496
-                $field_obj->set_timezone($this->_timezone);
1497
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
1498
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], $TZ);
1499
-                }
1500
-            }
1501
-        }
1502
-        $set_in_progress = false;
1503
-    }
1504
-
1505
-
1506
-    /**
1507
-     * @param array|EE_Base_Class $related
1508
-     * @since $VID:$
1509
-     */
1510
-    private function updateTimezoneOnRelated($related)
1511
-    {
1512
-        if ($related instanceof EE_Base_Class && $related->get_timezone() !== $this->_timezone) {
1513
-            $related->set_timezone($this->_timezone);
1514
-            return;
1515
-        }
1516
-        if (is_array($related)) {
1517
-            foreach ($related as $object) {
1518
-                $this->updateTimezoneOnRelated($object);
1519
-            }
1520
-        }
1521
-    }
1522
-
1523
-
1524
-    /**
1525
-     * This returns the current internal set format for the date and time formats.
1526
-     *
1527
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
1528
-     *                             where the first value is the date format and the second value is the time format.
1529
-     * @return mixed string|array
1530
-     */
1531
-    public function get_format($full = true)
1532
-    {
1533
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1534
-    }
1535
-
1536
-
1537
-    /**
1538
-     * update_cache_after_object_save
1539
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
1540
-     * obtained after being saved to the db
1541
-     *
1542
-     * @param string        $relationName       - the type of object that is cached
1543
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
1544
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
1545
-     * @return boolean TRUE on success, FALSE on fail
1546
-     * @throws InvalidArgumentException
1547
-     * @throws InvalidDataTypeException
1548
-     * @throws InvalidInterfaceException
1549
-     * @throws EE_Error
1550
-     */
1551
-    public function update_cache_after_object_save(
1552
-        $relationName,
1553
-        EE_Base_Class $newly_saved_object,
1554
-        $current_cache_id = ''
1555
-    ) {
1556
-        // verify that incoming object is of the correct type
1557
-        $obj_class = 'EE_' . $relationName;
1558
-        if (! $newly_saved_object instanceof $obj_class) {
1559
-            return false;
1560
-        }
16
+	/**
17
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
18
+	 * For example, we might create model objects intended to only be used for the duration
19
+	 * of this request and to be thrown away, and if they were accidentally saved
20
+	 * it would be a bug.
21
+	 */
22
+	protected $_allow_persist = true;
23
+
24
+	/**
25
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
26
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
27
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
28
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
29
+	 *
30
+	 * @var array
31
+	 */
32
+	protected $_cached_properties = [];
33
+
34
+	/**
35
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
36
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
37
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
38
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
39
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
40
+	 * array as:
41
+	 * array(
42
+	 *  'Registration_Count' => 24
43
+	 * );
44
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
45
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
46
+	 * info)
47
+	 *
48
+	 * @var array
49
+	 */
50
+	protected $custom_selection_results = [];
51
+
52
+	/**
53
+	 * date format
54
+	 * pattern or format for displaying dates
55
+	 *
56
+	 * @var string $_dt_frmt
57
+	 */
58
+	protected $_dt_frmt;
59
+
60
+	/**
61
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
62
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
63
+	 *
64
+	 * @var array
65
+	 */
66
+	protected $_fields = [];
67
+
68
+	/**
69
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
70
+	 */
71
+	protected $_has_changes = false;
72
+
73
+	/**
74
+	 * @var EEM_Base
75
+	 */
76
+	protected $_model;
77
+
78
+
79
+	/**
80
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
81
+	 * single
82
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
83
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
84
+	 * all others have an array)
85
+	 *
86
+	 * @var array
87
+	 */
88
+	protected $_model_relations = [];
89
+
90
+	/**
91
+	 * This is an array of the original properties and values provided during construction
92
+	 * of this model object. (keys are model field names, values are their values).
93
+	 * This list is important to remember so that when we are merging data from the db, we know
94
+	 * which values to override and which to not override.
95
+	 *
96
+	 * @var array
97
+	 */
98
+	protected $_props_n_values_provided_in_constructor;
99
+
100
+	/**
101
+	 * Timezone
102
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
103
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
104
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
105
+	 * access to it.
106
+	 *
107
+	 * @var string
108
+	 */
109
+	protected $_timezone = '';
110
+
111
+	/**
112
+	 * time format
113
+	 * pattern or format for displaying time
114
+	 *
115
+	 * @var string $_tm_frmt
116
+	 */
117
+	protected $_tm_frmt;
118
+
119
+
120
+	/**
121
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
122
+	 * play nice
123
+	 *
124
+	 * @param array   $field_values                            where each key is a field (ie, array key in the 2nd
125
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
126
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
127
+	 * @param bool $set_from_db                             a flag for setting if the class is instantiated by the
128
+	 *                                                         corresponding db model or not.
129
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
130
+	 *                                                         be in when instantiating a EE_Base_Class object.
131
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
132
+	 *                                                         value is the date_format and second value is the time
133
+	 *                                                         format.
134
+	 * @throws InvalidArgumentException
135
+	 * @throws InvalidInterfaceException
136
+	 * @throws InvalidDataTypeException
137
+	 * @throws EE_Error
138
+	 * @throws ReflectionException
139
+	 */
140
+	protected function __construct(
141
+		array $field_values = [],
142
+		bool $set_from_db = false,
143
+		string $timezone = '',
144
+		array $date_formats = []
145
+	) {
146
+		// ensure $fieldValues is an array
147
+		$field_values = is_array($field_values) ? $field_values : [$field_values];
148
+		$className    = get_class($this);
149
+		do_action("AHEE__{$className}__construct", $this, $field_values);
150
+		// remember what values were passed to this constructor
151
+		$this->_props_n_values_provided_in_constructor = $field_values;
152
+		$this->setDateAndTimeFormats($date_formats);
153
+		$this->_model = $this->get_model($timezone);
154
+		$model_fields = $this->_model->field_settings();
155
+		$this->validateFieldValues($model_fields, $field_values);
156
+		$this->setFieldValues($model_fields, $field_values, $set_from_db);
157
+		// remember in entity mapper
158
+		if (! $set_from_db && $this->_model->has_primary_key_field() && $this->ID()) {
159
+			$this->_model->add_to_entity_map($this);
160
+		}
161
+		// setup all the relations
162
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
163
+			$this->_model_relations[ $relation_name ] = $relation_obj instanceof EE_Belongs_To_Relation
164
+				? null
165
+				: [];
166
+		}
167
+		/**
168
+		 * Action done at the end of each model object construction
169
+		 *
170
+		 * @param EE_Base_Class $this the model object just created
171
+		 */
172
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
173
+	}
174
+
175
+
176
+	/**
177
+	 * for getting a model while instantiated.
178
+	 *
179
+	 * @param string $timezone
180
+	 * @return EEM_Base | EEM_CPT_Base
181
+	 * @throws EE_Error
182
+	 * @throws ReflectionException
183
+	 */
184
+	public function get_model(string $timezone = '')
185
+	{
186
+		if (! $this->_model) {
187
+			$timezone     = $timezone ?? $this->_timezone;
188
+			$modelName    = self::_get_model_classname(get_class($this));
189
+			$this->_model = self::_get_model_instance_with_name($modelName, $timezone);
190
+			$this->set_timezone($timezone);
191
+		}
192
+		return $this->_model;
193
+	}
194
+
195
+
196
+	/**
197
+	 * Overrides parent because parent expects old models.
198
+	 * This also doesn't do any validation, and won't work for serialized arrays
199
+	 *
200
+	 * @param string $field_name
201
+	 * @param mixed  $field_value_from_db
202
+	 * @throws InvalidArgumentException
203
+	 * @throws InvalidInterfaceException
204
+	 * @throws InvalidDataTypeException
205
+	 * @throws EE_Error
206
+	 */
207
+	public function set_from_db(string $field_name, $field_value_from_db)
208
+	{
209
+		$field_obj = $this->_model->field_settings_for($field_name);
210
+		if ($field_obj instanceof EE_Model_Field_Base) {
211
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
212
+			// eg, a CPT model object could have an entry in the posts table, but no
213
+			// entry in the meta table. Meaning that all its columns in the meta table
214
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
215
+
216
+			if ($field_value_from_db === null) {
217
+				if ($field_obj->is_nullable()) {
218
+					// if the field allows nulls, then let it be null
219
+					$field_value = null;
220
+				} else {
221
+					$field_value = $field_obj->get_default_value();
222
+				}
223
+			} else {
224
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
225
+			}
226
+			$this->_fields[ $field_name ] = $field_value;
227
+			$this->_clear_cached_property($field_name);
228
+		}
229
+	}
230
+
231
+
232
+	/**
233
+	 * Overrides parent because parent expects old models.
234
+	 * This also doesn't do any validation, and won't work for serialized arrays
235
+	 *
236
+	 * @param string $field_name
237
+	 * @param mixed  $field_value
238
+	 * @param bool   $use_default
239
+	 * @throws InvalidArgumentException
240
+	 * @throws InvalidInterfaceException
241
+	 * @throws InvalidDataTypeException
242
+	 * @throws EE_Error
243
+	 * @throws ReflectionException
244
+	 * @throws ReflectionException
245
+	 * @throws ReflectionException
246
+	 */
247
+	public function set(string $field_name, $field_value, bool $use_default = false)
248
+	{
249
+		// if not using default and nothing has changed, and object has already been setup (has ID),
250
+		// then don't do anything
251
+		if (
252
+			! $use_default
253
+			&& $this->_fields[ $field_name ] === $field_value
254
+			&& $this->ID()
255
+		) {
256
+			return;
257
+		}
258
+		$this->_has_changes = true;
259
+		$field_obj          = $this->_model->field_settings_for($field_name);
260
+		if ($field_obj instanceof EE_Model_Field_Base) {
261
+			if ($field_obj instanceof EE_Datetime_Field) {
262
+				$field_obj->set_timezone($this->_timezone);
263
+				$field_obj->set_date_format($this->_dt_frmt);
264
+				$field_obj->set_time_format($this->_tm_frmt);
265
+			}
266
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
267
+			// should the value be null?
268
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
269
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
270
+				/**
271
+				 * To save having to refactor all the models, if a default value is used for a
272
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
273
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
274
+				 * object.
275
+				 *
276
+				 * @since 4.6.10+
277
+				 */
278
+				if (
279
+					$field_obj instanceof EE_Datetime_Field
280
+					&& $this->_fields[ $field_name ] !== null
281
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
282
+				) {
283
+					empty($this->_fields[ $field_name ])
284
+						? $this->set($field_name, time())
285
+						: $this->set($field_name, $this->_fields[ $field_name ]);
286
+				}
287
+			} else {
288
+				$this->_fields[ $field_name ] = $holder_of_value;
289
+			}
290
+			// if we're not in the constructor...
291
+			// now check if what we set was a primary key
292
+			if (
293
+				// note: props_n_values_provided_in_constructor is only set at the END of the constructor
294
+				$this->_props_n_values_provided_in_constructor
295
+				&& $field_value
296
+				&& $field_name === $this->_model->primary_key_name()
297
+			) {
298
+				// if so, we want all this object's fields to be filled either with
299
+				// what we've explicitly set on this model
300
+				// or what we have in the db
301
+				// echo "setting primary key!";
302
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
303
+				$obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
304
+				foreach ($fields_on_model as $field_obj) {
305
+					if (
306
+						! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
307
+						&& $field_obj->get_name() !== $field_name
308
+					) {
309
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
310
+					}
311
+				}
312
+				// oh this model object has an ID? well make sure its in the entity mapper
313
+				$this->_model->add_to_entity_map($this);
314
+			}
315
+			// let's unset any cache for this field_name from the $_cached_properties property.
316
+			$this->_clear_cached_property($field_name);
317
+		} else {
318
+			throw new EE_Error(
319
+				sprintf(
320
+					esc_html__(
321
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
322
+						'event_espresso'
323
+					),
324
+					$field_name
325
+				)
326
+			);
327
+		}
328
+	}
329
+
330
+
331
+	/**
332
+	 * Gets the value of the primary key.
333
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
334
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
335
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
336
+	 *
337
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
338
+	 * @throws InvalidArgumentException
339
+	 * @throws InvalidInterfaceException
340
+	 * @throws InvalidDataTypeException
341
+	 * @throws EE_Error
342
+	 */
343
+	public function ID()
344
+	{
345
+		// now that we know the name of the variable, use a variable variable to get its value and return its
346
+		if ($this->_model->has_primary_key_field()) {
347
+			return $this->_fields[ $this->_model->primary_key_name() ];
348
+		}
349
+		return $this->_model->get_index_primary_key_string($this->_fields);
350
+	}
351
+
352
+
353
+	/**
354
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
355
+	 * Also works if a model class's classname is provided (eg EE_Registration).
356
+	 *
357
+	 * @param string $model_name
358
+	 * @return string like EEM_Attendee
359
+	 */
360
+	private static function _get_model_classname(string $model_name = ''): string
361
+	{
362
+		return strpos($model_name, 'EE_') === 0
363
+			? str_replace('EE_', 'EEM_', $model_name)
364
+			: 'EEM_' . $model_name;
365
+	}
366
+
367
+
368
+	/**
369
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
370
+	 *
371
+	 * @param string $model_classname
372
+	 * @param string $timezone
373
+	 * @return EEM_Base
374
+	 * @throws ReflectionException
375
+	 * @throws InvalidArgumentException
376
+	 * @throws InvalidInterfaceException
377
+	 * @throws InvalidDataTypeException
378
+	 * @throws EE_Error
379
+	 */
380
+	protected static function _get_model_instance_with_name(string $model_classname, string $timezone = ''): EEM_Base
381
+	{
382
+		$model_classname = str_replace('EEM_', '', $model_classname);
383
+		$model           = EE_Registry::instance()->load_model($model_classname);
384
+		$model->set_timezone($timezone);
385
+		return $model;
386
+	}
387
+
388
+
389
+	/**
390
+	 * This just clears out ONE property if it exists in the cache
391
+	 *
392
+	 * @param string $property_name the property to remove if it exists (from the _cached_properties array)
393
+	 * @return void
394
+	 */
395
+	protected function _clear_cached_property(string $property_name)
396
+	{
397
+		unset($this->_cached_properties[ $property_name ]);
398
+	}
399
+
400
+
401
+	/**
402
+	 * Gets the EEM_*_Model for this class
403
+	 *
404
+	 * @param string $classname
405
+	 * @param string $timezone
406
+	 * @return EEM_Base
407
+	 * @throws InvalidArgumentException
408
+	 * @throws InvalidInterfaceException
409
+	 * @throws InvalidDataTypeException
410
+	 * @throws EE_Error
411
+	 * @throws ReflectionException
412
+	 */
413
+	protected static function _get_model(string $classname, string $timezone = ''): EEM_Base
414
+	{
415
+		// find model for this class
416
+		if (! $classname) {
417
+			throw new EE_Error(
418
+				sprintf(
419
+					esc_html__(
420
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
421
+						'event_espresso'
422
+					),
423
+					$classname
424
+				)
425
+			);
426
+		}
427
+		$modelName = self::_get_model_classname($classname);
428
+		return self::_get_model_instance_with_name($modelName, $timezone);
429
+	}
430
+
431
+
432
+	/**
433
+	 * verifies that the specified field is of the correct type
434
+	 *
435
+	 * @param string $field_name
436
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
437
+	 *                                (in cases where the same property may be used for different outputs
438
+	 *                                - i.e. datetime, money etc.)
439
+	 * @return mixed
440
+	 * @throws InvalidArgumentException
441
+	 * @throws InvalidInterfaceException
442
+	 * @throws InvalidDataTypeException
443
+	 * @throws EE_Error
444
+	 */
445
+	public function get(string $field_name, string $extra_cache_ref = '')
446
+	{
447
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
448
+	}
449
+
450
+
451
+	/**
452
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
453
+	 * This also SETS the cache if we return the actual property!
454
+	 *
455
+	 * @param string $field_name       the name of the property we're trying to retrieve
456
+	 * @param bool   $pretty
457
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
458
+	 *                                 (in cases where the same property may be used for different outputs
459
+	 *                                 - i.e. datetime, money etc.)
460
+	 *                                 It can also accept certain pre-defined "schema" strings
461
+	 *                                 to define how to output the property.
462
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
463
+	 * @return mixed                   whatever the value for the property is we're retrieving
464
+	 * @throws InvalidArgumentException
465
+	 * @throws InvalidInterfaceException
466
+	 * @throws InvalidDataTypeException
467
+	 * @throws EE_Error
468
+	 */
469
+	protected function _get_cached_property(string $field_name, bool $pretty = false, string $extra_cache_ref = '')
470
+	{
471
+		// verify the field exists
472
+		$this->_model->field_settings_for($field_name);
473
+		$cache_type = $pretty ? 'pretty' : 'standard';
474
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
475
+		if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
476
+			return $this->_cached_properties[ $field_name ][ $cache_type ];
477
+		}
478
+		$value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
479
+		$this->_set_cached_property($field_name, $value, $cache_type);
480
+		return $value;
481
+	}
482
+
483
+
484
+	/**
485
+	 * If the cache didn't fetch the needed item, this fetches it.
486
+	 *
487
+	 * @param string $field_name
488
+	 * @param bool   $pretty
489
+	 * @param string $extra_cache_ref
490
+	 * @return mixed
491
+	 * @throws InvalidArgumentException
492
+	 * @throws InvalidInterfaceException
493
+	 * @throws InvalidDataTypeException
494
+	 * @throws EE_Error
495
+	 */
496
+	protected function _get_fresh_property(string $field_name, bool $pretty = false, string $extra_cache_ref = '')
497
+	{
498
+		$field_obj = $this->_model->field_settings_for($field_name);
499
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
500
+		if ($field_obj instanceof EE_Datetime_Field) {
501
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
502
+		}
503
+		if (! isset($this->_fields[ $field_name ])) {
504
+			$this->_fields[ $field_name ] = null;
505
+		}
506
+		return $pretty
507
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
508
+			: $field_obj->prepare_for_get($this->_fields[ $field_name ]);
509
+	}
510
+
511
+
512
+	/**
513
+	 * For adding an item to the cached_properties property.
514
+	 *
515
+	 * @param string      $field_name the property item the corresponding value is for.
516
+	 * @param mixed       $value      The value we are caching.
517
+	 * @param string $cache_type
518
+	 * @return void
519
+	 * @throws InvalidArgumentException
520
+	 * @throws InvalidInterfaceException
521
+	 * @throws InvalidDataTypeException
522
+	 * @throws EE_Error
523
+	 */
524
+	protected function _set_cached_property(string $field_name, $value, string $cache_type = '')
525
+	{
526
+		// first make sure this property exists
527
+		$this->_model->field_settings_for($field_name);
528
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
529
+		$this->_cached_properties[ $field_name ][ $cache_type ] = $value;
530
+	}
531
+
532
+
533
+	/**
534
+	 * set timezone, formats, and output for EE_Datetime_Field objects
535
+	 *
536
+	 * @param EE_Datetime_Field $datetime_field
537
+	 * @param bool              $pretty
538
+	 * @param null              $date_or_time
539
+	 * @return void
540
+	 * @throws InvalidArgumentException
541
+	 * @throws InvalidInterfaceException
542
+	 * @throws InvalidDataTypeException
543
+	 */
544
+	protected function _prepare_datetime_field(
545
+		EE_Datetime_Field $datetime_field,
546
+		bool $pretty = false,
547
+		$date_or_time = null
548
+	) {
549
+		$datetime_field->set_timezone($this->_timezone);
550
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
551
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
552
+		// set the output returned
553
+		switch ($date_or_time) {
554
+			case 'D':
555
+				$datetime_field->set_date_time_output('date');
556
+				break;
557
+			case 'T':
558
+				$datetime_field->set_date_time_output('time');
559
+				break;
560
+			default:
561
+				$datetime_field->set_date_time_output();
562
+		}
563
+	}
564
+
565
+
566
+	/**
567
+	 * @param $props_n_values
568
+	 * @param $classname
569
+	 * @return EE_Base_Class|null
570
+	 * @throws ReflectionException
571
+	 * @throws InvalidArgumentException
572
+	 * @throws InvalidInterfaceException
573
+	 * @throws InvalidDataTypeException
574
+	 * @throws EE_Error
575
+	 * @throws ReflectionException
576
+	 */
577
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname): ?EE_Base_Class
578
+	{
579
+		// TODO: will not work for Term_Relationships because they have no PK!
580
+		$primary_id_ref = self::_get_primary_key_name($classname);
581
+		if (
582
+			array_key_exists($primary_id_ref, $props_n_values)
583
+			&& ! empty($props_n_values[ $primary_id_ref ])
584
+		) {
585
+			$id = $props_n_values[ $primary_id_ref ];
586
+			return self::_get_model($classname)->get_from_entity_map($id);
587
+		}
588
+		return null;
589
+	}
590
+
591
+
592
+	/**
593
+	 * returns the name of the primary key attribute
594
+	 *
595
+	 * @param null $classname
596
+	 * @return string
597
+	 * @throws InvalidArgumentException
598
+	 * @throws InvalidInterfaceException
599
+	 * @throws InvalidDataTypeException
600
+	 * @throws EE_Error
601
+	 * @throws ReflectionException
602
+	 * @throws ReflectionException
603
+	 * @throws ReflectionException
604
+	 */
605
+	protected static function _get_primary_key_name($classname = null): string
606
+	{
607
+		if (! $classname) {
608
+			throw new EE_Error(
609
+				sprintf(
610
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
611
+					$classname
612
+				)
613
+			);
614
+		}
615
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
616
+	}
617
+
618
+
619
+	/**
620
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
621
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
622
+	 * 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
623
+	 * we return false.
624
+	 *
625
+	 * @param array  $props_n_values    incoming array of properties and their values
626
+	 * @param string $classname         the classname of the child class
627
+	 * @param string $timezone
628
+	 * @param array  $date_formats      incoming date_formats in an array where the first value is the
629
+	 *                                  date_format and the second value is the time format
630
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|null
631
+	 * @throws InvalidArgumentException
632
+	 * @throws InvalidInterfaceException
633
+	 * @throws InvalidDataTypeException
634
+	 * @throws EE_Error
635
+	 * @throws ReflectionException
636
+	 * @throws ReflectionException
637
+	 * @throws ReflectionException
638
+	 */
639
+	protected static function _check_for_object(
640
+		array $props_n_values, string $classname, string $timezone = '', array $date_formats = []
641
+	) {
642
+		$existing = null;
643
+		$model    = self::_get_model($classname, $timezone);
644
+		if ($model->has_primary_key_field()) {
645
+			$primary_id_ref = self::_get_primary_key_name($classname);
646
+			if (
647
+				array_key_exists($primary_id_ref, $props_n_values)
648
+				&& ! empty($props_n_values[ $primary_id_ref ])
649
+			) {
650
+				$existing = $model->get_one_by_ID($props_n_values[ $primary_id_ref ]);
651
+			}
652
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
653
+			// no primary key on this model, but there's still a matching item in the DB
654
+			$existing = $model->get_one_by_ID(
655
+				$model->get_index_primary_key_string($props_n_values)
656
+			);
657
+		}
658
+		if ($existing) {
659
+			$has_date_formats = ! empty($date_formats) && is_array($date_formats)
660
+								&& isset($date_formats[0], $date_formats[1]);
661
+			$date_format      = $has_date_formats ? $date_formats[0] : (string) get_option('date_format', 'Y-m-d');
662
+			$time_format      = $has_date_formats ? $date_formats[1] : (string) get_option('time_format', 'g:i a');
663
+			// set date formats before setting values
664
+			$existing->set_date_format($date_format);
665
+			$existing->set_time_format($time_format);
666
+			foreach ($props_n_values as $property => $field_value) {
667
+				$existing->set($property, $field_value);
668
+			}
669
+			return $existing;
670
+		}
671
+		return null;
672
+	}
673
+
674
+
675
+	/**
676
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
677
+	 * internally instead of wp set date format options
678
+	 *
679
+	 * @param string $format should be a format recognizable by PHP date() functions.
680
+	 * @since 4.6
681
+	 */
682
+	public function set_date_format($format)
683
+	{
684
+		if ($format !== $this->_dt_frmt) {
685
+			$this->_dt_frmt = $format;
686
+			// clear cached_properties because they won't be relevant now.
687
+			$this->_clear_cached_properties();
688
+		}
689
+	}
690
+
691
+
692
+	/**
693
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
694
+	 * class internally instead of wp set time format options.
695
+	 *
696
+	 * @param string $format should be a format recognizable by PHP date() functions.
697
+	 * @since 4.6
698
+	 */
699
+	public function set_time_format($format)
700
+	{
701
+		if ($format !== $this->_tm_frmt) {
702
+			$this->_tm_frmt = $format;
703
+			// clear cached_properties because they won't be relevant now.
704
+			$this->_clear_cached_properties();
705
+		}
706
+	}
707
+
708
+
709
+	/**
710
+	 * This just takes care of clearing out the cached_properties
711
+	 *
712
+	 * @return void
713
+	 */
714
+	protected function _clear_cached_properties()
715
+	{
716
+		$this->_cached_properties = [];
717
+	}
718
+
719
+
720
+	/**
721
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
722
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
723
+	 * you got new information that somehow made you change your mind.
724
+	 *
725
+	 * @param boolean $allow_persist
726
+	 * @return boolean
727
+	 */
728
+	public function set_allow_persist($allow_persist)
729
+	{
730
+		return $this->_allow_persist = $allow_persist;
731
+	}
732
+
733
+
734
+	/**
735
+	 * Gets the field's original value when this object was constructed during this request.
736
+	 * This can be helpful when determining if a model object has changed or not
737
+	 *
738
+	 * @param string $field_name
739
+	 * @return mixed|null
740
+	 * @throws InvalidArgumentException
741
+	 * @throws InvalidInterfaceException
742
+	 * @throws InvalidDataTypeException
743
+	 * @throws EE_Error
744
+	 */
745
+	public function get_original(string $field_name)
746
+	{
747
+		if (
748
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
749
+			&& $field_settings = $this->_model->field_settings_for($field_name)
750
+		) {
751
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
752
+		}
753
+		return null;
754
+	}
755
+
756
+
757
+	/**
758
+	 * @param EE_Base_Class $obj
759
+	 * @return string
760
+	 */
761
+	public function get_class($obj)
762
+	{
763
+		return get_class($obj);
764
+	}
765
+
766
+
767
+	/**
768
+	 * Set custom select values for model.
769
+	 *
770
+	 * @param array $custom_select_values
771
+	 */
772
+	public function setCustomSelectsValues(array $custom_select_values)
773
+	{
774
+		$this->custom_selection_results = $custom_select_values;
775
+	}
776
+
777
+
778
+	/**
779
+	 * Returns the custom select value for the provided alias if its set.
780
+	 * If not set, returns null.
781
+	 *
782
+	 * @param string $alias
783
+	 * @return string|int|float|null
784
+	 */
785
+	public function getCustomSelect($alias)
786
+	{
787
+		return isset($this->custom_selection_results[ $alias ])
788
+			? $this->custom_selection_results[ $alias ]
789
+			: null;
790
+	}
791
+
792
+
793
+	/**
794
+	 * This sets the field value on the db column if it exists for the given $column_name or
795
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
796
+	 *
797
+	 * @param string $field_name  Must be the exact column name.
798
+	 * @param mixed  $field_value The value to set.
799
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
800
+	 * @throws InvalidArgumentException
801
+	 * @throws InvalidInterfaceException
802
+	 * @throws InvalidDataTypeException
803
+	 * @throws EE_Error
804
+	 * @throws ReflectionException
805
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
806
+	 */
807
+	public function set_field_or_extra_meta(string $field_name, $field_value)
808
+	{
809
+		if ($this->_model->has_field($field_name)) {
810
+			$this->set($field_name, $field_value);
811
+			return true;
812
+		}
813
+		// ensure this object is saved first so that extra meta can be properly related.
814
+		$this->save();
815
+		return $this->update_extra_meta($field_name, $field_value);
816
+	}
817
+
818
+
819
+	/**
820
+	 *        Saves this object to the database. An array may be supplied to set some values on this
821
+	 * object just before saving.
822
+	 *
823
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
824
+	 *                                 if provided during the save() method
825
+	 *                                 (often client code will change the fields' values before calling save)
826
+	 * @return int                     1 on a successful update or the ID of the new entry on insert;
827
+	 *                                 0 on failure or if the model object isn't allowed to persist
828
+	 *                                 (as determined by EE_Base_Class::allow_persist())
829
+	 * @throws InvalidInterfaceException
830
+	 * @throws InvalidDataTypeException
831
+	 * @throws EE_Error
832
+	 * @throws InvalidArgumentException
833
+	 * @throws ReflectionException
834
+	 * @throws ReflectionException
835
+	 * @throws ReflectionException
836
+	 */
837
+	public function save($set_cols_n_values = [])
838
+	{
839
+		/**
840
+		 * Filters the fields we're about to save on the model object
841
+		 *
842
+		 * @param array         $set_cols_n_values
843
+		 * @param EE_Base_Class $model_object
844
+		 */
845
+		$set_cols_n_values = (array) apply_filters(
846
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
847
+			$set_cols_n_values,
848
+			$this
849
+		);
850
+		// set attributes as provided in $set_cols_n_values
851
+		foreach ($set_cols_n_values as $column => $value) {
852
+			$this->set($column, $value);
853
+		}
854
+		// no changes ? then don't do anything
855
+		if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
856
+			return 0;
857
+		}
858
+		/**
859
+		 * Saving a model object.
860
+		 * Before we perform a save, this action is fired.
861
+		 *
862
+		 * @param EE_Base_Class $model_object the model object about to be saved.
863
+		 */
864
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
865
+		if (! $this->allow_persist()) {
866
+			return 0;
867
+		}
868
+		// now get current attribute values
869
+		$save_cols_n_values = $this->_fields;
870
+		// if the object already has an ID, update it. Otherwise, insert it
871
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
872
+		// They have been
873
+		$old_assumption_concerning_value_preparation = $this->_model
874
+			->get_assumption_concerning_values_already_prepared_by_model_object();
875
+		$this->_model->assume_values_already_prepared_by_model_object(true);
876
+		// does this model have an autoincrement PK?
877
+		if ($this->_model->has_primary_key_field()) {
878
+			if ($this->_model->get_primary_key_field()->is_auto_increment()) {
879
+				// ok check if it's set, if so: update; if not, insert
880
+				if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
881
+					$results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
882
+				} else {
883
+					unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
884
+					$results = $this->_model->insert($save_cols_n_values);
885
+					if ($results) {
886
+						// if successful, set the primary key
887
+						// but don't use the normal SET method, because it will check if
888
+						// an item with the same ID exists in the mapper & db, then
889
+						// will find it in the db (because we just added it) and THAT object
890
+						// will get added to the mapper before we can add this one!
891
+						// but if we just avoid using the SET method, all that headache can be avoided
892
+						$pk_field_name                   = $this->_model->primary_key_name();
893
+						$this->_fields[ $pk_field_name ] = $results;
894
+						$this->_clear_cached_property($pk_field_name);
895
+						$this->_model->add_to_entity_map($this);
896
+						$this->_update_cached_related_model_objs_fks();
897
+					}
898
+				}
899
+			} else {
900
+				// PK is NOT auto-increment
901
+				// so check if one like it already exists in the db
902
+				if ($this->_model->exists_by_ID($this->ID())) {
903
+					if (WP_DEBUG && ! $this->in_entity_map()) {
904
+						throw new EE_Error(
905
+							sprintf(
906
+								esc_html__(
907
+									'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',
908
+									'event_espresso'
909
+								),
910
+								get_class($this),
911
+								get_class($this->_model) . '::instance()->add_to_entity_map()',
912
+								get_class($this->_model) . '::instance()->get_one_by_ID()',
913
+								'<br />'
914
+							)
915
+						);
916
+					}
917
+					$results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
918
+				} else {
919
+					$results = $this->_model->insert($save_cols_n_values);
920
+					$this->_update_cached_related_model_objs_fks();
921
+				}
922
+			}
923
+		} else {
924
+			// there is NO primary key
925
+			$already_in_db = false;
926
+			foreach ($this->_model->unique_indexes() as $index) {
927
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
928
+				if ($this->_model->exists([$uniqueness_where_params])) {
929
+					$already_in_db = true;
930
+				}
931
+			}
932
+			if ($already_in_db) {
933
+				$combined_pk_fields_n_values = array_intersect_key(
934
+					$save_cols_n_values,
935
+					$this->_model->get_combined_primary_key_fields()
936
+				);
937
+				$results                     = $this->_model->update(
938
+					$save_cols_n_values,
939
+					$combined_pk_fields_n_values
940
+				);
941
+			} else {
942
+				$results = $this->_model->insert($save_cols_n_values);
943
+			}
944
+		}
945
+		// restore the old assumption about values being prepared by the model object
946
+		$this->_model->assume_values_already_prepared_by_model_object(
947
+			$old_assumption_concerning_value_preparation
948
+		);
949
+		/**
950
+		 * After saving the model object this action is called
951
+		 *
952
+		 * @param EE_Base_Class $model_object which was just saved
953
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
954
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
955
+		 */
956
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
957
+		$this->_has_changes = false;
958
+		return $results;
959
+	}
960
+
961
+
962
+	/**
963
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
964
+	 * A $previous_value can be specified in case there are many meta rows with the same key
965
+	 *
966
+	 * @param string $meta_key
967
+	 * @param mixed  $meta_value
968
+	 * @param mixed  $previous_value
969
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
970
+	 *                  NOTE: if the values haven't changed, returns 0
971
+	 * @throws InvalidArgumentException
972
+	 * @throws InvalidInterfaceException
973
+	 * @throws InvalidDataTypeException
974
+	 * @throws EE_Error
975
+	 * @throws ReflectionException
976
+	 */
977
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
978
+	{
979
+		$query_params = [
980
+			[
981
+				'EXM_key'  => $meta_key,
982
+				'OBJ_ID'   => $this->ID(),
983
+				'EXM_type' => $this->_model->get_this_model_name(),
984
+			],
985
+		];
986
+		if ($previous_value !== null) {
987
+			$query_params[0]['EXM_value'] = $meta_value;
988
+		}
989
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
990
+		if (! $existing_rows_like_that) {
991
+			return $this->add_extra_meta($meta_key, $meta_value);
992
+		}
993
+		foreach ($existing_rows_like_that as $existing_row) {
994
+			$existing_row->save(['EXM_value' => $meta_value]);
995
+		}
996
+		return count($existing_rows_like_that);
997
+	}
998
+
999
+
1000
+	/**
1001
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
1002
+	 *
1003
+	 * @return boolean
1004
+	 */
1005
+	public function allow_persist()
1006
+	{
1007
+		return $this->_allow_persist;
1008
+	}
1009
+
1010
+
1011
+	/**
1012
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1013
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1014
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1015
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1016
+	 * 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
1017
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1018
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1019
+	 *
1020
+	 * @return void
1021
+	 * @throws ReflectionException
1022
+	 * @throws InvalidArgumentException
1023
+	 * @throws InvalidInterfaceException
1024
+	 * @throws InvalidDataTypeException
1025
+	 * @throws EE_Error
1026
+	 */
1027
+	protected function _update_cached_related_model_objs_fks()
1028
+	{
1029
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1030
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1031
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1032
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1033
+						$this->_model->get_this_model_name()
1034
+					);
1035
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1036
+					if ($related_model_obj_in_cache->ID()) {
1037
+						$related_model_obj_in_cache->save();
1038
+					}
1039
+				}
1040
+			}
1041
+		}
1042
+	}
1043
+
1044
+
1045
+	/**
1046
+	 * in_entity_map
1047
+	 * Checks if this model object has been proven to already be in the entity map
1048
+	 *
1049
+	 * @return boolean
1050
+	 * @throws InvalidArgumentException
1051
+	 * @throws InvalidInterfaceException
1052
+	 * @throws InvalidDataTypeException
1053
+	 * @throws EE_Error
1054
+	 */
1055
+	public function in_entity_map()
1056
+	{
1057
+		// well, if we looked, did we find it in the entity map?
1058
+		return $this->ID() && $this->_model->get_from_entity_map($this->ID()) === $this;
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
1064
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
1065
+	 * extra meta row was entered, false if not
1066
+	 *
1067
+	 * @param string  $meta_key
1068
+	 * @param mixed   $meta_value
1069
+	 * @param boolean $unique
1070
+	 * @return boolean
1071
+	 * @throws InvalidArgumentException
1072
+	 * @throws InvalidInterfaceException
1073
+	 * @throws InvalidDataTypeException
1074
+	 * @throws EE_Error
1075
+	 * @throws ReflectionException
1076
+	 * @throws ReflectionException
1077
+	 */
1078
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
1079
+	{
1080
+		if ($unique) {
1081
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
1082
+				[
1083
+					[
1084
+						'EXM_key'  => $meta_key,
1085
+						'OBJ_ID'   => $this->ID(),
1086
+						'EXM_type' => $this->_model->get_this_model_name(),
1087
+					],
1088
+				]
1089
+			);
1090
+			if ($existing_extra_meta) {
1091
+				return false;
1092
+			}
1093
+		}
1094
+		$new_extra_meta = EE_Extra_Meta::new_instance(
1095
+			[
1096
+				'EXM_key'   => $meta_key,
1097
+				'EXM_value' => $meta_value,
1098
+				'OBJ_ID'    => $this->ID(),
1099
+				'EXM_type'  => $this->_model->get_this_model_name(),
1100
+			]
1101
+		);
1102
+		$new_extra_meta->save();
1103
+		return true;
1104
+	}
1105
+
1106
+
1107
+	/**
1108
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1109
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1110
+	 *
1111
+	 * @param string $relationName
1112
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
1113
+	 * @throws InvalidArgumentException
1114
+	 * @throws InvalidInterfaceException
1115
+	 * @throws InvalidDataTypeException
1116
+	 * @throws EE_Error
1117
+	 * @throws ReflectionException
1118
+	 */
1119
+	public function get_all_from_cache($relationName)
1120
+	{
1121
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
1122
+		// if the result is not an array, but exists, make it an array
1123
+		$objects = is_array($objects) ? $objects : [$objects];
1124
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
1125
+		// basically, if this model object was stored in the session, and these cached model objects
1126
+		// already have IDs, let's make sure they're in their model's entity mapper
1127
+		// otherwise we will have duplicates next time we call
1128
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
1129
+		$related_model = EE_Registry::instance()->load_model($relationName);
1130
+		foreach ($objects as $model_object) {
1131
+			if ($related_model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
1132
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
1133
+				if ($model_object->ID()) {
1134
+					$related_model->add_to_entity_map($model_object);
1135
+				}
1136
+			} else {
1137
+				throw new EE_Error(
1138
+					sprintf(
1139
+						esc_html__(
1140
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
1141
+							'event_espresso'
1142
+						),
1143
+						$relationName,
1144
+						gettype($model_object)
1145
+					)
1146
+				);
1147
+			}
1148
+		}
1149
+		$this->updateTimezoneOnRelated($objects);
1150
+		return $objects;
1151
+	}
1152
+
1153
+
1154
+	/**
1155
+	 * This retrieves the value of the db column set on this class or if that's not present
1156
+	 * it will attempt to retrieve from extra_meta if found.
1157
+	 * Example Usage:
1158
+	 * Via EE_Message child class:
1159
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
1160
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
1161
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
1162
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
1163
+	 * value for those extra fields dynamically via the EE_message object.
1164
+	 *
1165
+	 * @param string $field_name expecting the fully qualified field name.
1166
+	 * @return mixed|null  value for the field if found.  null if not found.
1167
+	 * @throws ReflectionException
1168
+	 * @throws InvalidArgumentException
1169
+	 * @throws InvalidInterfaceException
1170
+	 * @throws InvalidDataTypeException
1171
+	 * @throws EE_Error
1172
+	 */
1173
+	public function get_field_or_extra_meta(string $field_name)
1174
+	{
1175
+		// if this isn't a column in the main table, then see if it is in the extra meta.
1176
+		return $this->_model->has_field($field_name)
1177
+			? $this->get($field_name)
1178
+			: $this->get_extra_meta($field_name, true);
1179
+	}
1180
+
1181
+
1182
+	/**
1183
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
1184
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
1185
+	 * You can specify $default is case you haven't found the extra meta
1186
+	 *
1187
+	 * @param string  $meta_key
1188
+	 * @param boolean $single
1189
+	 * @param mixed   $default if we don't find anything, what should we return?
1190
+	 * @return mixed single value if $single; array if ! $single
1191
+	 * @throws ReflectionException
1192
+	 * @throws InvalidArgumentException
1193
+	 * @throws InvalidInterfaceException
1194
+	 * @throws InvalidDataTypeException
1195
+	 * @throws EE_Error
1196
+	 */
1197
+	public function get_extra_meta($meta_key, $single = false, $default = null)
1198
+	{
1199
+		if ($single) {
1200
+			$result = $this->get_first_related(
1201
+				'Extra_Meta',
1202
+				[['EXM_key' => $meta_key]]
1203
+			);
1204
+			if ($result instanceof EE_Extra_Meta) {
1205
+				return $result->value();
1206
+			}
1207
+		} else {
1208
+			$results = $this->get_many_related(
1209
+				'Extra_Meta',
1210
+				[['EXM_key' => $meta_key]]
1211
+			);
1212
+			if ($results) {
1213
+				$values = [];
1214
+				foreach ($results as $result) {
1215
+					if ($result instanceof EE_Extra_Meta) {
1216
+						$values[ $result->ID() ] = $result->value();
1217
+					}
1218
+				}
1219
+				return $values;
1220
+			}
1221
+		}
1222
+		// if nothing discovered yet return default.
1223
+		return apply_filters(
1224
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
1225
+			$default,
1226
+			$meta_key,
1227
+			$single,
1228
+			$this
1229
+		);
1230
+	}
1231
+
1232
+
1233
+	/**
1234
+	 * Gets the first (ie, one) related model object of the specified type.
1235
+	 *
1236
+	 * @param string $relationName key in the model's _model_relations array
1237
+	 * @param array  $query_params
1238
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1239
+	 * @return EE_Base_Class (not an array, a single object)
1240
+	 * @throws ReflectionException
1241
+	 * @throws InvalidArgumentException
1242
+	 * @throws InvalidInterfaceException
1243
+	 * @throws InvalidDataTypeException
1244
+	 * @throws EE_Error
1245
+	 */
1246
+	public function get_first_related($relationName, $query_params = [])
1247
+	{
1248
+		$model_relation = $this->_model->related_settings_for($relationName);
1249
+		if (! $this->ID()) {
1250
+			// this doesn't exist in the DB,
1251
+			// but maybe the relation type is "belongs to" and the related object might
1252
+			if ($model_relation instanceof EE_Belongs_To_Relation) {
1253
+				return $this->_model->get_first_related(
1254
+					$this,
1255
+					$relationName,
1256
+					$query_params
1257
+				);
1258
+			}
1259
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
1260
+			// just get what's cached on this object
1261
+			return $this->get_one_from_cache($relationName);
1262
+		}
1263
+		// this exists in the DB, get from the cache OR the DB
1264
+		// if they've provided some query parameters, don't bother trying to cache the result
1265
+		// also make sure we're not caching the result of get_first_related
1266
+		// on a relation which should have an array of objects (because the cache might have an array of objects)
1267
+		if (
1268
+			$query_params
1269
+			|| ! $model_relation instanceof EE_Belongs_To_Relation
1270
+		) {
1271
+			return $this->_model->get_first_related(
1272
+				$this,
1273
+				$relationName,
1274
+				$query_params
1275
+			);
1276
+		}
1277
+		// check if we've already cached the result of this query
1278
+		$cached_result = $this->get_one_from_cache($relationName);
1279
+		if ($cached_result) {
1280
+			return $cached_result;
1281
+		}
1282
+		$related_model_object = $this->_model->get_first_related(
1283
+			$this,
1284
+			$relationName,
1285
+			$query_params
1286
+		);
1287
+		$this->cache($relationName, $related_model_object);
1288
+		return $related_model_object;
1289
+	}
1290
+
1291
+
1292
+	/**
1293
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
1294
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
1295
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
1296
+	 * because we want to get even deleted items etc.
1297
+	 *
1298
+	 * @param string $relationName key in the model's _model_relations array
1299
+	 * @param array  $query_params
1300
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1301
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
1302
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
1303
+	 *                             results if you want IDs
1304
+	 * @throws ReflectionException
1305
+	 * @throws InvalidArgumentException
1306
+	 * @throws InvalidInterfaceException
1307
+	 * @throws InvalidDataTypeException
1308
+	 * @throws EE_Error
1309
+	 */
1310
+	public function get_many_related($relationName, $query_params = [])
1311
+	{
1312
+		if (! $this->ID()) {
1313
+			// this doesn't exist in the DB, so just get the related things from the cache
1314
+			return $this->get_all_from_cache($relationName);
1315
+		}
1316
+		// this exists in the DB, so get the related things from either the cache or the DB
1317
+		// if there are query parameters, forget about caching the related model objects.
1318
+		if ($query_params) {
1319
+			return $this->_model->get_all_related(
1320
+				$this,
1321
+				$relationName,
1322
+				$query_params
1323
+			);
1324
+		}
1325
+		// did we already cache the result of this query?
1326
+		$cached_results = $this->get_all_from_cache($relationName);
1327
+		if ($cached_results) {
1328
+			return $cached_results;
1329
+		}
1330
+		$related_model_objects = $this->_model->get_all_related(
1331
+			$this,
1332
+			$relationName,
1333
+			$query_params
1334
+		);
1335
+		// if no query parameters were passed, then we got all the related model objects
1336
+		// for that relation. We can cache them then.
1337
+		foreach ($related_model_objects as $related_model_object) {
1338
+			$this->cache($relationName, $related_model_object);
1339
+		}
1340
+		$this->updateTimezoneOnRelated($related_model_objects);
1341
+		return $related_model_objects;
1342
+	}
1343
+
1344
+
1345
+	/**
1346
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1347
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1348
+	 *
1349
+	 * @param string $relationName
1350
+	 * @return EE_Base_Class
1351
+	 */
1352
+	public function get_one_from_cache($relationName)
1353
+	{
1354
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
1355
+			? $this->_model_relations[ $relationName ]
1356
+			: null;
1357
+		if (is_array($cached_array_or_object)) {
1358
+			$cached_array_or_object = array_shift($cached_array_or_object);
1359
+		}
1360
+		$this->updateTimezoneOnRelated($cached_array_or_object);
1361
+		return $cached_array_or_object;
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * cache
1367
+	 * stores the passed model object on the current model object.
1368
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
1369
+	 *
1370
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
1371
+	 *                                       'Registration' associated with this model object
1372
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
1373
+	 *                                       that could be a payment or a registration)
1374
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
1375
+	 *                                       items which will be stored in an array on this object
1376
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
1377
+	 *                                       related thing, no array)
1378
+	 * @throws InvalidArgumentException
1379
+	 * @throws InvalidInterfaceException
1380
+	 * @throws InvalidDataTypeException
1381
+	 * @throws EE_Error
1382
+	 */
1383
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
1384
+	{
1385
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
1386
+		if (! $object_to_cache instanceof EE_Base_Class) {
1387
+			return false;
1388
+		}
1389
+		// also get "how" the object is related, or throw an error
1390
+		if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1391
+			throw new EE_Error(
1392
+				sprintf(
1393
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
1394
+					$relationName,
1395
+					get_class($this)
1396
+				)
1397
+			);
1398
+		}
1399
+		// how many things are related ?
1400
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1401
+			// if it's a "belongs to" relationship, then there's only one related model object
1402
+			// eg, if this is a registration, there's only 1 attendee for it
1403
+			// so for these model objects just set it to be cached
1404
+			$this->_model_relations[ $relationName ] = $object_to_cache;
1405
+			return true;
1406
+		}
1407
+		// otherwise, this is the "many" side of a one to many relationship,
1408
+		// so we'll add the object to the array of related objects for that type.
1409
+		// eg: if this is an event, there are many registrations for that event,
1410
+		// so we cache the registrations in an array
1411
+		if (! is_array($this->_model_relations[ $relationName ])) {
1412
+			// if for some reason, the cached item is a model object,
1413
+			// then stick that in the array, otherwise start with an empty array
1414
+			$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ] instanceof EE_Base_Class
1415
+				? [$this->_model_relations[ $relationName ]]
1416
+				: [];
1417
+		}
1418
+		// first check for a cache_id which is normally empty
1419
+		if (! empty($cache_id)) {
1420
+			// if the cache_id exists, then it means we are purposely trying to cache this
1421
+			// with a known key that can then be used to retrieve the object later on
1422
+			$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
1423
+			return $cache_id;
1424
+		}
1425
+		if ($object_to_cache->ID()) {
1426
+			// OR the cached object originally came from the db, so let's just use it's PK for an ID
1427
+			$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
1428
+			return $object_to_cache->ID();
1429
+		}
1430
+		// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
1431
+		$this->_model_relations[ $relationName ][] = $object_to_cache;
1432
+		// move the internal pointer to the end of the array
1433
+		end($this->_model_relations[ $relationName ]);
1434
+		// and grab the key so that we can return it
1435
+		return key($this->_model_relations[ $relationName ]);
1436
+	}
1437
+
1438
+
1439
+	/**
1440
+	 * This just returns whatever is set for the current timezone.
1441
+	 *
1442
+	 * @return string timezone string
1443
+	 */
1444
+	public function get_timezone()
1445
+	{
1446
+		if (empty($this->_timezone)) {
1447
+			$this->set_timezone();
1448
+		}
1449
+		return $this->_timezone;
1450
+	}
1451
+
1452
+
1453
+	/**
1454
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
1455
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
1456
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
1457
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
1458
+	 *
1459
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
1460
+	 * @param bool   $only_if_not_set if true and $this->_timezone already has a value, then will not do anything
1461
+	 * @return void
1462
+	 */
1463
+	public function set_timezone(string $timezone = '', $only_if_not_set = false)
1464
+	{
1465
+		static $set_in_progress = false;
1466
+		// don't update the timezone if it's already set ?
1467
+		if (($only_if_not_set && $this->_timezone !== '') ) {
1468
+			return;
1469
+		}
1470
+		$valid_timezone = ! empty($timezone) && $this->_timezone && $timezone !== $this->_timezone
1471
+			? EEH_DTT_Helper::get_valid_timezone_string($timezone)
1472
+			: $this->_timezone;
1473
+		// do NOT set the timezone if we are already in the process of setting the timezone
1474
+		// OR the existing timezone is already set and the incoming value is nothing (which gets set to default TZ)
1475
+		// OR the existing timezone is already set and the validated value is the same as the existing timezone
1476
+		if (
1477
+			$set_in_progress
1478
+			|| (
1479
+				! empty($this->_timezone)
1480
+				&& (
1481
+					empty($timezone) || $valid_timezone === $this->_timezone
1482
+				)
1483
+			)
1484
+		) {
1485
+			return;
1486
+		}
1487
+		$set_in_progress = true;
1488
+		$this->_timezone = $valid_timezone ? $valid_timezone : EEH_DTT_Helper::get_valid_timezone_string();
1489
+		$TZ = new DateTimeZone($this->_timezone);
1490
+		// make sure we clear all cached properties because they won't be relevant now
1491
+		$this->_clear_cached_properties();
1492
+		// make sure we update field settings and the date for all EE_Datetime_Fields
1493
+		$model_fields = $this->_model->field_settings();
1494
+		foreach ($model_fields as $field_name => $field_obj) {
1495
+			if ($field_obj instanceof EE_Datetime_Field) {
1496
+				$field_obj->set_timezone($this->_timezone);
1497
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
1498
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], $TZ);
1499
+				}
1500
+			}
1501
+		}
1502
+		$set_in_progress = false;
1503
+	}
1504
+
1505
+
1506
+	/**
1507
+	 * @param array|EE_Base_Class $related
1508
+	 * @since $VID:$
1509
+	 */
1510
+	private function updateTimezoneOnRelated($related)
1511
+	{
1512
+		if ($related instanceof EE_Base_Class && $related->get_timezone() !== $this->_timezone) {
1513
+			$related->set_timezone($this->_timezone);
1514
+			return;
1515
+		}
1516
+		if (is_array($related)) {
1517
+			foreach ($related as $object) {
1518
+				$this->updateTimezoneOnRelated($object);
1519
+			}
1520
+		}
1521
+	}
1522
+
1523
+
1524
+	/**
1525
+	 * This returns the current internal set format for the date and time formats.
1526
+	 *
1527
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
1528
+	 *                             where the first value is the date format and the second value is the time format.
1529
+	 * @return mixed string|array
1530
+	 */
1531
+	public function get_format($full = true)
1532
+	{
1533
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1534
+	}
1535
+
1536
+
1537
+	/**
1538
+	 * update_cache_after_object_save
1539
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
1540
+	 * obtained after being saved to the db
1541
+	 *
1542
+	 * @param string        $relationName       - the type of object that is cached
1543
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
1544
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
1545
+	 * @return boolean TRUE on success, FALSE on fail
1546
+	 * @throws InvalidArgumentException
1547
+	 * @throws InvalidDataTypeException
1548
+	 * @throws InvalidInterfaceException
1549
+	 * @throws EE_Error
1550
+	 */
1551
+	public function update_cache_after_object_save(
1552
+		$relationName,
1553
+		EE_Base_Class $newly_saved_object,
1554
+		$current_cache_id = ''
1555
+	) {
1556
+		// verify that incoming object is of the correct type
1557
+		$obj_class = 'EE_' . $relationName;
1558
+		if (! $newly_saved_object instanceof $obj_class) {
1559
+			return false;
1560
+		}
1561 1561
 		$this->updateTimezoneOnRelated($newly_saved_object);
1562
-        /* @type EE_Base_Class $newly_saved_object */
1563
-        // now get the type of relation
1564
-        $relationship_to_model = $this->_model->related_settings_for($relationName);
1565
-        // if this is a 1:1 relationship
1566
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1567
-            // then just replace the cached object with the newly saved object
1568
-            $this->_model_relations[ $relationName ] = $newly_saved_object;
1569
-            return true;
1570
-        }
1571
-        // or if it's some kind of sordid feral polyamorous relationship...
1572
-        if (
1573
-            is_array($this->_model_relations[ $relationName ])
1574
-            && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
1575
-        ) {
1576
-            // then remove the current cached item
1577
-            unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
1578
-            // and cache the newly saved object using it's new ID
1579
-            $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
1580
-            return true;
1581
-        }
1582
-        return false;
1583
-    }
1584
-
1585
-
1586
-    /**
1587
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1588
-     * matching the given query conditions.
1589
-     *
1590
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1591
-     * @param int   $limit              How many objects to return.
1592
-     * @param array $query_params       Any additional conditions on the query.
1593
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1594
-     *                                  you can indicate just the columns you want returned
1595
-     * @return array|EE_Base_Class[]
1596
-     * @throws ReflectionException
1597
-     * @throws InvalidArgumentException
1598
-     * @throws InvalidInterfaceException
1599
-     * @throws InvalidDataTypeException
1600
-     * @throws EE_Error
1601
-     */
1602
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1603
-    {
1604
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1605
-            ? $this->_model->get_primary_key_field()->get_name()
1606
-            : $field_to_order_by;
1607
-        $current_value = ! empty($field) ? $this->get($field) : null;
1608
-        if (empty($field) || empty($current_value)) {
1609
-            return [];
1610
-        }
1611
-        return $this->_model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1612
-    }
1613
-
1614
-
1615
-    /**
1616
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1617
-     * matching the given query conditions.
1618
-     *
1619
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1620
-     * @param int   $limit              How many objects to return.
1621
-     * @param array $query_params       Any additional conditions on the query.
1622
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1623
-     *                                  you can indicate just the columns you want returned
1624
-     * @return array|EE_Base_Class[]
1625
-     * @throws ReflectionException
1626
-     * @throws InvalidArgumentException
1627
-     * @throws InvalidInterfaceException
1628
-     * @throws InvalidDataTypeException
1629
-     * @throws EE_Error
1630
-     */
1631
-    public function previous_x(
1632
-        $field_to_order_by = null,
1633
-        $limit = 1,
1634
-        $query_params = [],
1635
-        $columns_to_select = null
1636
-    ) {
1637
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1638
-            ? $this->_model->get_primary_key_field()->get_name()
1639
-            : $field_to_order_by;
1640
-        $current_value = ! empty($field) ? $this->get($field) : null;
1641
-        if (empty($field) || empty($current_value)) {
1642
-            return [];
1643
-        }
1644
-        return $this->_model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1645
-    }
1646
-
1647
-
1648
-    /**
1649
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1650
-     * matching the given query conditions.
1651
-     *
1652
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1653
-     * @param array $query_params       Any additional conditions on the query.
1654
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1655
-     *                                  you can indicate just the columns you want returned
1656
-     * @return array|EE_Base_Class
1657
-     * @throws ReflectionException
1658
-     * @throws InvalidArgumentException
1659
-     * @throws InvalidInterfaceException
1660
-     * @throws InvalidDataTypeException
1661
-     * @throws EE_Error
1662
-     */
1663
-    public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1664
-    {
1665
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1666
-            ? $this->_model->get_primary_key_field()->get_name()
1667
-            : $field_to_order_by;
1668
-        $current_value = ! empty($field) ? $this->get($field) : null;
1669
-        if (empty($field) || empty($current_value)) {
1670
-            return [];
1671
-        }
1672
-        return $this->_model->next($current_value, $field, $query_params, $columns_to_select);
1673
-    }
1674
-
1675
-
1676
-    /**
1677
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1678
-     * matching the given query conditions.
1679
-     *
1680
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1681
-     * @param array $query_params       Any additional conditions on the query.
1682
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1683
-     *                                  you can indicate just the column you want returned
1684
-     * @return array|EE_Base_Class
1685
-     * @throws ReflectionException
1686
-     * @throws InvalidArgumentException
1687
-     * @throws InvalidInterfaceException
1688
-     * @throws InvalidDataTypeException
1689
-     * @throws EE_Error
1690
-     */
1691
-    public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1692
-    {
1693
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1694
-            ? $this->_model->get_primary_key_field()->get_name()
1695
-            : $field_to_order_by;
1696
-        $current_value = ! empty($field) ? $this->get($field) : null;
1697
-        if (empty($field) || empty($current_value)) {
1698
-            return [];
1699
-        }
1700
-        return $this->_model->previous($current_value, $field, $query_params, $columns_to_select);
1701
-    }
1702
-
1703
-
1704
-    /**
1705
-     * This is used to return the internal DateTime object used for a field that is a
1706
-     * EE_Datetime_Field.
1707
-     *
1708
-     * @param string $field_name               The field name retrieving the DateTime object.
1709
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1710
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1711
-     *                                         EE_Datetime_Field and but the field value is null, then
1712
-     *                                         just null is returned (because that indicates that likely
1713
-     *                                         this field is nullable).
1714
-     * @throws InvalidArgumentException
1715
-     * @throws InvalidDataTypeException
1716
-     * @throws InvalidInterfaceException
1717
-     */
1718
-    public function get_DateTime_object(string $field_name)
1719
-    {
1720
-        $field_settings = $this->_model->field_settings_for($field_name);
1721
-        if (! $field_settings instanceof EE_Datetime_Field) {
1722
-            EE_Error::add_error(
1723
-                sprintf(
1724
-                    esc_html__(
1725
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1726
-                        'event_espresso'
1727
-                    ),
1728
-                    $field_name
1729
-                ),
1730
-                __FILE__,
1731
-                __FUNCTION__,
1732
-                __LINE__
1733
-            );
1734
-            return false;
1735
-        }
1736
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1737
-            ? clone $this->_fields[ $field_name ]
1738
-            : null;
1739
-    }
1740
-
1741
-
1742
-
1743
-
1744
-    /**
1745
-     * NOTE ABOUT BELOW:
1746
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1747
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1748
-     * you want to set both date and time at the same time, you can just use the models default set($field_name,$value)
1749
-     * method and make sure you send the entire datetime value for setting.
1750
-     */
1751
-
1752
-
1753
-    /**
1754
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1755
-     * can be easily used as the value of form input.
1756
-     *
1757
-     * @param string string $field_name
1758
-     * @return void
1759
-     * @throws InvalidArgumentException
1760
-     * @throws InvalidInterfaceException
1761
-     * @throws InvalidDataTypeException
1762
-     * @throws EE_Error
1763
-     */
1764
-    public function f(string $field_name)
1765
-    {
1766
-        $this->e($field_name, 'form_input');
1767
-    }
1768
-
1769
-
1770
-    /**
1771
-     * To be used in template to immediately echo out the value, and format it for output.
1772
-     * Eg, should call stripslashes and whatnot before echoing
1773
-     *
1774
-     * @param string $field_name      the name of the field as it appears in the DB
1775
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1776
-     *                                (in cases where the same property may be used for different outputs
1777
-     *                                - i.e. datetime, money etc.)
1778
-     * @return void
1779
-     * @throws InvalidArgumentException
1780
-     * @throws InvalidInterfaceException
1781
-     * @throws InvalidDataTypeException
1782
-     * @throws EE_Error
1783
-     */
1784
-    public function e(string $field_name, string $extra_cache_ref = '')
1785
-    {
1786
-        echo $this->get_pretty($field_name, $extra_cache_ref);
1787
-    }
1788
-
1789
-
1790
-    /**
1791
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1792
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1793
-     * to see what options are available.
1794
-     *
1795
-     * @param string $field_name
1796
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1797
-     *                                (in cases where the same property may be used for different outputs
1798
-     *                                - i.e. datetime, money etc.)
1799
-     * @return mixed
1800
-     * @throws InvalidArgumentException
1801
-     * @throws InvalidInterfaceException
1802
-     * @throws InvalidDataTypeException
1803
-     * @throws EE_Error
1804
-     */
1805
-    public function get_pretty(string $field_name, string $extra_cache_ref = '')
1806
-    {
1807
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1808
-    }
1809
-
1810
-
1811
-    /**
1812
-     * Same as `f()` but just returns the value instead of echoing it
1813
-     *
1814
-     * @param string $field_name
1815
-     * @return string
1816
-     * @throws InvalidArgumentException
1817
-     * @throws InvalidInterfaceException
1818
-     * @throws InvalidDataTypeException
1819
-     * @throws EE_Error
1820
-     */
1821
-    public function get_f(string $field_name)
1822
-    {
1823
-        return (string) $this->get_pretty($field_name, 'form_input');
1824
-    }
1825
-
1826
-
1827
-    /**
1828
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1829
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1830
-     * other echoes the pretty value for dtt)
1831
-     *
1832
-     * @param string $field_name name of model object datetime field holding the value
1833
-     * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1834
-     * @return string            datetime value formatted
1835
-     * @throws InvalidArgumentException
1836
-     * @throws InvalidInterfaceException
1837
-     * @throws InvalidDataTypeException
1838
-     * @throws EE_Error
1839
-     */
1840
-    public function get_date(string $field_name, $format = '')
1841
-    {
1842
-        return $this->_get_datetime($field_name, $format, null, 'D');
1843
-    }
1844
-
1845
-
1846
-    /**
1847
-     * This simply returns the datetime for the given field name
1848
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1849
-     * (and the equivalent e_date, e_time, e_datetime).
1850
-     *
1851
-     * @param string  $field_name    Field on the instantiated EE_Base_Class child object
1852
-     * @param string  $date_format   valid datetime format used for date
1853
-     *                               (if '' then we just use the default on the field,
1854
-     *                               if NULL we use the last-used format)
1855
-     * @param string  $time_format   Same as above except this is for time format
1856
-     * @param string  $date_or_time  if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1857
-     * @param boolean $echo          Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1858
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1859
-     *                               if field is not a valid dtt field, or void if echoing
1860
-     * @throws InvalidArgumentException
1861
-     * @throws InvalidInterfaceException
1862
-     * @throws InvalidDataTypeException
1863
-     * @throws EE_Error
1864
-     */
1865
-    protected function _get_datetime(
1866
-        string $field_name,
1867
-        $date_format = '',
1868
-        $time_format = '',
1869
-        $date_or_time = '',
1870
-        $echo = false
1871
-    ) {
1872
-        // clear cached property
1873
-        $this->_clear_cached_property($field_name);
1874
-        // reset format properties because they are used in get()
1875
-        $this->_dt_frmt = $date_format !== '' ? $date_format : $this->_dt_frmt;
1876
-        $this->_tm_frmt = $time_format !== '' ? $time_format : $this->_tm_frmt;
1877
-        if ($echo) {
1878
-            $this->e($field_name, $date_or_time);
1879
-            return '';
1880
-        }
1881
-        return $this->get($field_name, $date_or_time);
1882
-    }
1883
-
1884
-
1885
-    /**
1886
-     * @param        $field_name
1887
-     * @param string $format
1888
-     * @throws InvalidArgumentException
1889
-     * @throws InvalidInterfaceException
1890
-     * @throws InvalidDataTypeException
1891
-     * @throws EE_Error
1892
-     */
1893
-    public function e_date(string $field_name, $format = '')
1894
-    {
1895
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1896
-    }
1897
-
1898
-
1899
-    /**
1900
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1901
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1902
-     * other echoes the pretty value for dtt)
1903
-     *
1904
-     * @param string $field_name name of model object datetime field holding the value
1905
-     * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1906
-     * @return string             datetime value formatted
1907
-     * @throws InvalidArgumentException
1908
-     * @throws InvalidInterfaceException
1909
-     * @throws InvalidDataTypeException
1910
-     * @throws EE_Error
1911
-     */
1912
-    public function get_time(string $field_name, $format = '')
1913
-    {
1914
-        return $this->_get_datetime($field_name, null, $format, 'T');
1915
-    }
1916
-
1917
-
1918
-    /**
1919
-     * @param        $field_name
1920
-     * @param string $format
1921
-     * @throws InvalidArgumentException
1922
-     * @throws InvalidInterfaceException
1923
-     * @throws InvalidDataTypeException
1924
-     * @throws EE_Error
1925
-     */
1926
-    public function e_time(string $field_name, $format = '')
1927
-    {
1928
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1929
-    }
1930
-
1931
-
1932
-    /**
1933
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1934
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1935
-     * other echoes the pretty value for dtt)
1936
-     *
1937
-     * @param string $field_name  name of model object datetime field holding the value
1938
-     * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1939
-     * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1940
-     * @return string             datetime value formatted
1941
-     * @throws InvalidArgumentException
1942
-     * @throws InvalidInterfaceException
1943
-     * @throws InvalidDataTypeException
1944
-     * @throws EE_Error
1945
-     */
1946
-    public function get_datetime(string $field_name, $date_format = '', $time_format = '')
1947
-    {
1948
-        return $this->_get_datetime($field_name, $date_format, $time_format);
1949
-    }
1950
-
1951
-
1952
-    /**
1953
-     * @param string $field_name
1954
-     * @param string $date_format
1955
-     * @param string $time_format
1956
-     * @throws InvalidArgumentException
1957
-     * @throws InvalidInterfaceException
1958
-     * @throws InvalidDataTypeException
1959
-     * @throws EE_Error
1960
-     */
1961
-    public function e_datetime(string $field_name, $date_format = '', $time_format = '')
1962
-    {
1963
-        $this->_get_datetime($field_name, $date_format, $time_format, null, true);
1964
-    }
1965
-
1966
-
1967
-    /**
1968
-     * Get the i8ln value for a date using the WordPress
1969
-     *
1970
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1971
-     * @param string $format     PHP valid date/time string format.
1972
-     *                           If none is provided then the internal set format on the object will be used.
1973
-     * @return string Date and time string in set locale or false if no field exists for the given
1974
-     * @throws InvalidArgumentException
1975
-     * @throws InvalidInterfaceException
1976
-     * @throws InvalidDataTypeException
1977
-     * @throws EE_Error
1978
-     *                           field name.
1979
-     * @see date_i18n function.
1980
-     */
1981
-    public function get_i18n_datetime(string $field_name, $format = '')
1982
-    {
1983
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1984
-        return date_i18n(
1985
-            $format,
1986
-            EEH_DTT_Helper::get_timestamp_with_offset(
1987
-                $this->get_raw($field_name),
1988
-                $this->get_timezone()
1989
-            )
1990
-        );
1991
-    }
1992
-
1993
-
1994
-    /**
1995
-     * This method simply returns the RAW unprocessed value for the given property in this class
1996
-     *
1997
-     * @param string $field_name A valid field name
1998
-     * @return mixed              Whatever the raw value stored on the property is.
1999
-     * @throws InvalidArgumentException
2000
-     * @throws InvalidInterfaceException
2001
-     * @throws InvalidDataTypeException
2002
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
2003
-     */
2004
-    public function get_raw(string $field_name)
2005
-    {
2006
-        $field_settings = $this->_model->field_settings_for($field_name);
2007
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
2008
-            ? $this->_fields[ $field_name ]->format('U')
2009
-            : $this->_fields[ $field_name ];
2010
-    }
2011
-
2012
-
2013
-    /**
2014
-     * This will return a timestamp for the website timezone
2015
-     * but ONLY when the current website timezone is different
2016
-     * than the timezone set for the website.
2017
-     * NOTE, this currently only works well with methods that return values.
2018
-     * If you use it with methods that echo values
2019
-     * the $_timestamp property may not get reset to its original value
2020
-     * and that could lead to some unexpected results!
2021
-     *
2022
-     * @param string       $field_name  This is the name of the field on the object that contains the date/time
2023
-     *                                  value being returned.
2024
-     * @param string       $callback    must match a valid method in this class (defaults to get_datetime)
2025
-     * @param array|string $args        This is the arguments that will be passed to the callback.
2026
-     * @param string       $prepend     You can include something to prepend on the timestamp
2027
-     * @param string       $append      You can include something to append on the timestamp
2028
-     * @return string timestamp
2029
-     * @throws InvalidArgumentException
2030
-     * @throws InvalidInterfaceException
2031
-     * @throws InvalidDataTypeException
2032
-     * @throws EE_Error
2033
-     */
2034
-    public function display_in_my_timezone(
2035
-        string $field_name,
2036
-        $callback = 'get_datetime',
2037
-        $args = null,
2038
-        $prepend = '',
2039
-        $append = ''
2040
-    ) {
2041
-        $timezone = EEH_DTT_Helper::get_timezone();
2042
-        if ($timezone === $this->_timezone) {
2043
-            return '';
2044
-        }
2045
-        $original_timezone = $this->_timezone;
2046
-        $this->set_timezone($timezone);
2047
-        $fn   = (array) $field_name;
2048
-        $args = array_merge($fn, (array) $args);
2049
-        if (! method_exists($this, $callback)) {
2050
-            throw new EE_Error(
2051
-                sprintf(
2052
-                    esc_html__(
2053
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
2054
-                        'event_espresso'
2055
-                    ),
2056
-                    $callback
2057
-                )
2058
-            );
2059
-        }
2060
-        $args   = (array) $args;
2061
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
2062
-        $this->set_timezone($original_timezone);
2063
-        return $return;
2064
-    }
2065
-
2066
-
2067
-    /**
2068
-     * Deletes this model object.
2069
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
2070
-     * override
2071
-     * `EE_Base_Class::_delete` NOT this class.
2072
-     *
2073
-     * @return boolean | int
2074
-     * @throws ReflectionException
2075
-     * @throws InvalidArgumentException
2076
-     * @throws InvalidInterfaceException
2077
-     * @throws InvalidDataTypeException
2078
-     * @throws EE_Error
2079
-     */
2080
-    public function delete()
2081
-    {
2082
-        /**
2083
-         * Called just before the `EE_Base_Class::_delete` method call.
2084
-         * Note:
2085
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2086
-         * should be aware that `_delete` may not always result in a permanent delete.
2087
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
2088
-         * soft deletes (trash) the object and does not permanently delete it.
2089
-         *
2090
-         * @param EE_Base_Class $model_object about to be 'deleted'
2091
-         */
2092
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
2093
-        $result = $this->_delete();
2094
-        /**
2095
-         * Called just after the `EE_Base_Class::_delete` method call.
2096
-         * Note:
2097
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2098
-         * should be aware that `_delete` may not always result in a permanent delete.
2099
-         * For example `EE_Soft_Base_Class::_delete`
2100
-         * soft deletes (trash) the object and does not permanently delete it.
2101
-         *
2102
-         * @param EE_Base_Class $model_object that was just 'deleted'
2103
-         * @param boolean       $result
2104
-         */
2105
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
2106
-        return $result;
2107
-    }
2108
-
2109
-
2110
-    /**
2111
-     * Calls the specific delete method for the instantiated class.
2112
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
2113
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
2114
-     * `EE_Base_Class::delete`
2115
-     *
2116
-     * @return bool|int
2117
-     * @throws ReflectionException
2118
-     * @throws InvalidArgumentException
2119
-     * @throws InvalidInterfaceException
2120
-     * @throws InvalidDataTypeException
2121
-     * @throws EE_Error
2122
-     */
2123
-    protected function _delete()
2124
-    {
2125
-        return $this->delete_permanently();
2126
-    }
2127
-
2128
-
2129
-    /**
2130
-     * Deletes this model object permanently from db
2131
-     * (but keep in mind related models may block the delete and return an error)
2132
-     *
2133
-     * @return bool | int
2134
-     * @throws ReflectionException
2135
-     * @throws InvalidArgumentException
2136
-     * @throws InvalidInterfaceException
2137
-     * @throws InvalidDataTypeException
2138
-     * @throws EE_Error
2139
-     */
2140
-    public function delete_permanently()
2141
-    {
2142
-        /**
2143
-         * Called just before HARD deleting a model object
2144
-         *
2145
-         * @param EE_Base_Class $model_object about to be 'deleted'
2146
-         */
2147
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
2148
-        $result = $this->_model->delete_permanently_by_ID($this->ID());
2149
-        $this->refresh_cache_of_related_objects();
2150
-        /**
2151
-         * Called just after HARD deleting a model object
2152
-         *
2153
-         * @param EE_Base_Class $model_object that was just 'deleted'
2154
-         * @param boolean       $result
2155
-         */
2156
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
2157
-        return $result;
2158
-    }
2159
-
2160
-
2161
-    /**
2162
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
2163
-     * related model objects
2164
-     *
2165
-     * @throws ReflectionException
2166
-     * @throws InvalidArgumentException
2167
-     * @throws InvalidInterfaceException
2168
-     * @throws InvalidDataTypeException
2169
-     * @throws EE_Error
2170
-     */
2171
-    public function refresh_cache_of_related_objects()
2172
-    {
2173
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
2174
-            if (! empty($this->_model_relations[ $relation_name ])) {
2175
-                $related_objects = $this->_model_relations[ $relation_name ];
2176
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
2177
-                    // this relation only stores a single model object, not an array
2178
-                    // but let's make it consistent
2179
-                    $related_objects = [$related_objects];
2180
-                }
2181
-                foreach ($related_objects as $related_object) {
2182
-                    // only refresh their cache if they're in memory
2183
-                    if ($related_object instanceof EE_Base_Class) {
2184
-                        $related_object->clear_cache(
2185
-                            $this->_model->get_this_model_name(),
2186
-                            $this
2187
-                        );
2188
-                    }
2189
-                }
2190
-            }
2191
-        }
2192
-    }
2193
-
2194
-
2195
-    /**
2196
-     * Forgets the cached model of the given relation Name. So the next time we request it,
2197
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
2198
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
2199
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
2200
-     *
2201
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
2202
-     *                                                     Eg 'Registration'
2203
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
2204
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
2205
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
2206
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
2207
-     *                                                     this is HasMany or HABTM.
2208
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
2209
-     *                                                     relation from all
2210
-     * @throws InvalidArgumentException
2211
-     * @throws InvalidInterfaceException
2212
-     * @throws InvalidDataTypeException
2213
-     * @throws EE_Error
2214
-     * @throws ReflectionException
2215
-     */
2216
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
2217
-    {
2218
-        $relationship_to_model = $this->_model->related_settings_for($relationName);
2219
-        $index_in_cache        = '';
2220
-        if (! $relationship_to_model) {
2221
-            throw new EE_Error(
2222
-                sprintf(
2223
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
2224
-                    $relationName,
2225
-                    get_class($this)
2226
-                )
2227
-            );
2228
-        }
2229
-        if ($clear_all) {
2230
-            $obj_removed                             = true;
2231
-            $this->_model_relations[ $relationName ] = null;
2232
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
2233
-            $obj_removed                             = $this->_model_relations[ $relationName ];
2234
-            $this->_model_relations[ $relationName ] = null;
2235
-        } else {
2236
-            if (
2237
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
2238
-                && $object_to_remove_or_index_into_array->ID()
2239
-            ) {
2240
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
2241
-                if (
2242
-                    is_array($this->_model_relations[ $relationName ])
2243
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
2244
-                ) {
2245
-                    $index_found_at = null;
2246
-                    // find this object in the array even though it has a different key
2247
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
2248
-                        /** @noinspection TypeUnsafeComparisonInspection */
2249
-                        if (
2250
-                            $obj instanceof EE_Base_Class
2251
-                            && (
2252
-                                $obj == $object_to_remove_or_index_into_array
2253
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
2254
-                            )
2255
-                        ) {
2256
-                            $index_found_at = $index;
2257
-                            break;
2258
-                        }
2259
-                    }
2260
-                    if ($index_found_at) {
2261
-                        $index_in_cache = $index_found_at;
2262
-                    } else {
2263
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
2264
-                        // if it wasn't in it to begin with. So we're done
2265
-                        return $object_to_remove_or_index_into_array;
2266
-                    }
2267
-                }
2268
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
2269
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
2270
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
2271
-                    /** @noinspection TypeUnsafeComparisonInspection */
2272
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
2273
-                        $index_in_cache = $index;
2274
-                    }
2275
-                }
2276
-            } else {
2277
-                $index_in_cache = $object_to_remove_or_index_into_array;
2278
-            }
2279
-            // supposedly we've found it. But it could just be that the client code
2280
-            // provided a bad index/object
2281
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
2282
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
2283
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
2284
-            } else {
2285
-                // that thing was never cached anyways.
2286
-                $obj_removed = null;
2287
-            }
2288
-        }
2289
-        return $obj_removed;
2290
-    }
2291
-
2292
-
2293
-    /**
2294
-     * Saves this model object and its NEW cached relations to the database.
2295
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
2296
-     * In order for that to work, we would need to mark model objects as dirty/clean...
2297
-     * because otherwise, there's a potential for infinite looping of saving
2298
-     * Saves the cached related model objects, and ensures the relation between them
2299
-     * and this object and properly setup
2300
-     *
2301
-     * @return int ID of new model object on save; 0 on failure+
2302
-     * @throws ReflectionException
2303
-     * @throws InvalidArgumentException
2304
-     * @throws InvalidInterfaceException
2305
-     * @throws InvalidDataTypeException
2306
-     * @throws EE_Error
2307
-     */
2308
-    public function save_new_cached_related_model_objs()
2309
-    {
2310
-        // make sure this has been saved
2311
-        if (! $this->ID()) {
2312
-            $id = $this->save();
2313
-        } else {
2314
-            $id = $this->ID();
2315
-        }
2316
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
2317
-        foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
2318
-            if ($this->_model_relations[ $relationName ]) {
2319
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2320
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2321
-                /* @var $related_model_obj EE_Base_Class */
2322
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
2323
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
2324
-                    // but ONLY if it DOES NOT exist in the DB
2325
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2326
-                    // if( ! $related_model_obj->ID()){
2327
-                    $this->_add_relation_to($related_model_obj, $relationName);
2328
-                    $related_model_obj->save_new_cached_related_model_objs();
2329
-                    // }
2330
-                } else {
2331
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2332
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
2333
-                        // but ONLY if it DOES NOT exist in the DB
2334
-                        // if( ! $related_model_obj->ID()){
2335
-                        $this->_add_relation_to($related_model_obj, $relationName);
2336
-                        $related_model_obj->save_new_cached_related_model_objs();
2337
-                        // }
2338
-                    }
2339
-                }
2340
-            }
2341
-        }
2342
-        return $id;
2343
-    }
2344
-
2345
-
2346
-    /**
2347
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2348
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2349
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2350
-     *
2351
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2352
-     * @param string $relationName                     eg 'Events','Question',etc.
2353
-     *                                                 an attendee to a group, you also want to specify which role they
2354
-     *                                                 will have in that group. So you would use this parameter to
2355
-     *                                                 specify array('role-column-name'=>'role-id')
2356
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2357
-     *                                                 allow you to further constrict the relation to being added.
2358
-     *                                                 However, keep in mind that the columns (keys) given must match a
2359
-     *                                                 column on the JOIN table and currently only the HABTM models
2360
-     *                                                 accept these additional conditions.  Also remember that if an
2361
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2362
-     *                                                 NEW row is created in the join table.
2363
-     * @param null   $cache_id
2364
-     * @return EE_Base_Class the object the relation was added to
2365
-     * @throws ReflectionException
2366
-     * @throws InvalidArgumentException
2367
-     * @throws InvalidInterfaceException
2368
-     * @throws InvalidDataTypeException
2369
-     * @throws EE_Error
2370
-     */
2371
-    public function _add_relation_to(
2372
-        $otherObjectModelObjectOrID,
2373
-        $relationName,
2374
-        $extra_join_model_fields_n_values = [],
2375
-        $cache_id = null
2376
-    ) {
2377
-        // if this thing exists in the DB, save the relation to the DB
2378
-        if ($this->ID()) {
2379
-            $otherObject = $this->_model->add_relationship_to(
2380
-                $this,
2381
-                $otherObjectModelObjectOrID,
2382
-                $relationName,
2383
-                $extra_join_model_fields_n_values
2384
-            );
2385
-            // clear cache so future get_many_related and get_first_related() return new results.
2386
-            $this->clear_cache($relationName, $otherObject, true);
2387
-            if ($otherObject instanceof EE_Base_Class) {
2388
-                $otherObject->clear_cache($this->_model->get_this_model_name(), $this);
2389
-            }
2390
-        } else {
2391
-            // this thing doesn't exist in the DB,  so just cache it
2392
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2393
-                throw new EE_Error(
2394
-                    sprintf(
2395
-                        esc_html__(
2396
-                            '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',
2397
-                            'event_espresso'
2398
-                        ),
2399
-                        $otherObjectModelObjectOrID,
2400
-                        get_class($this)
2401
-                    )
2402
-                );
2403
-            }
2404
-            $otherObject = $otherObjectModelObjectOrID;
2405
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2406
-        }
2407
-        if ($otherObject instanceof EE_Base_Class) {
2408
-            // fix the reciprocal relation too
2409
-            if ($otherObject->ID()) {
2410
-                // its saved so assumed relations exist in the DB, so we can just
2411
-                // clear the cache so future queries use the updated info in the DB
2412
-                $otherObject->clear_cache(
2413
-                    $this->_model->get_this_model_name(),
2414
-                    null,
2415
-                    true
2416
-                );
2417
-            } else {
2418
-                // it's not saved, so it caches relations like this
2419
-                $otherObject->cache($this->_model->get_this_model_name(), $this);
2420
-            }
2421
-        }
2422
-        return $otherObject;
2423
-    }
2424
-
2425
-
2426
-    /**
2427
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2428
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2429
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2430
-     * from the cache
2431
-     *
2432
-     * @param mixed  $otherObjectModelObjectOrID
2433
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2434
-     *                to the DB yet
2435
-     * @param string $relationName
2436
-     * @param array  $where_query
2437
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2438
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2439
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2440
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2441
-     *                deleted.
2442
-     * @return EE_Base_Class the relation was removed from
2443
-     * @throws ReflectionException
2444
-     * @throws InvalidArgumentException
2445
-     * @throws InvalidInterfaceException
2446
-     * @throws InvalidDataTypeException
2447
-     * @throws EE_Error
2448
-     */
2449
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
2450
-    {
2451
-        if ($this->ID()) {
2452
-            // if this exists in the DB, save the relation change to the DB too
2453
-            $otherObject = $this->_model->remove_relationship_to(
2454
-                $this,
2455
-                $otherObjectModelObjectOrID,
2456
-                $relationName,
2457
-                $where_query
2458
-            );
2459
-            $this->clear_cache(
2460
-                $relationName,
2461
-                $otherObject
2462
-            );
2463
-        } else {
2464
-            // this doesn't exist in the DB, just remove it from the cache
2465
-            $otherObject = $this->clear_cache(
2466
-                $relationName,
2467
-                $otherObjectModelObjectOrID
2468
-            );
2469
-        }
2470
-        if ($otherObject instanceof EE_Base_Class) {
2471
-            $otherObject->clear_cache(
2472
-                $this->_model->get_this_model_name(),
2473
-                $this
2474
-            );
2475
-        }
2476
-        return $otherObject;
2477
-    }
2478
-
2479
-
2480
-    /**
2481
-     * Removes ALL the related things for the $relationName.
2482
-     *
2483
-     * @param string $relationName
2484
-     * @param array  $where_query_params
2485
-     * @return EE_Base_Class
2486
-     * @throws ReflectionException
2487
-     * @throws InvalidArgumentException
2488
-     * @throws InvalidInterfaceException
2489
-     * @throws InvalidDataTypeException
2490
-     * @throws EE_Error
2491
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2492
-     */
2493
-    public function _remove_relations($relationName, $where_query_params = [])
2494
-    {
2495
-        if ($this->ID()) {
2496
-            // if this exists in the DB, save the relation change to the DB too
2497
-            $otherObjects = $this->_model->remove_relations(
2498
-                $this,
2499
-                $relationName,
2500
-                $where_query_params
2501
-            );
2502
-            $this->clear_cache(
2503
-                $relationName,
2504
-                null,
2505
-                true
2506
-            );
2507
-        } else {
2508
-            // this doesn't exist in the DB, just remove it from the cache
2509
-            $otherObjects = $this->clear_cache(
2510
-                $relationName,
2511
-                null,
2512
-                true
2513
-            );
2514
-        }
2515
-        if (is_array($otherObjects)) {
2516
-            foreach ($otherObjects as $otherObject) {
2517
-                $otherObject->clear_cache(
2518
-                    $this->_model->get_this_model_name(),
2519
-                    $this
2520
-                );
2521
-            }
2522
-        }
2523
-        return $otherObjects;
2524
-    }
2525
-
2526
-
2527
-    /**
2528
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2529
-     * unless otherwise specified in the $query_params
2530
-     *
2531
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2532
-     * @param array  $query_params
2533
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2534
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2535
-     *                               that by the setting $distinct to TRUE;
2536
-     * @return int
2537
-     * @throws ReflectionException
2538
-     * @throws InvalidArgumentException
2539
-     * @throws InvalidInterfaceException
2540
-     * @throws InvalidDataTypeException
2541
-     * @throws EE_Error
2542
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2543
-     */
2544
-    public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2545
-    {
2546
-        return $this->_model->count_related(
2547
-            $this,
2548
-            $relation_name,
2549
-            $query_params,
2550
-            $field_to_count,
2551
-            $distinct
2552
-        );
2553
-    }
2554
-
2555
-
2556
-    /**
2557
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2558
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2559
-     *
2560
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2561
-     * @param array  $query_params
2562
-     * @param string $field_to_sum  name of field to count by.
2563
-     *                              By default, uses primary key
2564
-     *                              (which doesn't make much sense, so you should probably change it)
2565
-     * @return int
2566
-     * @throws ReflectionException
2567
-     * @throws InvalidArgumentException
2568
-     * @throws InvalidInterfaceException
2569
-     * @throws InvalidDataTypeException
2570
-     * @throws EE_Error
2571
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2572
-     */
2573
-    public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2574
-    {
2575
-        return $this->_model->sum_related(
2576
-            $this,
2577
-            $relation_name,
2578
-            $query_params,
2579
-            $field_to_sum
2580
-        );
2581
-    }
2582
-
2583
-
2584
-    /**
2585
-     * Does a delete on all related objects of type $relationName and removes
2586
-     * the current model object's relation to them. If they can't be deleted (because
2587
-     * of blocking related model objects) does nothing. If the related model objects are
2588
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2589
-     * If this model object doesn't exist yet in the DB, just removes its related things
2590
-     *
2591
-     * @param string $relationName
2592
-     * @param array  $query_params
2593
-     * @return int how many deleted
2594
-     * @throws ReflectionException
2595
-     * @throws InvalidArgumentException
2596
-     * @throws InvalidInterfaceException
2597
-     * @throws InvalidDataTypeException
2598
-     * @throws EE_Error
2599
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2600
-     */
2601
-    public function delete_related($relationName, $query_params = [])
2602
-    {
2603
-        if ($this->ID()) {
2604
-            $count = $this->_model->delete_related(
2605
-                $this,
2606
-                $relationName,
2607
-                $query_params
2608
-            );
2609
-        } else {
2610
-            $count = count($this->get_all_from_cache($relationName));
2611
-            $this->clear_cache($relationName, null, true);
2612
-        }
2613
-        return $count;
2614
-    }
2615
-
2616
-
2617
-    /**
2618
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2619
-     * the current model object's relation to them. If they can't be deleted (because
2620
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2621
-     * If the related thing isn't a soft-deletable model object, this function is identical
2622
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2623
-     *
2624
-     * @param string $relationName
2625
-     * @param array  $query_params
2626
-     * @return int how many deleted (including those soft deleted)
2627
-     * @throws ReflectionException
2628
-     * @throws InvalidArgumentException
2629
-     * @throws InvalidInterfaceException
2630
-     * @throws InvalidDataTypeException
2631
-     * @throws EE_Error
2632
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2633
-     */
2634
-    public function delete_related_permanently($relationName, $query_params = [])
2635
-    {
2636
-        if ($this->ID()) {
2637
-            $count = $this->_model->delete_related_permanently(
2638
-                $this,
2639
-                $relationName,
2640
-                $query_params
2641
-            );
2642
-        } else {
2643
-            $count = count($this->get_all_from_cache($relationName));
2644
-        }
2645
-        $this->clear_cache($relationName, null, true);
2646
-        return $count;
2647
-    }
2648
-
2649
-
2650
-    /**
2651
-     * is_set
2652
-     * Just a simple utility function children can use for checking if property exists
2653
-     *
2654
-     * @param string $field_name property to check
2655
-     * @return bool              TRUE if existing, FALSE if not.
2656
-     */
2657
-    public function is_set(string $field_name)
2658
-    {
2659
-        return isset($this->_fields[ $field_name ]);
2660
-    }
2661
-
2662
-
2663
-    /**
2664
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2665
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2666
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2667
-     * Instead of requiring a plugin to extend the EE_Base_Class
2668
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2669
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2670
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2671
-     * and accepts 2 arguments: the object on which the function was called,
2672
-     * and an array of the original arguments passed to the function.
2673
-     * Whatever their callback function returns will be returned by this function.
2674
-     * Example: in functions.php (or in a plugin):
2675
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2676
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2677
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2678
-     *          return $previousReturnValue.$returnString;
2679
-     *      }
2680
-     * require('EE_Answer.class.php');
2681
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2682
-     * echo $answer->my_callback('monkeys',100);
2683
-     * //will output "you called my_callback! and passed args:monkeys,100"
2684
-     *
2685
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2686
-     * @param array  $args       array of original arguments passed to the function
2687
-     * @return mixed whatever the plugin which calls add_filter decides
2688
-     * @throws EE_Error
2689
-     */
2690
-    public function __call($methodName, $args)
2691
-    {
2692
-        $className = get_class($this);
2693
-        $tagName   = "FHEE__{$className}__{$methodName}";
2694
-        if (! has_filter($tagName)) {
2695
-            throw new EE_Error(
2696
-                sprintf(
2697
-                    esc_html__(
2698
-                        "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;}",
2699
-                        'event_espresso'
2700
-                    ),
2701
-                    $methodName,
2702
-                    $className,
2703
-                    $tagName
2704
-                )
2705
-            );
2706
-        }
2707
-        return apply_filters($tagName, null, $this, $args);
2708
-    }
2709
-
2710
-
2711
-    /**
2712
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2713
-     * is specified, only deletes extra meta records with that value.
2714
-     *
2715
-     * @param string $meta_key
2716
-     * @param mixed  $meta_value
2717
-     * @return int number of extra meta rows deleted
2718
-     * @throws InvalidArgumentException
2719
-     * @throws InvalidInterfaceException
2720
-     * @throws InvalidDataTypeException
2721
-     * @throws EE_Error
2722
-     * @throws ReflectionException
2723
-     */
2724
-    public function delete_extra_meta($meta_key, $meta_value = null)
2725
-    {
2726
-        $query_params = [
2727
-            [
2728
-                'EXM_key'  => $meta_key,
2729
-                'OBJ_ID'   => $this->ID(),
2730
-                'EXM_type' => $this->_model->get_this_model_name(),
2731
-            ],
2732
-        ];
2733
-        if ($meta_value !== null) {
2734
-            $query_params[0]['EXM_value'] = $meta_value;
2735
-        }
2736
-        return EEM_Extra_Meta::instance()->delete($query_params);
2737
-    }
2738
-
2739
-
2740
-    /**
2741
-     * Returns a simple array of all the extra meta associated with this model object.
2742
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2743
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2744
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2745
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2746
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2747
-     * finally the extra meta's value as each sub-value. (eg
2748
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2749
-     *
2750
-     * @param boolean $one_of_each_key
2751
-     * @return array
2752
-     * @throws ReflectionException
2753
-     * @throws InvalidArgumentException
2754
-     * @throws InvalidInterfaceException
2755
-     * @throws InvalidDataTypeException
2756
-     * @throws EE_Error
2757
-     */
2758
-    public function all_extra_meta_array($one_of_each_key = true)
2759
-    {
2760
-        $return_array = [];
2761
-        if ($one_of_each_key) {
2762
-            $extra_meta_objs = $this->get_many_related(
2763
-                'Extra_Meta',
2764
-                ['group_by' => 'EXM_key']
2765
-            );
2766
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2767
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2768
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2769
-                }
2770
-            }
2771
-        } else {
2772
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2773
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2774
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2775
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2776
-                        $return_array[ $extra_meta_obj->key() ] = [];
2777
-                    }
2778
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2779
-                }
2780
-            }
2781
-        }
2782
-        return $return_array;
2783
-    }
2784
-
2785
-
2786
-    /**
2787
-     * refresh_from_db
2788
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
2789
-     *
2790
-     * @throws ReflectionException
2791
-     * @throws InvalidArgumentException
2792
-     * @throws InvalidInterfaceException
2793
-     * @throws InvalidDataTypeException
2794
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2795
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2796
-     */
2797
-    public function refresh_from_db()
2798
-    {
2799
-        if ($this->ID() && $this->in_entity_map()) {
2800
-            $this->_model->refresh_entity_map_from_db($this->ID());
2801
-        } else {
2802
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2803
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
2804
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2805
-            // absolutely nothing in it for this ID
2806
-            if (WP_DEBUG) {
2807
-                throw new EE_Error(
2808
-                    sprintf(
2809
-                        esc_html__(
2810
-                            '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.',
2811
-                            'event_espresso'
2812
-                        ),
2813
-                        $this->ID(),
2814
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2815
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2816
-                    )
2817
-                );
2818
-            }
2819
-        }
2820
-    }
2821
-
2822
-
2823
-    /**
2824
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
2825
-     * Does not allow negative values, however.
2826
-     *
2827
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
2828
-     *                                   (positive or negative). One important gotcha: all these values must be
2829
-     *                                   on the same table (eg don't pass in one field for the posts table and
2830
-     *                                   another for the event meta table.)
2831
-     * @return bool
2832
-     * @throws EE_Error
2833
-     * @throws InvalidArgumentException
2834
-     * @throws InvalidDataTypeException
2835
-     * @throws InvalidInterfaceException
2836
-     * @throws ReflectionException
2837
-     * @since 4.9.80.p
2838
-     */
2839
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
2840
-    {
2841
-        global $wpdb;
2842
-        if (empty($fields_n_quantities)) {
2843
-            // No fields to update? Well sure, we updated them to that value just fine.
2844
-            return true;
2845
-        }
2846
-        $fields             = [];
2847
-        $set_sql_statements = [];
2848
-        foreach ($fields_n_quantities as $field_name => $quantity) {
2849
-            $field       = $this->_model->field_settings_for($field_name);
2850
-            $fields[]    = $field;
2851
-            $column_name = $field->get_table_column();
2852
-
2853
-            $abs_qty = absint($quantity);
2854
-            if ($quantity > 0) {
2855
-                // don't let the value be negative as often these fields are unsigned
2856
-                $set_sql_statements[] = $wpdb->prepare(
2857
-                    "`{$column_name}` = `{$column_name}` + %d",
2858
-                    $abs_qty
2859
-                );
2860
-            } else {
2861
-                $set_sql_statements[] = $wpdb->prepare(
2862
-                    "`{$column_name}` = CASE
1562
+		/* @type EE_Base_Class $newly_saved_object */
1563
+		// now get the type of relation
1564
+		$relationship_to_model = $this->_model->related_settings_for($relationName);
1565
+		// if this is a 1:1 relationship
1566
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1567
+			// then just replace the cached object with the newly saved object
1568
+			$this->_model_relations[ $relationName ] = $newly_saved_object;
1569
+			return true;
1570
+		}
1571
+		// or if it's some kind of sordid feral polyamorous relationship...
1572
+		if (
1573
+			is_array($this->_model_relations[ $relationName ])
1574
+			&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
1575
+		) {
1576
+			// then remove the current cached item
1577
+			unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
1578
+			// and cache the newly saved object using it's new ID
1579
+			$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
1580
+			return true;
1581
+		}
1582
+		return false;
1583
+	}
1584
+
1585
+
1586
+	/**
1587
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1588
+	 * matching the given query conditions.
1589
+	 *
1590
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1591
+	 * @param int   $limit              How many objects to return.
1592
+	 * @param array $query_params       Any additional conditions on the query.
1593
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1594
+	 *                                  you can indicate just the columns you want returned
1595
+	 * @return array|EE_Base_Class[]
1596
+	 * @throws ReflectionException
1597
+	 * @throws InvalidArgumentException
1598
+	 * @throws InvalidInterfaceException
1599
+	 * @throws InvalidDataTypeException
1600
+	 * @throws EE_Error
1601
+	 */
1602
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1603
+	{
1604
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1605
+			? $this->_model->get_primary_key_field()->get_name()
1606
+			: $field_to_order_by;
1607
+		$current_value = ! empty($field) ? $this->get($field) : null;
1608
+		if (empty($field) || empty($current_value)) {
1609
+			return [];
1610
+		}
1611
+		return $this->_model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1612
+	}
1613
+
1614
+
1615
+	/**
1616
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1617
+	 * matching the given query conditions.
1618
+	 *
1619
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1620
+	 * @param int   $limit              How many objects to return.
1621
+	 * @param array $query_params       Any additional conditions on the query.
1622
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1623
+	 *                                  you can indicate just the columns you want returned
1624
+	 * @return array|EE_Base_Class[]
1625
+	 * @throws ReflectionException
1626
+	 * @throws InvalidArgumentException
1627
+	 * @throws InvalidInterfaceException
1628
+	 * @throws InvalidDataTypeException
1629
+	 * @throws EE_Error
1630
+	 */
1631
+	public function previous_x(
1632
+		$field_to_order_by = null,
1633
+		$limit = 1,
1634
+		$query_params = [],
1635
+		$columns_to_select = null
1636
+	) {
1637
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1638
+			? $this->_model->get_primary_key_field()->get_name()
1639
+			: $field_to_order_by;
1640
+		$current_value = ! empty($field) ? $this->get($field) : null;
1641
+		if (empty($field) || empty($current_value)) {
1642
+			return [];
1643
+		}
1644
+		return $this->_model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1645
+	}
1646
+
1647
+
1648
+	/**
1649
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1650
+	 * matching the given query conditions.
1651
+	 *
1652
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1653
+	 * @param array $query_params       Any additional conditions on the query.
1654
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1655
+	 *                                  you can indicate just the columns you want returned
1656
+	 * @return array|EE_Base_Class
1657
+	 * @throws ReflectionException
1658
+	 * @throws InvalidArgumentException
1659
+	 * @throws InvalidInterfaceException
1660
+	 * @throws InvalidDataTypeException
1661
+	 * @throws EE_Error
1662
+	 */
1663
+	public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1664
+	{
1665
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1666
+			? $this->_model->get_primary_key_field()->get_name()
1667
+			: $field_to_order_by;
1668
+		$current_value = ! empty($field) ? $this->get($field) : null;
1669
+		if (empty($field) || empty($current_value)) {
1670
+			return [];
1671
+		}
1672
+		return $this->_model->next($current_value, $field, $query_params, $columns_to_select);
1673
+	}
1674
+
1675
+
1676
+	/**
1677
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1678
+	 * matching the given query conditions.
1679
+	 *
1680
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1681
+	 * @param array $query_params       Any additional conditions on the query.
1682
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1683
+	 *                                  you can indicate just the column you want returned
1684
+	 * @return array|EE_Base_Class
1685
+	 * @throws ReflectionException
1686
+	 * @throws InvalidArgumentException
1687
+	 * @throws InvalidInterfaceException
1688
+	 * @throws InvalidDataTypeException
1689
+	 * @throws EE_Error
1690
+	 */
1691
+	public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1692
+	{
1693
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1694
+			? $this->_model->get_primary_key_field()->get_name()
1695
+			: $field_to_order_by;
1696
+		$current_value = ! empty($field) ? $this->get($field) : null;
1697
+		if (empty($field) || empty($current_value)) {
1698
+			return [];
1699
+		}
1700
+		return $this->_model->previous($current_value, $field, $query_params, $columns_to_select);
1701
+	}
1702
+
1703
+
1704
+	/**
1705
+	 * This is used to return the internal DateTime object used for a field that is a
1706
+	 * EE_Datetime_Field.
1707
+	 *
1708
+	 * @param string $field_name               The field name retrieving the DateTime object.
1709
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1710
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1711
+	 *                                         EE_Datetime_Field and but the field value is null, then
1712
+	 *                                         just null is returned (because that indicates that likely
1713
+	 *                                         this field is nullable).
1714
+	 * @throws InvalidArgumentException
1715
+	 * @throws InvalidDataTypeException
1716
+	 * @throws InvalidInterfaceException
1717
+	 */
1718
+	public function get_DateTime_object(string $field_name)
1719
+	{
1720
+		$field_settings = $this->_model->field_settings_for($field_name);
1721
+		if (! $field_settings instanceof EE_Datetime_Field) {
1722
+			EE_Error::add_error(
1723
+				sprintf(
1724
+					esc_html__(
1725
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1726
+						'event_espresso'
1727
+					),
1728
+					$field_name
1729
+				),
1730
+				__FILE__,
1731
+				__FUNCTION__,
1732
+				__LINE__
1733
+			);
1734
+			return false;
1735
+		}
1736
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1737
+			? clone $this->_fields[ $field_name ]
1738
+			: null;
1739
+	}
1740
+
1741
+
1742
+
1743
+
1744
+	/**
1745
+	 * NOTE ABOUT BELOW:
1746
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1747
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1748
+	 * you want to set both date and time at the same time, you can just use the models default set($field_name,$value)
1749
+	 * method and make sure you send the entire datetime value for setting.
1750
+	 */
1751
+
1752
+
1753
+	/**
1754
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1755
+	 * can be easily used as the value of form input.
1756
+	 *
1757
+	 * @param string string $field_name
1758
+	 * @return void
1759
+	 * @throws InvalidArgumentException
1760
+	 * @throws InvalidInterfaceException
1761
+	 * @throws InvalidDataTypeException
1762
+	 * @throws EE_Error
1763
+	 */
1764
+	public function f(string $field_name)
1765
+	{
1766
+		$this->e($field_name, 'form_input');
1767
+	}
1768
+
1769
+
1770
+	/**
1771
+	 * To be used in template to immediately echo out the value, and format it for output.
1772
+	 * Eg, should call stripslashes and whatnot before echoing
1773
+	 *
1774
+	 * @param string $field_name      the name of the field as it appears in the DB
1775
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1776
+	 *                                (in cases where the same property may be used for different outputs
1777
+	 *                                - i.e. datetime, money etc.)
1778
+	 * @return void
1779
+	 * @throws InvalidArgumentException
1780
+	 * @throws InvalidInterfaceException
1781
+	 * @throws InvalidDataTypeException
1782
+	 * @throws EE_Error
1783
+	 */
1784
+	public function e(string $field_name, string $extra_cache_ref = '')
1785
+	{
1786
+		echo $this->get_pretty($field_name, $extra_cache_ref);
1787
+	}
1788
+
1789
+
1790
+	/**
1791
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1792
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1793
+	 * to see what options are available.
1794
+	 *
1795
+	 * @param string $field_name
1796
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1797
+	 *                                (in cases where the same property may be used for different outputs
1798
+	 *                                - i.e. datetime, money etc.)
1799
+	 * @return mixed
1800
+	 * @throws InvalidArgumentException
1801
+	 * @throws InvalidInterfaceException
1802
+	 * @throws InvalidDataTypeException
1803
+	 * @throws EE_Error
1804
+	 */
1805
+	public function get_pretty(string $field_name, string $extra_cache_ref = '')
1806
+	{
1807
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1808
+	}
1809
+
1810
+
1811
+	/**
1812
+	 * Same as `f()` but just returns the value instead of echoing it
1813
+	 *
1814
+	 * @param string $field_name
1815
+	 * @return string
1816
+	 * @throws InvalidArgumentException
1817
+	 * @throws InvalidInterfaceException
1818
+	 * @throws InvalidDataTypeException
1819
+	 * @throws EE_Error
1820
+	 */
1821
+	public function get_f(string $field_name)
1822
+	{
1823
+		return (string) $this->get_pretty($field_name, 'form_input');
1824
+	}
1825
+
1826
+
1827
+	/**
1828
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1829
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1830
+	 * other echoes the pretty value for dtt)
1831
+	 *
1832
+	 * @param string $field_name name of model object datetime field holding the value
1833
+	 * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1834
+	 * @return string            datetime value formatted
1835
+	 * @throws InvalidArgumentException
1836
+	 * @throws InvalidInterfaceException
1837
+	 * @throws InvalidDataTypeException
1838
+	 * @throws EE_Error
1839
+	 */
1840
+	public function get_date(string $field_name, $format = '')
1841
+	{
1842
+		return $this->_get_datetime($field_name, $format, null, 'D');
1843
+	}
1844
+
1845
+
1846
+	/**
1847
+	 * This simply returns the datetime for the given field name
1848
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1849
+	 * (and the equivalent e_date, e_time, e_datetime).
1850
+	 *
1851
+	 * @param string  $field_name    Field on the instantiated EE_Base_Class child object
1852
+	 * @param string  $date_format   valid datetime format used for date
1853
+	 *                               (if '' then we just use the default on the field,
1854
+	 *                               if NULL we use the last-used format)
1855
+	 * @param string  $time_format   Same as above except this is for time format
1856
+	 * @param string  $date_or_time  if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1857
+	 * @param boolean $echo          Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1858
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1859
+	 *                               if field is not a valid dtt field, or void if echoing
1860
+	 * @throws InvalidArgumentException
1861
+	 * @throws InvalidInterfaceException
1862
+	 * @throws InvalidDataTypeException
1863
+	 * @throws EE_Error
1864
+	 */
1865
+	protected function _get_datetime(
1866
+		string $field_name,
1867
+		$date_format = '',
1868
+		$time_format = '',
1869
+		$date_or_time = '',
1870
+		$echo = false
1871
+	) {
1872
+		// clear cached property
1873
+		$this->_clear_cached_property($field_name);
1874
+		// reset format properties because they are used in get()
1875
+		$this->_dt_frmt = $date_format !== '' ? $date_format : $this->_dt_frmt;
1876
+		$this->_tm_frmt = $time_format !== '' ? $time_format : $this->_tm_frmt;
1877
+		if ($echo) {
1878
+			$this->e($field_name, $date_or_time);
1879
+			return '';
1880
+		}
1881
+		return $this->get($field_name, $date_or_time);
1882
+	}
1883
+
1884
+
1885
+	/**
1886
+	 * @param        $field_name
1887
+	 * @param string $format
1888
+	 * @throws InvalidArgumentException
1889
+	 * @throws InvalidInterfaceException
1890
+	 * @throws InvalidDataTypeException
1891
+	 * @throws EE_Error
1892
+	 */
1893
+	public function e_date(string $field_name, $format = '')
1894
+	{
1895
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1896
+	}
1897
+
1898
+
1899
+	/**
1900
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1901
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1902
+	 * other echoes the pretty value for dtt)
1903
+	 *
1904
+	 * @param string $field_name name of model object datetime field holding the value
1905
+	 * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1906
+	 * @return string             datetime value formatted
1907
+	 * @throws InvalidArgumentException
1908
+	 * @throws InvalidInterfaceException
1909
+	 * @throws InvalidDataTypeException
1910
+	 * @throws EE_Error
1911
+	 */
1912
+	public function get_time(string $field_name, $format = '')
1913
+	{
1914
+		return $this->_get_datetime($field_name, null, $format, 'T');
1915
+	}
1916
+
1917
+
1918
+	/**
1919
+	 * @param        $field_name
1920
+	 * @param string $format
1921
+	 * @throws InvalidArgumentException
1922
+	 * @throws InvalidInterfaceException
1923
+	 * @throws InvalidDataTypeException
1924
+	 * @throws EE_Error
1925
+	 */
1926
+	public function e_time(string $field_name, $format = '')
1927
+	{
1928
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1929
+	}
1930
+
1931
+
1932
+	/**
1933
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1934
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1935
+	 * other echoes the pretty value for dtt)
1936
+	 *
1937
+	 * @param string $field_name  name of model object datetime field holding the value
1938
+	 * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1939
+	 * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1940
+	 * @return string             datetime value formatted
1941
+	 * @throws InvalidArgumentException
1942
+	 * @throws InvalidInterfaceException
1943
+	 * @throws InvalidDataTypeException
1944
+	 * @throws EE_Error
1945
+	 */
1946
+	public function get_datetime(string $field_name, $date_format = '', $time_format = '')
1947
+	{
1948
+		return $this->_get_datetime($field_name, $date_format, $time_format);
1949
+	}
1950
+
1951
+
1952
+	/**
1953
+	 * @param string $field_name
1954
+	 * @param string $date_format
1955
+	 * @param string $time_format
1956
+	 * @throws InvalidArgumentException
1957
+	 * @throws InvalidInterfaceException
1958
+	 * @throws InvalidDataTypeException
1959
+	 * @throws EE_Error
1960
+	 */
1961
+	public function e_datetime(string $field_name, $date_format = '', $time_format = '')
1962
+	{
1963
+		$this->_get_datetime($field_name, $date_format, $time_format, null, true);
1964
+	}
1965
+
1966
+
1967
+	/**
1968
+	 * Get the i8ln value for a date using the WordPress
1969
+	 *
1970
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1971
+	 * @param string $format     PHP valid date/time string format.
1972
+	 *                           If none is provided then the internal set format on the object will be used.
1973
+	 * @return string Date and time string in set locale or false if no field exists for the given
1974
+	 * @throws InvalidArgumentException
1975
+	 * @throws InvalidInterfaceException
1976
+	 * @throws InvalidDataTypeException
1977
+	 * @throws EE_Error
1978
+	 *                           field name.
1979
+	 * @see date_i18n function.
1980
+	 */
1981
+	public function get_i18n_datetime(string $field_name, $format = '')
1982
+	{
1983
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1984
+		return date_i18n(
1985
+			$format,
1986
+			EEH_DTT_Helper::get_timestamp_with_offset(
1987
+				$this->get_raw($field_name),
1988
+				$this->get_timezone()
1989
+			)
1990
+		);
1991
+	}
1992
+
1993
+
1994
+	/**
1995
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1996
+	 *
1997
+	 * @param string $field_name A valid field name
1998
+	 * @return mixed              Whatever the raw value stored on the property is.
1999
+	 * @throws InvalidArgumentException
2000
+	 * @throws InvalidInterfaceException
2001
+	 * @throws InvalidDataTypeException
2002
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
2003
+	 */
2004
+	public function get_raw(string $field_name)
2005
+	{
2006
+		$field_settings = $this->_model->field_settings_for($field_name);
2007
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
2008
+			? $this->_fields[ $field_name ]->format('U')
2009
+			: $this->_fields[ $field_name ];
2010
+	}
2011
+
2012
+
2013
+	/**
2014
+	 * This will return a timestamp for the website timezone
2015
+	 * but ONLY when the current website timezone is different
2016
+	 * than the timezone set for the website.
2017
+	 * NOTE, this currently only works well with methods that return values.
2018
+	 * If you use it with methods that echo values
2019
+	 * the $_timestamp property may not get reset to its original value
2020
+	 * and that could lead to some unexpected results!
2021
+	 *
2022
+	 * @param string       $field_name  This is the name of the field on the object that contains the date/time
2023
+	 *                                  value being returned.
2024
+	 * @param string       $callback    must match a valid method in this class (defaults to get_datetime)
2025
+	 * @param array|string $args        This is the arguments that will be passed to the callback.
2026
+	 * @param string       $prepend     You can include something to prepend on the timestamp
2027
+	 * @param string       $append      You can include something to append on the timestamp
2028
+	 * @return string timestamp
2029
+	 * @throws InvalidArgumentException
2030
+	 * @throws InvalidInterfaceException
2031
+	 * @throws InvalidDataTypeException
2032
+	 * @throws EE_Error
2033
+	 */
2034
+	public function display_in_my_timezone(
2035
+		string $field_name,
2036
+		$callback = 'get_datetime',
2037
+		$args = null,
2038
+		$prepend = '',
2039
+		$append = ''
2040
+	) {
2041
+		$timezone = EEH_DTT_Helper::get_timezone();
2042
+		if ($timezone === $this->_timezone) {
2043
+			return '';
2044
+		}
2045
+		$original_timezone = $this->_timezone;
2046
+		$this->set_timezone($timezone);
2047
+		$fn   = (array) $field_name;
2048
+		$args = array_merge($fn, (array) $args);
2049
+		if (! method_exists($this, $callback)) {
2050
+			throw new EE_Error(
2051
+				sprintf(
2052
+					esc_html__(
2053
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
2054
+						'event_espresso'
2055
+					),
2056
+					$callback
2057
+				)
2058
+			);
2059
+		}
2060
+		$args   = (array) $args;
2061
+		$return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
2062
+		$this->set_timezone($original_timezone);
2063
+		return $return;
2064
+	}
2065
+
2066
+
2067
+	/**
2068
+	 * Deletes this model object.
2069
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
2070
+	 * override
2071
+	 * `EE_Base_Class::_delete` NOT this class.
2072
+	 *
2073
+	 * @return boolean | int
2074
+	 * @throws ReflectionException
2075
+	 * @throws InvalidArgumentException
2076
+	 * @throws InvalidInterfaceException
2077
+	 * @throws InvalidDataTypeException
2078
+	 * @throws EE_Error
2079
+	 */
2080
+	public function delete()
2081
+	{
2082
+		/**
2083
+		 * Called just before the `EE_Base_Class::_delete` method call.
2084
+		 * Note:
2085
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2086
+		 * should be aware that `_delete` may not always result in a permanent delete.
2087
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
2088
+		 * soft deletes (trash) the object and does not permanently delete it.
2089
+		 *
2090
+		 * @param EE_Base_Class $model_object about to be 'deleted'
2091
+		 */
2092
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
2093
+		$result = $this->_delete();
2094
+		/**
2095
+		 * Called just after the `EE_Base_Class::_delete` method call.
2096
+		 * Note:
2097
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2098
+		 * should be aware that `_delete` may not always result in a permanent delete.
2099
+		 * For example `EE_Soft_Base_Class::_delete`
2100
+		 * soft deletes (trash) the object and does not permanently delete it.
2101
+		 *
2102
+		 * @param EE_Base_Class $model_object that was just 'deleted'
2103
+		 * @param boolean       $result
2104
+		 */
2105
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
2106
+		return $result;
2107
+	}
2108
+
2109
+
2110
+	/**
2111
+	 * Calls the specific delete method for the instantiated class.
2112
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
2113
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
2114
+	 * `EE_Base_Class::delete`
2115
+	 *
2116
+	 * @return bool|int
2117
+	 * @throws ReflectionException
2118
+	 * @throws InvalidArgumentException
2119
+	 * @throws InvalidInterfaceException
2120
+	 * @throws InvalidDataTypeException
2121
+	 * @throws EE_Error
2122
+	 */
2123
+	protected function _delete()
2124
+	{
2125
+		return $this->delete_permanently();
2126
+	}
2127
+
2128
+
2129
+	/**
2130
+	 * Deletes this model object permanently from db
2131
+	 * (but keep in mind related models may block the delete and return an error)
2132
+	 *
2133
+	 * @return bool | int
2134
+	 * @throws ReflectionException
2135
+	 * @throws InvalidArgumentException
2136
+	 * @throws InvalidInterfaceException
2137
+	 * @throws InvalidDataTypeException
2138
+	 * @throws EE_Error
2139
+	 */
2140
+	public function delete_permanently()
2141
+	{
2142
+		/**
2143
+		 * Called just before HARD deleting a model object
2144
+		 *
2145
+		 * @param EE_Base_Class $model_object about to be 'deleted'
2146
+		 */
2147
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
2148
+		$result = $this->_model->delete_permanently_by_ID($this->ID());
2149
+		$this->refresh_cache_of_related_objects();
2150
+		/**
2151
+		 * Called just after HARD deleting a model object
2152
+		 *
2153
+		 * @param EE_Base_Class $model_object that was just 'deleted'
2154
+		 * @param boolean       $result
2155
+		 */
2156
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
2157
+		return $result;
2158
+	}
2159
+
2160
+
2161
+	/**
2162
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
2163
+	 * related model objects
2164
+	 *
2165
+	 * @throws ReflectionException
2166
+	 * @throws InvalidArgumentException
2167
+	 * @throws InvalidInterfaceException
2168
+	 * @throws InvalidDataTypeException
2169
+	 * @throws EE_Error
2170
+	 */
2171
+	public function refresh_cache_of_related_objects()
2172
+	{
2173
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
2174
+			if (! empty($this->_model_relations[ $relation_name ])) {
2175
+				$related_objects = $this->_model_relations[ $relation_name ];
2176
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
2177
+					// this relation only stores a single model object, not an array
2178
+					// but let's make it consistent
2179
+					$related_objects = [$related_objects];
2180
+				}
2181
+				foreach ($related_objects as $related_object) {
2182
+					// only refresh their cache if they're in memory
2183
+					if ($related_object instanceof EE_Base_Class) {
2184
+						$related_object->clear_cache(
2185
+							$this->_model->get_this_model_name(),
2186
+							$this
2187
+						);
2188
+					}
2189
+				}
2190
+			}
2191
+		}
2192
+	}
2193
+
2194
+
2195
+	/**
2196
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
2197
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
2198
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
2199
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
2200
+	 *
2201
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
2202
+	 *                                                     Eg 'Registration'
2203
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
2204
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
2205
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
2206
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
2207
+	 *                                                     this is HasMany or HABTM.
2208
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
2209
+	 *                                                     relation from all
2210
+	 * @throws InvalidArgumentException
2211
+	 * @throws InvalidInterfaceException
2212
+	 * @throws InvalidDataTypeException
2213
+	 * @throws EE_Error
2214
+	 * @throws ReflectionException
2215
+	 */
2216
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
2217
+	{
2218
+		$relationship_to_model = $this->_model->related_settings_for($relationName);
2219
+		$index_in_cache        = '';
2220
+		if (! $relationship_to_model) {
2221
+			throw new EE_Error(
2222
+				sprintf(
2223
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
2224
+					$relationName,
2225
+					get_class($this)
2226
+				)
2227
+			);
2228
+		}
2229
+		if ($clear_all) {
2230
+			$obj_removed                             = true;
2231
+			$this->_model_relations[ $relationName ] = null;
2232
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
2233
+			$obj_removed                             = $this->_model_relations[ $relationName ];
2234
+			$this->_model_relations[ $relationName ] = null;
2235
+		} else {
2236
+			if (
2237
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
2238
+				&& $object_to_remove_or_index_into_array->ID()
2239
+			) {
2240
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
2241
+				if (
2242
+					is_array($this->_model_relations[ $relationName ])
2243
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
2244
+				) {
2245
+					$index_found_at = null;
2246
+					// find this object in the array even though it has a different key
2247
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
2248
+						/** @noinspection TypeUnsafeComparisonInspection */
2249
+						if (
2250
+							$obj instanceof EE_Base_Class
2251
+							&& (
2252
+								$obj == $object_to_remove_or_index_into_array
2253
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
2254
+							)
2255
+						) {
2256
+							$index_found_at = $index;
2257
+							break;
2258
+						}
2259
+					}
2260
+					if ($index_found_at) {
2261
+						$index_in_cache = $index_found_at;
2262
+					} else {
2263
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
2264
+						// if it wasn't in it to begin with. So we're done
2265
+						return $object_to_remove_or_index_into_array;
2266
+					}
2267
+				}
2268
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
2269
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
2270
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
2271
+					/** @noinspection TypeUnsafeComparisonInspection */
2272
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
2273
+						$index_in_cache = $index;
2274
+					}
2275
+				}
2276
+			} else {
2277
+				$index_in_cache = $object_to_remove_or_index_into_array;
2278
+			}
2279
+			// supposedly we've found it. But it could just be that the client code
2280
+			// provided a bad index/object
2281
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
2282
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
2283
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
2284
+			} else {
2285
+				// that thing was never cached anyways.
2286
+				$obj_removed = null;
2287
+			}
2288
+		}
2289
+		return $obj_removed;
2290
+	}
2291
+
2292
+
2293
+	/**
2294
+	 * Saves this model object and its NEW cached relations to the database.
2295
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
2296
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
2297
+	 * because otherwise, there's a potential for infinite looping of saving
2298
+	 * Saves the cached related model objects, and ensures the relation between them
2299
+	 * and this object and properly setup
2300
+	 *
2301
+	 * @return int ID of new model object on save; 0 on failure+
2302
+	 * @throws ReflectionException
2303
+	 * @throws InvalidArgumentException
2304
+	 * @throws InvalidInterfaceException
2305
+	 * @throws InvalidDataTypeException
2306
+	 * @throws EE_Error
2307
+	 */
2308
+	public function save_new_cached_related_model_objs()
2309
+	{
2310
+		// make sure this has been saved
2311
+		if (! $this->ID()) {
2312
+			$id = $this->save();
2313
+		} else {
2314
+			$id = $this->ID();
2315
+		}
2316
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
2317
+		foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
2318
+			if ($this->_model_relations[ $relationName ]) {
2319
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2320
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2321
+				/* @var $related_model_obj EE_Base_Class */
2322
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
2323
+					// add a relation to that relation type (which saves the appropriate thing in the process)
2324
+					// but ONLY if it DOES NOT exist in the DB
2325
+					$related_model_obj = $this->_model_relations[ $relationName ];
2326
+					// if( ! $related_model_obj->ID()){
2327
+					$this->_add_relation_to($related_model_obj, $relationName);
2328
+					$related_model_obj->save_new_cached_related_model_objs();
2329
+					// }
2330
+				} else {
2331
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2332
+						// add a relation to that relation type (which saves the appropriate thing in the process)
2333
+						// but ONLY if it DOES NOT exist in the DB
2334
+						// if( ! $related_model_obj->ID()){
2335
+						$this->_add_relation_to($related_model_obj, $relationName);
2336
+						$related_model_obj->save_new_cached_related_model_objs();
2337
+						// }
2338
+					}
2339
+				}
2340
+			}
2341
+		}
2342
+		return $id;
2343
+	}
2344
+
2345
+
2346
+	/**
2347
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2348
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2349
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2350
+	 *
2351
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2352
+	 * @param string $relationName                     eg 'Events','Question',etc.
2353
+	 *                                                 an attendee to a group, you also want to specify which role they
2354
+	 *                                                 will have in that group. So you would use this parameter to
2355
+	 *                                                 specify array('role-column-name'=>'role-id')
2356
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2357
+	 *                                                 allow you to further constrict the relation to being added.
2358
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2359
+	 *                                                 column on the JOIN table and currently only the HABTM models
2360
+	 *                                                 accept these additional conditions.  Also remember that if an
2361
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2362
+	 *                                                 NEW row is created in the join table.
2363
+	 * @param null   $cache_id
2364
+	 * @return EE_Base_Class the object the relation was added to
2365
+	 * @throws ReflectionException
2366
+	 * @throws InvalidArgumentException
2367
+	 * @throws InvalidInterfaceException
2368
+	 * @throws InvalidDataTypeException
2369
+	 * @throws EE_Error
2370
+	 */
2371
+	public function _add_relation_to(
2372
+		$otherObjectModelObjectOrID,
2373
+		$relationName,
2374
+		$extra_join_model_fields_n_values = [],
2375
+		$cache_id = null
2376
+	) {
2377
+		// if this thing exists in the DB, save the relation to the DB
2378
+		if ($this->ID()) {
2379
+			$otherObject = $this->_model->add_relationship_to(
2380
+				$this,
2381
+				$otherObjectModelObjectOrID,
2382
+				$relationName,
2383
+				$extra_join_model_fields_n_values
2384
+			);
2385
+			// clear cache so future get_many_related and get_first_related() return new results.
2386
+			$this->clear_cache($relationName, $otherObject, true);
2387
+			if ($otherObject instanceof EE_Base_Class) {
2388
+				$otherObject->clear_cache($this->_model->get_this_model_name(), $this);
2389
+			}
2390
+		} else {
2391
+			// this thing doesn't exist in the DB,  so just cache it
2392
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2393
+				throw new EE_Error(
2394
+					sprintf(
2395
+						esc_html__(
2396
+							'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',
2397
+							'event_espresso'
2398
+						),
2399
+						$otherObjectModelObjectOrID,
2400
+						get_class($this)
2401
+					)
2402
+				);
2403
+			}
2404
+			$otherObject = $otherObjectModelObjectOrID;
2405
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2406
+		}
2407
+		if ($otherObject instanceof EE_Base_Class) {
2408
+			// fix the reciprocal relation too
2409
+			if ($otherObject->ID()) {
2410
+				// its saved so assumed relations exist in the DB, so we can just
2411
+				// clear the cache so future queries use the updated info in the DB
2412
+				$otherObject->clear_cache(
2413
+					$this->_model->get_this_model_name(),
2414
+					null,
2415
+					true
2416
+				);
2417
+			} else {
2418
+				// it's not saved, so it caches relations like this
2419
+				$otherObject->cache($this->_model->get_this_model_name(), $this);
2420
+			}
2421
+		}
2422
+		return $otherObject;
2423
+	}
2424
+
2425
+
2426
+	/**
2427
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2428
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2429
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2430
+	 * from the cache
2431
+	 *
2432
+	 * @param mixed  $otherObjectModelObjectOrID
2433
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2434
+	 *                to the DB yet
2435
+	 * @param string $relationName
2436
+	 * @param array  $where_query
2437
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2438
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2439
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2440
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2441
+	 *                deleted.
2442
+	 * @return EE_Base_Class the relation was removed from
2443
+	 * @throws ReflectionException
2444
+	 * @throws InvalidArgumentException
2445
+	 * @throws InvalidInterfaceException
2446
+	 * @throws InvalidDataTypeException
2447
+	 * @throws EE_Error
2448
+	 */
2449
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
2450
+	{
2451
+		if ($this->ID()) {
2452
+			// if this exists in the DB, save the relation change to the DB too
2453
+			$otherObject = $this->_model->remove_relationship_to(
2454
+				$this,
2455
+				$otherObjectModelObjectOrID,
2456
+				$relationName,
2457
+				$where_query
2458
+			);
2459
+			$this->clear_cache(
2460
+				$relationName,
2461
+				$otherObject
2462
+			);
2463
+		} else {
2464
+			// this doesn't exist in the DB, just remove it from the cache
2465
+			$otherObject = $this->clear_cache(
2466
+				$relationName,
2467
+				$otherObjectModelObjectOrID
2468
+			);
2469
+		}
2470
+		if ($otherObject instanceof EE_Base_Class) {
2471
+			$otherObject->clear_cache(
2472
+				$this->_model->get_this_model_name(),
2473
+				$this
2474
+			);
2475
+		}
2476
+		return $otherObject;
2477
+	}
2478
+
2479
+
2480
+	/**
2481
+	 * Removes ALL the related things for the $relationName.
2482
+	 *
2483
+	 * @param string $relationName
2484
+	 * @param array  $where_query_params
2485
+	 * @return EE_Base_Class
2486
+	 * @throws ReflectionException
2487
+	 * @throws InvalidArgumentException
2488
+	 * @throws InvalidInterfaceException
2489
+	 * @throws InvalidDataTypeException
2490
+	 * @throws EE_Error
2491
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2492
+	 */
2493
+	public function _remove_relations($relationName, $where_query_params = [])
2494
+	{
2495
+		if ($this->ID()) {
2496
+			// if this exists in the DB, save the relation change to the DB too
2497
+			$otherObjects = $this->_model->remove_relations(
2498
+				$this,
2499
+				$relationName,
2500
+				$where_query_params
2501
+			);
2502
+			$this->clear_cache(
2503
+				$relationName,
2504
+				null,
2505
+				true
2506
+			);
2507
+		} else {
2508
+			// this doesn't exist in the DB, just remove it from the cache
2509
+			$otherObjects = $this->clear_cache(
2510
+				$relationName,
2511
+				null,
2512
+				true
2513
+			);
2514
+		}
2515
+		if (is_array($otherObjects)) {
2516
+			foreach ($otherObjects as $otherObject) {
2517
+				$otherObject->clear_cache(
2518
+					$this->_model->get_this_model_name(),
2519
+					$this
2520
+				);
2521
+			}
2522
+		}
2523
+		return $otherObjects;
2524
+	}
2525
+
2526
+
2527
+	/**
2528
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2529
+	 * unless otherwise specified in the $query_params
2530
+	 *
2531
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2532
+	 * @param array  $query_params
2533
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2534
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2535
+	 *                               that by the setting $distinct to TRUE;
2536
+	 * @return int
2537
+	 * @throws ReflectionException
2538
+	 * @throws InvalidArgumentException
2539
+	 * @throws InvalidInterfaceException
2540
+	 * @throws InvalidDataTypeException
2541
+	 * @throws EE_Error
2542
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2543
+	 */
2544
+	public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2545
+	{
2546
+		return $this->_model->count_related(
2547
+			$this,
2548
+			$relation_name,
2549
+			$query_params,
2550
+			$field_to_count,
2551
+			$distinct
2552
+		);
2553
+	}
2554
+
2555
+
2556
+	/**
2557
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2558
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2559
+	 *
2560
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2561
+	 * @param array  $query_params
2562
+	 * @param string $field_to_sum  name of field to count by.
2563
+	 *                              By default, uses primary key
2564
+	 *                              (which doesn't make much sense, so you should probably change it)
2565
+	 * @return int
2566
+	 * @throws ReflectionException
2567
+	 * @throws InvalidArgumentException
2568
+	 * @throws InvalidInterfaceException
2569
+	 * @throws InvalidDataTypeException
2570
+	 * @throws EE_Error
2571
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2572
+	 */
2573
+	public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2574
+	{
2575
+		return $this->_model->sum_related(
2576
+			$this,
2577
+			$relation_name,
2578
+			$query_params,
2579
+			$field_to_sum
2580
+		);
2581
+	}
2582
+
2583
+
2584
+	/**
2585
+	 * Does a delete on all related objects of type $relationName and removes
2586
+	 * the current model object's relation to them. If they can't be deleted (because
2587
+	 * of blocking related model objects) does nothing. If the related model objects are
2588
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2589
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2590
+	 *
2591
+	 * @param string $relationName
2592
+	 * @param array  $query_params
2593
+	 * @return int how many deleted
2594
+	 * @throws ReflectionException
2595
+	 * @throws InvalidArgumentException
2596
+	 * @throws InvalidInterfaceException
2597
+	 * @throws InvalidDataTypeException
2598
+	 * @throws EE_Error
2599
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2600
+	 */
2601
+	public function delete_related($relationName, $query_params = [])
2602
+	{
2603
+		if ($this->ID()) {
2604
+			$count = $this->_model->delete_related(
2605
+				$this,
2606
+				$relationName,
2607
+				$query_params
2608
+			);
2609
+		} else {
2610
+			$count = count($this->get_all_from_cache($relationName));
2611
+			$this->clear_cache($relationName, null, true);
2612
+		}
2613
+		return $count;
2614
+	}
2615
+
2616
+
2617
+	/**
2618
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2619
+	 * the current model object's relation to them. If they can't be deleted (because
2620
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2621
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2622
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2623
+	 *
2624
+	 * @param string $relationName
2625
+	 * @param array  $query_params
2626
+	 * @return int how many deleted (including those soft deleted)
2627
+	 * @throws ReflectionException
2628
+	 * @throws InvalidArgumentException
2629
+	 * @throws InvalidInterfaceException
2630
+	 * @throws InvalidDataTypeException
2631
+	 * @throws EE_Error
2632
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2633
+	 */
2634
+	public function delete_related_permanently($relationName, $query_params = [])
2635
+	{
2636
+		if ($this->ID()) {
2637
+			$count = $this->_model->delete_related_permanently(
2638
+				$this,
2639
+				$relationName,
2640
+				$query_params
2641
+			);
2642
+		} else {
2643
+			$count = count($this->get_all_from_cache($relationName));
2644
+		}
2645
+		$this->clear_cache($relationName, null, true);
2646
+		return $count;
2647
+	}
2648
+
2649
+
2650
+	/**
2651
+	 * is_set
2652
+	 * Just a simple utility function children can use for checking if property exists
2653
+	 *
2654
+	 * @param string $field_name property to check
2655
+	 * @return bool              TRUE if existing, FALSE if not.
2656
+	 */
2657
+	public function is_set(string $field_name)
2658
+	{
2659
+		return isset($this->_fields[ $field_name ]);
2660
+	}
2661
+
2662
+
2663
+	/**
2664
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2665
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2666
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2667
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2668
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2669
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2670
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2671
+	 * and accepts 2 arguments: the object on which the function was called,
2672
+	 * and an array of the original arguments passed to the function.
2673
+	 * Whatever their callback function returns will be returned by this function.
2674
+	 * Example: in functions.php (or in a plugin):
2675
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2676
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2677
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2678
+	 *          return $previousReturnValue.$returnString;
2679
+	 *      }
2680
+	 * require('EE_Answer.class.php');
2681
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2682
+	 * echo $answer->my_callback('monkeys',100);
2683
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2684
+	 *
2685
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2686
+	 * @param array  $args       array of original arguments passed to the function
2687
+	 * @return mixed whatever the plugin which calls add_filter decides
2688
+	 * @throws EE_Error
2689
+	 */
2690
+	public function __call($methodName, $args)
2691
+	{
2692
+		$className = get_class($this);
2693
+		$tagName   = "FHEE__{$className}__{$methodName}";
2694
+		if (! has_filter($tagName)) {
2695
+			throw new EE_Error(
2696
+				sprintf(
2697
+					esc_html__(
2698
+						"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;}",
2699
+						'event_espresso'
2700
+					),
2701
+					$methodName,
2702
+					$className,
2703
+					$tagName
2704
+				)
2705
+			);
2706
+		}
2707
+		return apply_filters($tagName, null, $this, $args);
2708
+	}
2709
+
2710
+
2711
+	/**
2712
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2713
+	 * is specified, only deletes extra meta records with that value.
2714
+	 *
2715
+	 * @param string $meta_key
2716
+	 * @param mixed  $meta_value
2717
+	 * @return int number of extra meta rows deleted
2718
+	 * @throws InvalidArgumentException
2719
+	 * @throws InvalidInterfaceException
2720
+	 * @throws InvalidDataTypeException
2721
+	 * @throws EE_Error
2722
+	 * @throws ReflectionException
2723
+	 */
2724
+	public function delete_extra_meta($meta_key, $meta_value = null)
2725
+	{
2726
+		$query_params = [
2727
+			[
2728
+				'EXM_key'  => $meta_key,
2729
+				'OBJ_ID'   => $this->ID(),
2730
+				'EXM_type' => $this->_model->get_this_model_name(),
2731
+			],
2732
+		];
2733
+		if ($meta_value !== null) {
2734
+			$query_params[0]['EXM_value'] = $meta_value;
2735
+		}
2736
+		return EEM_Extra_Meta::instance()->delete($query_params);
2737
+	}
2738
+
2739
+
2740
+	/**
2741
+	 * Returns a simple array of all the extra meta associated with this model object.
2742
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2743
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2744
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2745
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2746
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2747
+	 * finally the extra meta's value as each sub-value. (eg
2748
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2749
+	 *
2750
+	 * @param boolean $one_of_each_key
2751
+	 * @return array
2752
+	 * @throws ReflectionException
2753
+	 * @throws InvalidArgumentException
2754
+	 * @throws InvalidInterfaceException
2755
+	 * @throws InvalidDataTypeException
2756
+	 * @throws EE_Error
2757
+	 */
2758
+	public function all_extra_meta_array($one_of_each_key = true)
2759
+	{
2760
+		$return_array = [];
2761
+		if ($one_of_each_key) {
2762
+			$extra_meta_objs = $this->get_many_related(
2763
+				'Extra_Meta',
2764
+				['group_by' => 'EXM_key']
2765
+			);
2766
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2767
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2768
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2769
+				}
2770
+			}
2771
+		} else {
2772
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2773
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2774
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2775
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2776
+						$return_array[ $extra_meta_obj->key() ] = [];
2777
+					}
2778
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2779
+				}
2780
+			}
2781
+		}
2782
+		return $return_array;
2783
+	}
2784
+
2785
+
2786
+	/**
2787
+	 * refresh_from_db
2788
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
2789
+	 *
2790
+	 * @throws ReflectionException
2791
+	 * @throws InvalidArgumentException
2792
+	 * @throws InvalidInterfaceException
2793
+	 * @throws InvalidDataTypeException
2794
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2795
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2796
+	 */
2797
+	public function refresh_from_db()
2798
+	{
2799
+		if ($this->ID() && $this->in_entity_map()) {
2800
+			$this->_model->refresh_entity_map_from_db($this->ID());
2801
+		} else {
2802
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2803
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
2804
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2805
+			// absolutely nothing in it for this ID
2806
+			if (WP_DEBUG) {
2807
+				throw new EE_Error(
2808
+					sprintf(
2809
+						esc_html__(
2810
+							'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.',
2811
+							'event_espresso'
2812
+						),
2813
+						$this->ID(),
2814
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2815
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2816
+					)
2817
+				);
2818
+			}
2819
+		}
2820
+	}
2821
+
2822
+
2823
+	/**
2824
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
2825
+	 * Does not allow negative values, however.
2826
+	 *
2827
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
2828
+	 *                                   (positive or negative). One important gotcha: all these values must be
2829
+	 *                                   on the same table (eg don't pass in one field for the posts table and
2830
+	 *                                   another for the event meta table.)
2831
+	 * @return bool
2832
+	 * @throws EE_Error
2833
+	 * @throws InvalidArgumentException
2834
+	 * @throws InvalidDataTypeException
2835
+	 * @throws InvalidInterfaceException
2836
+	 * @throws ReflectionException
2837
+	 * @since 4.9.80.p
2838
+	 */
2839
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
2840
+	{
2841
+		global $wpdb;
2842
+		if (empty($fields_n_quantities)) {
2843
+			// No fields to update? Well sure, we updated them to that value just fine.
2844
+			return true;
2845
+		}
2846
+		$fields             = [];
2847
+		$set_sql_statements = [];
2848
+		foreach ($fields_n_quantities as $field_name => $quantity) {
2849
+			$field       = $this->_model->field_settings_for($field_name);
2850
+			$fields[]    = $field;
2851
+			$column_name = $field->get_table_column();
2852
+
2853
+			$abs_qty = absint($quantity);
2854
+			if ($quantity > 0) {
2855
+				// don't let the value be negative as often these fields are unsigned
2856
+				$set_sql_statements[] = $wpdb->prepare(
2857
+					"`{$column_name}` = `{$column_name}` + %d",
2858
+					$abs_qty
2859
+				);
2860
+			} else {
2861
+				$set_sql_statements[] = $wpdb->prepare(
2862
+					"`{$column_name}` = CASE
2863 2863
                        WHEN (`{$column_name}` >= %d)
2864 2864
                        THEN `{$column_name}` - %d
2865 2865
                        ELSE 0
2866 2866
                     END",
2867
-                    $abs_qty,
2868
-                    $abs_qty
2869
-                );
2870
-            }
2871
-        }
2872
-        return $this->updateFieldsInDB(
2873
-            $fields,
2874
-            implode(', ', $set_sql_statements)
2875
-        );
2876
-    }
2877
-
2878
-
2879
-    /**
2880
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
2881
-     *
2882
-     * @param EE_Model_Field_Base[] $fields
2883
-     * @param string                $new_value_sql
2884
-     *          example: 'column_name=123',
2885
-     *          or 'column_name=column_name+1',
2886
-     *          or 'column_name= CASE
2887
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
2888
-     *          THEN `column_name` + 5
2889
-     *          ELSE `column_name`
2890
-     *          END'
2891
-     *          Also updates $field on this model object with the latest value from the database.
2892
-     * @return bool
2893
-     * @throws EE_Error
2894
-     * @throws InvalidArgumentException
2895
-     * @throws InvalidDataTypeException
2896
-     * @throws InvalidInterfaceException
2897
-     * @throws ReflectionException
2898
-     * @since 4.9.80.p
2899
-     */
2900
-    protected function updateFieldsInDB($fields, $new_value_sql)
2901
-    {
2902
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
2903
-        // if it wasn't even there to start off.
2904
-        if (! $this->ID()) {
2905
-            $this->save();
2906
-        }
2907
-        global $wpdb;
2908
-        if (empty($fields)) {
2909
-            throw new InvalidArgumentException(
2910
-                esc_html__(
2911
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
2912
-                    'event_espresso'
2913
-                )
2914
-            );
2915
-        }
2916
-        $first_field = reset($fields);
2917
-        $table_alias = $first_field->get_table_alias();
2918
-        foreach ($fields as $field) {
2919
-            if ($table_alias !== $field->get_table_alias()) {
2920
-                throw new InvalidArgumentException(
2921
-                    sprintf(
2922
-                        esc_html__(
2923
-                        // @codingStandardsIgnoreStart
2924
-                            'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
2925
-                            // @codingStandardsIgnoreEnd
2926
-                            'event_espresso'
2927
-                        ),
2928
-                        $table_alias,
2929
-                        $field->get_table_alias()
2930
-                    )
2931
-                );
2932
-            }
2933
-        }
2934
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
2935
-        $table_obj      = $this->_model->get_table_obj_by_alias($table_alias);
2936
-        $table_pk_value = $this->ID();
2937
-        $table_name     = $table_obj->get_table_name();
2938
-        if ($table_obj instanceof EE_Secondary_Table) {
2939
-            $table_pk_field_name = $table_obj->get_fk_on_table();
2940
-        } else {
2941
-            $table_pk_field_name = $table_obj->get_pk_column();
2942
-        }
2943
-
2944
-        $query  =
2945
-            "UPDATE `{$table_name}`
2867
+					$abs_qty,
2868
+					$abs_qty
2869
+				);
2870
+			}
2871
+		}
2872
+		return $this->updateFieldsInDB(
2873
+			$fields,
2874
+			implode(', ', $set_sql_statements)
2875
+		);
2876
+	}
2877
+
2878
+
2879
+	/**
2880
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
2881
+	 *
2882
+	 * @param EE_Model_Field_Base[] $fields
2883
+	 * @param string                $new_value_sql
2884
+	 *          example: 'column_name=123',
2885
+	 *          or 'column_name=column_name+1',
2886
+	 *          or 'column_name= CASE
2887
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
2888
+	 *          THEN `column_name` + 5
2889
+	 *          ELSE `column_name`
2890
+	 *          END'
2891
+	 *          Also updates $field on this model object with the latest value from the database.
2892
+	 * @return bool
2893
+	 * @throws EE_Error
2894
+	 * @throws InvalidArgumentException
2895
+	 * @throws InvalidDataTypeException
2896
+	 * @throws InvalidInterfaceException
2897
+	 * @throws ReflectionException
2898
+	 * @since 4.9.80.p
2899
+	 */
2900
+	protected function updateFieldsInDB($fields, $new_value_sql)
2901
+	{
2902
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
2903
+		// if it wasn't even there to start off.
2904
+		if (! $this->ID()) {
2905
+			$this->save();
2906
+		}
2907
+		global $wpdb;
2908
+		if (empty($fields)) {
2909
+			throw new InvalidArgumentException(
2910
+				esc_html__(
2911
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
2912
+					'event_espresso'
2913
+				)
2914
+			);
2915
+		}
2916
+		$first_field = reset($fields);
2917
+		$table_alias = $first_field->get_table_alias();
2918
+		foreach ($fields as $field) {
2919
+			if ($table_alias !== $field->get_table_alias()) {
2920
+				throw new InvalidArgumentException(
2921
+					sprintf(
2922
+						esc_html__(
2923
+						// @codingStandardsIgnoreStart
2924
+							'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
2925
+							// @codingStandardsIgnoreEnd
2926
+							'event_espresso'
2927
+						),
2928
+						$table_alias,
2929
+						$field->get_table_alias()
2930
+					)
2931
+				);
2932
+			}
2933
+		}
2934
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
2935
+		$table_obj      = $this->_model->get_table_obj_by_alias($table_alias);
2936
+		$table_pk_value = $this->ID();
2937
+		$table_name     = $table_obj->get_table_name();
2938
+		if ($table_obj instanceof EE_Secondary_Table) {
2939
+			$table_pk_field_name = $table_obj->get_fk_on_table();
2940
+		} else {
2941
+			$table_pk_field_name = $table_obj->get_pk_column();
2942
+		}
2943
+
2944
+		$query  =
2945
+			"UPDATE `{$table_name}`
2946 2946
             SET "
2947
-            . $new_value_sql
2948
-            . $wpdb->prepare(
2949
-                "
2947
+			. $new_value_sql
2948
+			. $wpdb->prepare(
2949
+				"
2950 2950
             WHERE `{$table_pk_field_name}` = %d;",
2951
-                $table_pk_value
2952
-            );
2953
-        $result = $wpdb->query($query);
2954
-        foreach ($fields as $field) {
2955
-            // If it was successful, we'd like to know the new value.
2956
-            // If it failed, we'd also like to know the new value.
2957
-            $new_value = $this->_model->get_var(
2958
-                $this->_model->alter_query_params_to_restrict_by_ID(
2959
-                    $this->_model->get_index_primary_key_string(
2960
-                        $this->model_field_array()
2961
-                    ),
2962
-                    [
2963
-                        'default_where_conditions' => 'minimum',
2964
-                    ]
2965
-                ),
2966
-                $field->get_name()
2967
-            );
2968
-            $this->set_from_db(
2969
-                $field->get_name(),
2970
-                $new_value
2971
-            );
2972
-        }
2973
-        return (bool) $result;
2974
-    }
2975
-
2976
-
2977
-    /**
2978
-     * This simply returns an array of model fields for this object
2979
-     *
2980
-     * @return array
2981
-     * @throws InvalidArgumentException
2982
-     * @throws InvalidInterfaceException
2983
-     * @throws InvalidDataTypeException
2984
-     * @throws EE_Error
2985
-     */
2986
-    public function model_field_array()
2987
-    {
2988
-        $fields     = $this->_model->field_settings();
2989
-        $properties = [];
2990
-        // remove prepended underscore
2991
-        foreach ($fields as $field_name => $settings) {
2992
-            $properties[ $field_name ] = $this->get($field_name);
2993
-        }
2994
-        return $properties;
2995
-    }
2996
-
2997
-
2998
-    /**
2999
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3000
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3001
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3002
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3003
-     * Otherwise returns false.
3004
-     *
3005
-     * @param string $field_name_to_bump
3006
-     * @param string $field_name_affecting_total
3007
-     * @param string $limit_field_name
3008
-     * @param int    $quantity
3009
-     * @return bool
3010
-     * @throws EE_Error
3011
-     * @throws InvalidArgumentException
3012
-     * @throws InvalidDataTypeException
3013
-     * @throws InvalidInterfaceException
3014
-     * @throws ReflectionException
3015
-     * @since 4.9.80.p
3016
-     */
3017
-    public function incrementFieldConditionallyInDb(
3018
-        $field_name_to_bump,
3019
-        $field_name_affecting_total,
3020
-        $limit_field_name,
3021
-        $quantity
3022
-    ) {
3023
-        global $wpdb;
3024
-        $field       = $this->_model->field_settings_for($field_name_to_bump);
3025
-        $column_name = $field->get_table_column();
3026
-
3027
-        $field_affecting_total  = $this->_model->field_settings_for($field_name_affecting_total);
3028
-        $column_affecting_total = $field_affecting_total->get_table_column();
3029
-
3030
-        $limiting_field  = $this->_model->field_settings_for($limit_field_name);
3031
-        $limiting_column = $limiting_field->get_table_column();
3032
-        return $this->updateFieldsInDB(
3033
-            [$field],
3034
-            $wpdb->prepare(
3035
-                "`{$column_name}` =
2951
+				$table_pk_value
2952
+			);
2953
+		$result = $wpdb->query($query);
2954
+		foreach ($fields as $field) {
2955
+			// If it was successful, we'd like to know the new value.
2956
+			// If it failed, we'd also like to know the new value.
2957
+			$new_value = $this->_model->get_var(
2958
+				$this->_model->alter_query_params_to_restrict_by_ID(
2959
+					$this->_model->get_index_primary_key_string(
2960
+						$this->model_field_array()
2961
+					),
2962
+					[
2963
+						'default_where_conditions' => 'minimum',
2964
+					]
2965
+				),
2966
+				$field->get_name()
2967
+			);
2968
+			$this->set_from_db(
2969
+				$field->get_name(),
2970
+				$new_value
2971
+			);
2972
+		}
2973
+		return (bool) $result;
2974
+	}
2975
+
2976
+
2977
+	/**
2978
+	 * This simply returns an array of model fields for this object
2979
+	 *
2980
+	 * @return array
2981
+	 * @throws InvalidArgumentException
2982
+	 * @throws InvalidInterfaceException
2983
+	 * @throws InvalidDataTypeException
2984
+	 * @throws EE_Error
2985
+	 */
2986
+	public function model_field_array()
2987
+	{
2988
+		$fields     = $this->_model->field_settings();
2989
+		$properties = [];
2990
+		// remove prepended underscore
2991
+		foreach ($fields as $field_name => $settings) {
2992
+			$properties[ $field_name ] = $this->get($field_name);
2993
+		}
2994
+		return $properties;
2995
+	}
2996
+
2997
+
2998
+	/**
2999
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3000
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3001
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3002
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3003
+	 * Otherwise returns false.
3004
+	 *
3005
+	 * @param string $field_name_to_bump
3006
+	 * @param string $field_name_affecting_total
3007
+	 * @param string $limit_field_name
3008
+	 * @param int    $quantity
3009
+	 * @return bool
3010
+	 * @throws EE_Error
3011
+	 * @throws InvalidArgumentException
3012
+	 * @throws InvalidDataTypeException
3013
+	 * @throws InvalidInterfaceException
3014
+	 * @throws ReflectionException
3015
+	 * @since 4.9.80.p
3016
+	 */
3017
+	public function incrementFieldConditionallyInDb(
3018
+		$field_name_to_bump,
3019
+		$field_name_affecting_total,
3020
+		$limit_field_name,
3021
+		$quantity
3022
+	) {
3023
+		global $wpdb;
3024
+		$field       = $this->_model->field_settings_for($field_name_to_bump);
3025
+		$column_name = $field->get_table_column();
3026
+
3027
+		$field_affecting_total  = $this->_model->field_settings_for($field_name_affecting_total);
3028
+		$column_affecting_total = $field_affecting_total->get_table_column();
3029
+
3030
+		$limiting_field  = $this->_model->field_settings_for($limit_field_name);
3031
+		$limiting_column = $limiting_field->get_table_column();
3032
+		return $this->updateFieldsInDB(
3033
+			[$field],
3034
+			$wpdb->prepare(
3035
+				"`{$column_name}` =
3036 3036
             CASE
3037 3037
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3038 3038
                THEN `{$column_name}` + %d
3039 3039
                ELSE `{$column_name}`
3040 3040
             END",
3041
-                $quantity,
3042
-                EE_INF_IN_DB,
3043
-                $quantity
3044
-            )
3045
-        );
3046
-    }
3047
-
3048
-
3049
-    /**
3050
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3051
-     * (probably a bad assumption they have made, oh well)
3052
-     *
3053
-     * @return string
3054
-     */
3055
-    public function __toString()
3056
-    {
3057
-        try {
3058
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3059
-        } catch (Exception $e) {
3060
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3061
-            return '';
3062
-        }
3063
-    }
3064
-
3065
-
3066
-    /**
3067
-     * Gets a pretty nice displayable nice for this model object. Often overridden
3068
-     *
3069
-     * @return string
3070
-     * @throws InvalidArgumentException
3071
-     * @throws InvalidInterfaceException
3072
-     * @throws InvalidDataTypeException
3073
-     * @throws EE_Error
3074
-     */
3075
-    public function name()
3076
-    {
3077
-        // find a field that's not a text field
3078
-        $field_we_can_use = $this->_model->get_a_field_of_type('EE_Text_Field_Base');
3079
-        if ($field_we_can_use) {
3080
-            return $this->get($field_we_can_use->get_name());
3081
-        }
3082
-        $first_few_properties = $this->model_field_array();
3083
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
3084
-        $name_parts           = [];
3085
-        foreach ($first_few_properties as $name => $value) {
3086
-            $name_parts[] = "$name:$value";
3087
-        }
3088
-        return implode(',', $name_parts);
3089
-    }
3090
-
3091
-
3092
-    /**
3093
-     * Clear related model objects if they're already in the DB, because otherwise when we
3094
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3095
-     * This means if we have made changes to those related model objects, and want to unserialize
3096
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3097
-     * Instead, those related model objects should be directly serialized and stored.
3098
-     * Eg, the following won't work:
3099
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3100
-     * $att = $reg->attendee();
3101
-     * $att->set( 'ATT_fname', 'Dirk' );
3102
-     * update_option( 'my_option', serialize( $reg ) );
3103
-     * //END REQUEST
3104
-     * //START NEXT REQUEST
3105
-     * $reg = get_option( 'my_option' );
3106
-     * $reg->attendee()->save();
3107
-     * And would need to be replace with:
3108
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3109
-     * $att = $reg->attendee();
3110
-     * $att->set( 'ATT_fname', 'Dirk' );
3111
-     * update_option( 'my_option', serialize( $reg ) );
3112
-     * //END REQUEST
3113
-     * //START NEXT REQUEST
3114
-     * $att = get_option( 'my_option' );
3115
-     * $att->save();
3116
-     *
3117
-     * @return array
3118
-     * @throws ReflectionException
3119
-     * @throws InvalidArgumentException
3120
-     * @throws InvalidInterfaceException
3121
-     * @throws InvalidDataTypeException
3122
-     * @throws EE_Error
3123
-     */
3124
-    public function __sleep()
3125
-    {
3126
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3127
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3128
-                $classname = 'EE_' . $this->_model->get_this_model_name();
3129
-                if (
3130
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3131
-                    && $this->get_one_from_cache($relation_name)->ID()
3132
-                ) {
3133
-                    $this->clear_cache(
3134
-                        $relation_name,
3135
-                        $this->get_one_from_cache($relation_name)->ID()
3136
-                    );
3137
-                }
3138
-            }
3139
-        }
3140
-        $this->_props_n_values_provided_in_constructor = [];
3141
-        $properties_to_serialize                       = get_object_vars($this);
3142
-        // don't serialize the model. It's big and that risks recursion
3143
-        unset($properties_to_serialize['_model']);
3144
-        return array_keys($properties_to_serialize);
3145
-    }
3146
-
3147
-
3148
-    /**
3149
-     * restore _props_n_values_provided_in_constructor
3150
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3151
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3152
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3153
-     *
3154
-     * @throws EE_Error
3155
-     * @throws ReflectionException
3156
-     */
3157
-    public function __wakeup()
3158
-    {
3159
-        $this->_model = $this->get_model();
3160
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3161
-    }
3162
-
3163
-
3164
-    /**
3165
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3166
-     * distinct with the clone host instance are also cloned.
3167
-     */
3168
-    public function __clone()
3169
-    {
3170
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3171
-        foreach ($this->_fields as $field => $value) {
3172
-            if ($value instanceof DateTime) {
3173
-                $this->_fields[ $field ] = clone $value;
3174
-            }
3175
-        }
3176
-    }
3177
-
3178
-
3179
-    /**
3180
-     * Ensures that this related thing is a model object.
3181
-     *
3182
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
3183
-     * @param string $model_name   name of the related thing, eg 'Attendee',
3184
-     * @return EE_Base_Class
3185
-     * @throws ReflectionException
3186
-     * @throws InvalidArgumentException
3187
-     * @throws InvalidInterfaceException
3188
-     * @throws InvalidDataTypeException
3189
-     * @throws EE_Error
3190
-     */
3191
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
3192
-    {
3193
-        $other_model_instance = self::_get_model_instance_with_name(
3194
-            self::_get_model_classname($model_name),
3195
-            $this->_timezone
3196
-        );
3197
-        return $other_model_instance->ensure_is_obj($object_or_id);
3198
-    }
3199
-
3200
-
3201
-    /**
3202
-     * sets the time on a datetime property
3203
-     *
3204
-     * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
3205
-     * @param string          $field_name the name of the field the time is being set on (must match a
3206
-     *                                    EE_Datetime_Field)
3207
-     * @throws InvalidArgumentException
3208
-     * @throws InvalidInterfaceException
3209
-     * @throws InvalidDataTypeException
3210
-     * @throws EE_Error
3211
-     */
3212
-    protected function _set_time_for($time, string $field_name)
3213
-    {
3214
-        $this->_set_date_time('T', $time, $field_name);
3215
-    }
3216
-
3217
-
3218
-    /**
3219
-     * This takes care of setting a date or time independently on a given model object property. This method also
3220
-     * verifies that the given field name matches a model object property and is for a EE_Datetime_Field field
3221
-     *
3222
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
3223
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
3224
-     * @param string          $field_name     the name of the field the date OR time is being set on (must match a
3225
-     *                                        EE_Datetime_Field property)
3226
-     * @throws InvalidArgumentException
3227
-     * @throws InvalidInterfaceException
3228
-     * @throws InvalidDataTypeException
3229
-     * @throws EE_Error
3230
-     */
3231
-    protected function _set_date_time($what, $datetime_value, string $field_name)
3232
-    {
3233
-        $field = $this->_get_dtt_field_settings($field_name);
3234
-        $field->set_timezone($this->_timezone);
3235
-        $field->set_date_format($this->_dt_frmt);
3236
-        $field->set_time_format($this->_tm_frmt);
3237
-        $what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
3238
-        switch ($what) {
3239
-            case 'T':
3240
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
3241
-                    $datetime_value,
3242
-                    $this->_fields[ $field_name ]
3243
-                );
3244
-                $this->_has_changes           = true;
3245
-                break;
3246
-            case 'D':
3247
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
3248
-                    $datetime_value,
3249
-                    $this->_fields[ $field_name ]
3250
-                );
3251
-                $this->_has_changes           = true;
3252
-                break;
3253
-            case 'B':
3254
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
3255
-                $this->_has_changes           = true;
3256
-                break;
3257
-        }
3258
-        $this->_clear_cached_property($field_name);
3259
-    }
3260
-
3261
-
3262
-    /**
3263
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
3264
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
3265
-     * thrown.
3266
-     *
3267
-     * @param string string $field_name The field name being checked
3268
-     * @return EE_Datetime_Field
3269
-     * @throws InvalidArgumentException
3270
-     * @throws InvalidInterfaceException
3271
-     * @throws InvalidDataTypeException
3272
-     * @throws EE_Error
3273
-     */
3274
-    protected function _get_dtt_field_settings($field_name)
3275
-    {
3276
-        $field = $this->_model->field_settings_for($field_name);
3277
-        // check if field is dtt
3278
-        if ($field instanceof EE_Datetime_Field) {
3279
-            return $field;
3280
-        }
3281
-        throw new EE_Error(
3282
-            sprintf(
3283
-                esc_html__(
3284
-                    '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',
3285
-                    'event_espresso'
3286
-                ),
3287
-                $field_name,
3288
-                self::_get_model_classname(get_class($this))
3289
-            )
3290
-        );
3291
-    }
3292
-
3293
-
3294
-    /**
3295
-     * sets the date on a datetime property
3296
-     *
3297
-     * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
3298
-     * @param string          $field_name the name of the field the date is being set on (must match a
3299
-     *                                    EE_Datetime_Field)
3300
-     * @throws InvalidArgumentException
3301
-     * @throws InvalidInterfaceException
3302
-     * @throws InvalidDataTypeException
3303
-     * @throws EE_Error
3304
-     */
3305
-    protected function _set_date_for($date, string $field_name)
3306
-    {
3307
-        $this->_set_date_time('D', $date, $field_name);
3308
-    }
3309
-
3310
-
3311
-    /**
3312
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
3313
-     * EE_Error exception if they don't
3314
-     *
3315
-     * @param mixed (string|array) $properties properties to check
3316
-     * @return bool                              TRUE if existing, throw EE_Error if not.
3317
-     * @throws EE_Error
3318
-     */
3319
-    protected function _property_exists($properties)
3320
-    {
3321
-        foreach ((array) $properties as $property_name) {
3322
-            // first make sure this property exists
3323
-            if (! $this->_fields[ $property_name ]) {
3324
-                throw new EE_Error(
3325
-                    sprintf(
3326
-                        esc_html__(
3327
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
3328
-                            'event_espresso'
3329
-                        ),
3330
-                        $property_name
3331
-                    )
3332
-                );
3333
-            }
3334
-        }
3335
-        return true;
3336
-    }
3337
-
3338
-
3339
-    /**
3340
-     * @param array $date_formats
3341
-     * @since $VID:$
3342
-     */
3343
-    private function setDateAndTimeFormats(array $date_formats)
3344
-    {
3345
-        if (! empty($date_formats) && is_array($date_formats)) {
3346
-            [$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
3347
-        } else {
3348
-            // set default formats for date and time
3349
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
3350
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
3351
-        }
3352
-    }
3353
-
3354
-
3355
-    /**
3356
-     * @param array $model_fields
3357
-     * @param array $fieldValues
3358
-     * @throws EE_Error
3359
-     * @since $VID:$
3360
-     */
3361
-    private function validateFieldValues(array $model_fields, array $fieldValues)
3362
-    {
3363
-        // verify client code has not passed any invalid field names
3364
-        foreach ($fieldValues as $field_name => $field_value) {
3365
-            if (! isset($model_fields[ $field_name ])) {
3366
-                throw new EE_Error(
3367
-                    sprintf(
3368
-                        esc_html__(
3369
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
3370
-                            'event_espresso'
3371
-                        ),
3372
-                        $field_name,
3373
-                        get_class($this),
3374
-                        implode(', ', array_keys($model_fields))
3375
-                    )
3376
-                );
3377
-            }
3378
-        }
3379
-    }
3380
-
3381
-
3382
-    /**
3383
-     * @param array $model_fields
3384
-     * @param array $fieldValues
3385
-     * @param bool  $set_from_db
3386
-     * @throws EE_Error
3387
-     * @throws ReflectionException
3388
-     * @since $VID:$
3389
-     */
3390
-    private function setFieldValues(array $model_fields, array $fieldValues, bool $set_from_db)
3391
-    {
3392
-        foreach ($model_fields as $fieldName => $field) {
3393
-            // if db model is instantiating
3394
-            if ($set_from_db) {
3395
-                // client code has indicated these field values are from the database
3396
-                $this->set_from_db(
3397
-                    $fieldName,
3398
-                    $fieldValues[ $fieldName ] ?? null
3399
-                );
3400
-            } else {
3401
-                // we're constructing a brand new instance of the model object.
3402
-                // Generally, this means we'll need to do more field validation
3403
-                $this->set(
3404
-                    $fieldName,
3405
-                    $fieldValues[ $fieldName ] ?? null,
3406
-                    true
3407
-                );
3408
-            }
3409
-        }
3410
-    }
3041
+				$quantity,
3042
+				EE_INF_IN_DB,
3043
+				$quantity
3044
+			)
3045
+		);
3046
+	}
3047
+
3048
+
3049
+	/**
3050
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3051
+	 * (probably a bad assumption they have made, oh well)
3052
+	 *
3053
+	 * @return string
3054
+	 */
3055
+	public function __toString()
3056
+	{
3057
+		try {
3058
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3059
+		} catch (Exception $e) {
3060
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3061
+			return '';
3062
+		}
3063
+	}
3064
+
3065
+
3066
+	/**
3067
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
3068
+	 *
3069
+	 * @return string
3070
+	 * @throws InvalidArgumentException
3071
+	 * @throws InvalidInterfaceException
3072
+	 * @throws InvalidDataTypeException
3073
+	 * @throws EE_Error
3074
+	 */
3075
+	public function name()
3076
+	{
3077
+		// find a field that's not a text field
3078
+		$field_we_can_use = $this->_model->get_a_field_of_type('EE_Text_Field_Base');
3079
+		if ($field_we_can_use) {
3080
+			return $this->get($field_we_can_use->get_name());
3081
+		}
3082
+		$first_few_properties = $this->model_field_array();
3083
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
3084
+		$name_parts           = [];
3085
+		foreach ($first_few_properties as $name => $value) {
3086
+			$name_parts[] = "$name:$value";
3087
+		}
3088
+		return implode(',', $name_parts);
3089
+	}
3090
+
3091
+
3092
+	/**
3093
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3094
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3095
+	 * This means if we have made changes to those related model objects, and want to unserialize
3096
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3097
+	 * Instead, those related model objects should be directly serialized and stored.
3098
+	 * Eg, the following won't work:
3099
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3100
+	 * $att = $reg->attendee();
3101
+	 * $att->set( 'ATT_fname', 'Dirk' );
3102
+	 * update_option( 'my_option', serialize( $reg ) );
3103
+	 * //END REQUEST
3104
+	 * //START NEXT REQUEST
3105
+	 * $reg = get_option( 'my_option' );
3106
+	 * $reg->attendee()->save();
3107
+	 * And would need to be replace with:
3108
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3109
+	 * $att = $reg->attendee();
3110
+	 * $att->set( 'ATT_fname', 'Dirk' );
3111
+	 * update_option( 'my_option', serialize( $reg ) );
3112
+	 * //END REQUEST
3113
+	 * //START NEXT REQUEST
3114
+	 * $att = get_option( 'my_option' );
3115
+	 * $att->save();
3116
+	 *
3117
+	 * @return array
3118
+	 * @throws ReflectionException
3119
+	 * @throws InvalidArgumentException
3120
+	 * @throws InvalidInterfaceException
3121
+	 * @throws InvalidDataTypeException
3122
+	 * @throws EE_Error
3123
+	 */
3124
+	public function __sleep()
3125
+	{
3126
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3127
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3128
+				$classname = 'EE_' . $this->_model->get_this_model_name();
3129
+				if (
3130
+					$this->get_one_from_cache($relation_name) instanceof $classname
3131
+					&& $this->get_one_from_cache($relation_name)->ID()
3132
+				) {
3133
+					$this->clear_cache(
3134
+						$relation_name,
3135
+						$this->get_one_from_cache($relation_name)->ID()
3136
+					);
3137
+				}
3138
+			}
3139
+		}
3140
+		$this->_props_n_values_provided_in_constructor = [];
3141
+		$properties_to_serialize                       = get_object_vars($this);
3142
+		// don't serialize the model. It's big and that risks recursion
3143
+		unset($properties_to_serialize['_model']);
3144
+		return array_keys($properties_to_serialize);
3145
+	}
3146
+
3147
+
3148
+	/**
3149
+	 * restore _props_n_values_provided_in_constructor
3150
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3151
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3152
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3153
+	 *
3154
+	 * @throws EE_Error
3155
+	 * @throws ReflectionException
3156
+	 */
3157
+	public function __wakeup()
3158
+	{
3159
+		$this->_model = $this->get_model();
3160
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3161
+	}
3162
+
3163
+
3164
+	/**
3165
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3166
+	 * distinct with the clone host instance are also cloned.
3167
+	 */
3168
+	public function __clone()
3169
+	{
3170
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3171
+		foreach ($this->_fields as $field => $value) {
3172
+			if ($value instanceof DateTime) {
3173
+				$this->_fields[ $field ] = clone $value;
3174
+			}
3175
+		}
3176
+	}
3177
+
3178
+
3179
+	/**
3180
+	 * Ensures that this related thing is a model object.
3181
+	 *
3182
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
3183
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
3184
+	 * @return EE_Base_Class
3185
+	 * @throws ReflectionException
3186
+	 * @throws InvalidArgumentException
3187
+	 * @throws InvalidInterfaceException
3188
+	 * @throws InvalidDataTypeException
3189
+	 * @throws EE_Error
3190
+	 */
3191
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
3192
+	{
3193
+		$other_model_instance = self::_get_model_instance_with_name(
3194
+			self::_get_model_classname($model_name),
3195
+			$this->_timezone
3196
+		);
3197
+		return $other_model_instance->ensure_is_obj($object_or_id);
3198
+	}
3199
+
3200
+
3201
+	/**
3202
+	 * sets the time on a datetime property
3203
+	 *
3204
+	 * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
3205
+	 * @param string          $field_name the name of the field the time is being set on (must match a
3206
+	 *                                    EE_Datetime_Field)
3207
+	 * @throws InvalidArgumentException
3208
+	 * @throws InvalidInterfaceException
3209
+	 * @throws InvalidDataTypeException
3210
+	 * @throws EE_Error
3211
+	 */
3212
+	protected function _set_time_for($time, string $field_name)
3213
+	{
3214
+		$this->_set_date_time('T', $time, $field_name);
3215
+	}
3216
+
3217
+
3218
+	/**
3219
+	 * This takes care of setting a date or time independently on a given model object property. This method also
3220
+	 * verifies that the given field name matches a model object property and is for a EE_Datetime_Field field
3221
+	 *
3222
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
3223
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
3224
+	 * @param string          $field_name     the name of the field the date OR time is being set on (must match a
3225
+	 *                                        EE_Datetime_Field property)
3226
+	 * @throws InvalidArgumentException
3227
+	 * @throws InvalidInterfaceException
3228
+	 * @throws InvalidDataTypeException
3229
+	 * @throws EE_Error
3230
+	 */
3231
+	protected function _set_date_time($what, $datetime_value, string $field_name)
3232
+	{
3233
+		$field = $this->_get_dtt_field_settings($field_name);
3234
+		$field->set_timezone($this->_timezone);
3235
+		$field->set_date_format($this->_dt_frmt);
3236
+		$field->set_time_format($this->_tm_frmt);
3237
+		$what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
3238
+		switch ($what) {
3239
+			case 'T':
3240
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
3241
+					$datetime_value,
3242
+					$this->_fields[ $field_name ]
3243
+				);
3244
+				$this->_has_changes           = true;
3245
+				break;
3246
+			case 'D':
3247
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
3248
+					$datetime_value,
3249
+					$this->_fields[ $field_name ]
3250
+				);
3251
+				$this->_has_changes           = true;
3252
+				break;
3253
+			case 'B':
3254
+				$this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
3255
+				$this->_has_changes           = true;
3256
+				break;
3257
+		}
3258
+		$this->_clear_cached_property($field_name);
3259
+	}
3260
+
3261
+
3262
+	/**
3263
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
3264
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
3265
+	 * thrown.
3266
+	 *
3267
+	 * @param string string $field_name The field name being checked
3268
+	 * @return EE_Datetime_Field
3269
+	 * @throws InvalidArgumentException
3270
+	 * @throws InvalidInterfaceException
3271
+	 * @throws InvalidDataTypeException
3272
+	 * @throws EE_Error
3273
+	 */
3274
+	protected function _get_dtt_field_settings($field_name)
3275
+	{
3276
+		$field = $this->_model->field_settings_for($field_name);
3277
+		// check if field is dtt
3278
+		if ($field instanceof EE_Datetime_Field) {
3279
+			return $field;
3280
+		}
3281
+		throw new EE_Error(
3282
+			sprintf(
3283
+				esc_html__(
3284
+					'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',
3285
+					'event_espresso'
3286
+				),
3287
+				$field_name,
3288
+				self::_get_model_classname(get_class($this))
3289
+			)
3290
+		);
3291
+	}
3292
+
3293
+
3294
+	/**
3295
+	 * sets the date on a datetime property
3296
+	 *
3297
+	 * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
3298
+	 * @param string          $field_name the name of the field the date is being set on (must match a
3299
+	 *                                    EE_Datetime_Field)
3300
+	 * @throws InvalidArgumentException
3301
+	 * @throws InvalidInterfaceException
3302
+	 * @throws InvalidDataTypeException
3303
+	 * @throws EE_Error
3304
+	 */
3305
+	protected function _set_date_for($date, string $field_name)
3306
+	{
3307
+		$this->_set_date_time('D', $date, $field_name);
3308
+	}
3309
+
3310
+
3311
+	/**
3312
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
3313
+	 * EE_Error exception if they don't
3314
+	 *
3315
+	 * @param mixed (string|array) $properties properties to check
3316
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
3317
+	 * @throws EE_Error
3318
+	 */
3319
+	protected function _property_exists($properties)
3320
+	{
3321
+		foreach ((array) $properties as $property_name) {
3322
+			// first make sure this property exists
3323
+			if (! $this->_fields[ $property_name ]) {
3324
+				throw new EE_Error(
3325
+					sprintf(
3326
+						esc_html__(
3327
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
3328
+							'event_espresso'
3329
+						),
3330
+						$property_name
3331
+					)
3332
+				);
3333
+			}
3334
+		}
3335
+		return true;
3336
+	}
3337
+
3338
+
3339
+	/**
3340
+	 * @param array $date_formats
3341
+	 * @since $VID:$
3342
+	 */
3343
+	private function setDateAndTimeFormats(array $date_formats)
3344
+	{
3345
+		if (! empty($date_formats) && is_array($date_formats)) {
3346
+			[$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
3347
+		} else {
3348
+			// set default formats for date and time
3349
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
3350
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
3351
+		}
3352
+	}
3353
+
3354
+
3355
+	/**
3356
+	 * @param array $model_fields
3357
+	 * @param array $fieldValues
3358
+	 * @throws EE_Error
3359
+	 * @since $VID:$
3360
+	 */
3361
+	private function validateFieldValues(array $model_fields, array $fieldValues)
3362
+	{
3363
+		// verify client code has not passed any invalid field names
3364
+		foreach ($fieldValues as $field_name => $field_value) {
3365
+			if (! isset($model_fields[ $field_name ])) {
3366
+				throw new EE_Error(
3367
+					sprintf(
3368
+						esc_html__(
3369
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
3370
+							'event_espresso'
3371
+						),
3372
+						$field_name,
3373
+						get_class($this),
3374
+						implode(', ', array_keys($model_fields))
3375
+					)
3376
+				);
3377
+			}
3378
+		}
3379
+	}
3380
+
3381
+
3382
+	/**
3383
+	 * @param array $model_fields
3384
+	 * @param array $fieldValues
3385
+	 * @param bool  $set_from_db
3386
+	 * @throws EE_Error
3387
+	 * @throws ReflectionException
3388
+	 * @since $VID:$
3389
+	 */
3390
+	private function setFieldValues(array $model_fields, array $fieldValues, bool $set_from_db)
3391
+	{
3392
+		foreach ($model_fields as $fieldName => $field) {
3393
+			// if db model is instantiating
3394
+			if ($set_from_db) {
3395
+				// client code has indicated these field values are from the database
3396
+				$this->set_from_db(
3397
+					$fieldName,
3398
+					$fieldValues[ $fieldName ] ?? null
3399
+				);
3400
+			} else {
3401
+				// we're constructing a brand new instance of the model object.
3402
+				// Generally, this means we'll need to do more field validation
3403
+				$this->set(
3404
+					$fieldName,
3405
+					$fieldValues[ $fieldName ] ?? null,
3406
+					true
3407
+				);
3408
+			}
3409
+		}
3410
+	}
3411 3411
 }
Please login to merge, or discard this patch.
Spacing   +119 added lines, -119 removed lines patch added patch discarded remove patch
@@ -155,12 +155,12 @@  discard block
 block discarded – undo
155 155
         $this->validateFieldValues($model_fields, $field_values);
156 156
         $this->setFieldValues($model_fields, $field_values, $set_from_db);
157 157
         // remember in entity mapper
158
-        if (! $set_from_db && $this->_model->has_primary_key_field() && $this->ID()) {
158
+        if ( ! $set_from_db && $this->_model->has_primary_key_field() && $this->ID()) {
159 159
             $this->_model->add_to_entity_map($this);
160 160
         }
161 161
         // setup all the relations
162 162
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
163
-            $this->_model_relations[ $relation_name ] = $relation_obj instanceof EE_Belongs_To_Relation
163
+            $this->_model_relations[$relation_name] = $relation_obj instanceof EE_Belongs_To_Relation
164 164
                 ? null
165 165
                 : [];
166 166
         }
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
      */
184 184
     public function get_model(string $timezone = '')
185 185
     {
186
-        if (! $this->_model) {
186
+        if ( ! $this->_model) {
187 187
             $timezone     = $timezone ?? $this->_timezone;
188 188
             $modelName    = self::_get_model_classname(get_class($this));
189 189
             $this->_model = self::_get_model_instance_with_name($modelName, $timezone);
@@ -223,7 +223,7 @@  discard block
 block discarded – undo
223 223
             } else {
224 224
                 $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
225 225
             }
226
-            $this->_fields[ $field_name ] = $field_value;
226
+            $this->_fields[$field_name] = $field_value;
227 227
             $this->_clear_cached_property($field_name);
228 228
         }
229 229
     }
@@ -250,7 +250,7 @@  discard block
 block discarded – undo
250 250
         // then don't do anything
251 251
         if (
252 252
             ! $use_default
253
-            && $this->_fields[ $field_name ] === $field_value
253
+            && $this->_fields[$field_name] === $field_value
254 254
             && $this->ID()
255 255
         ) {
256 256
             return;
@@ -266,7 +266,7 @@  discard block
 block discarded – undo
266 266
             $holder_of_value = $field_obj->prepare_for_set($field_value);
267 267
             // should the value be null?
268 268
             if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
269
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
269
+                $this->_fields[$field_name] = $field_obj->get_default_value();
270 270
                 /**
271 271
                  * To save having to refactor all the models, if a default value is used for a
272 272
                  * EE_Datetime_Field, and that value is not null nor is it a DateTime
@@ -277,15 +277,15 @@  discard block
 block discarded – undo
277 277
                  */
278 278
                 if (
279 279
                     $field_obj instanceof EE_Datetime_Field
280
-                    && $this->_fields[ $field_name ] !== null
281
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
280
+                    && $this->_fields[$field_name] !== null
281
+                    && ! $this->_fields[$field_name] instanceof DateTime
282 282
                 ) {
283
-                    empty($this->_fields[ $field_name ])
283
+                    empty($this->_fields[$field_name])
284 284
                         ? $this->set($field_name, time())
285
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
285
+                        : $this->set($field_name, $this->_fields[$field_name]);
286 286
                 }
287 287
             } else {
288
-                $this->_fields[ $field_name ] = $holder_of_value;
288
+                $this->_fields[$field_name] = $holder_of_value;
289 289
             }
290 290
             // if we're not in the constructor...
291 291
             // now check if what we set was a primary key
@@ -344,7 +344,7 @@  discard block
 block discarded – undo
344 344
     {
345 345
         // now that we know the name of the variable, use a variable variable to get its value and return its
346 346
         if ($this->_model->has_primary_key_field()) {
347
-            return $this->_fields[ $this->_model->primary_key_name() ];
347
+            return $this->_fields[$this->_model->primary_key_name()];
348 348
         }
349 349
         return $this->_model->get_index_primary_key_string($this->_fields);
350 350
     }
@@ -361,7 +361,7 @@  discard block
 block discarded – undo
361 361
     {
362 362
         return strpos($model_name, 'EE_') === 0
363 363
             ? str_replace('EE_', 'EEM_', $model_name)
364
-            : 'EEM_' . $model_name;
364
+            : 'EEM_'.$model_name;
365 365
     }
366 366
 
367 367
 
@@ -394,7 +394,7 @@  discard block
 block discarded – undo
394 394
      */
395 395
     protected function _clear_cached_property(string $property_name)
396 396
     {
397
-        unset($this->_cached_properties[ $property_name ]);
397
+        unset($this->_cached_properties[$property_name]);
398 398
     }
399 399
 
400 400
 
@@ -413,7 +413,7 @@  discard block
 block discarded – undo
413 413
     protected static function _get_model(string $classname, string $timezone = ''): EEM_Base
414 414
     {
415 415
         // find model for this class
416
-        if (! $classname) {
416
+        if ( ! $classname) {
417 417
             throw new EE_Error(
418 418
                 sprintf(
419 419
                     esc_html__(
@@ -471,9 +471,9 @@  discard block
 block discarded – undo
471 471
         // verify the field exists
472 472
         $this->_model->field_settings_for($field_name);
473 473
         $cache_type = $pretty ? 'pretty' : 'standard';
474
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
475
-        if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
476
-            return $this->_cached_properties[ $field_name ][ $cache_type ];
474
+        $cache_type .= ! empty($extra_cache_ref) ? '_'.$extra_cache_ref : '';
475
+        if (isset($this->_cached_properties[$field_name][$cache_type])) {
476
+            return $this->_cached_properties[$field_name][$cache_type];
477 477
         }
478 478
         $value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
479 479
         $this->_set_cached_property($field_name, $value, $cache_type);
@@ -500,12 +500,12 @@  discard block
 block discarded – undo
500 500
         if ($field_obj instanceof EE_Datetime_Field) {
501 501
             $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
502 502
         }
503
-        if (! isset($this->_fields[ $field_name ])) {
504
-            $this->_fields[ $field_name ] = null;
503
+        if ( ! isset($this->_fields[$field_name])) {
504
+            $this->_fields[$field_name] = null;
505 505
         }
506 506
         return $pretty
507
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
508
-            : $field_obj->prepare_for_get($this->_fields[ $field_name ]);
507
+            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$field_name], $extra_cache_ref)
508
+            : $field_obj->prepare_for_get($this->_fields[$field_name]);
509 509
     }
510 510
 
511 511
 
@@ -526,7 +526,7 @@  discard block
 block discarded – undo
526 526
         // first make sure this property exists
527 527
         $this->_model->field_settings_for($field_name);
528 528
         $cache_type = empty($cache_type) ? 'standard' : $cache_type;
529
-        $this->_cached_properties[ $field_name ][ $cache_type ] = $value;
529
+        $this->_cached_properties[$field_name][$cache_type] = $value;
530 530
     }
531 531
 
532 532
 
@@ -580,9 +580,9 @@  discard block
 block discarded – undo
580 580
         $primary_id_ref = self::_get_primary_key_name($classname);
581 581
         if (
582 582
             array_key_exists($primary_id_ref, $props_n_values)
583
-            && ! empty($props_n_values[ $primary_id_ref ])
583
+            && ! empty($props_n_values[$primary_id_ref])
584 584
         ) {
585
-            $id = $props_n_values[ $primary_id_ref ];
585
+            $id = $props_n_values[$primary_id_ref];
586 586
             return self::_get_model($classname)->get_from_entity_map($id);
587 587
         }
588 588
         return null;
@@ -604,7 +604,7 @@  discard block
 block discarded – undo
604 604
      */
605 605
     protected static function _get_primary_key_name($classname = null): string
606 606
     {
607
-        if (! $classname) {
607
+        if ( ! $classname) {
608 608
             throw new EE_Error(
609 609
                 sprintf(
610 610
                     esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
@@ -645,9 +645,9 @@  discard block
 block discarded – undo
645 645
             $primary_id_ref = self::_get_primary_key_name($classname);
646 646
             if (
647 647
                 array_key_exists($primary_id_ref, $props_n_values)
648
-                && ! empty($props_n_values[ $primary_id_ref ])
648
+                && ! empty($props_n_values[$primary_id_ref])
649 649
             ) {
650
-                $existing = $model->get_one_by_ID($props_n_values[ $primary_id_ref ]);
650
+                $existing = $model->get_one_by_ID($props_n_values[$primary_id_ref]);
651 651
             }
652 652
         } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
653 653
             // no primary key on this model, but there's still a matching item in the DB
@@ -745,10 +745,10 @@  discard block
 block discarded – undo
745 745
     public function get_original(string $field_name)
746 746
     {
747 747
         if (
748
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
748
+            isset($this->_props_n_values_provided_in_constructor[$field_name])
749 749
             && $field_settings = $this->_model->field_settings_for($field_name)
750 750
         ) {
751
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
751
+            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
752 752
         }
753 753
         return null;
754 754
     }
@@ -784,8 +784,8 @@  discard block
 block discarded – undo
784 784
      */
785 785
     public function getCustomSelect($alias)
786 786
     {
787
-        return isset($this->custom_selection_results[ $alias ])
788
-            ? $this->custom_selection_results[ $alias ]
787
+        return isset($this->custom_selection_results[$alias])
788
+            ? $this->custom_selection_results[$alias]
789 789
             : null;
790 790
     }
791 791
 
@@ -852,7 +852,7 @@  discard block
 block discarded – undo
852 852
             $this->set($column, $value);
853 853
         }
854 854
         // no changes ? then don't do anything
855
-        if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
855
+        if ( ! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
856 856
             return 0;
857 857
         }
858 858
         /**
@@ -862,7 +862,7 @@  discard block
 block discarded – undo
862 862
          * @param EE_Base_Class $model_object the model object about to be saved.
863 863
          */
864 864
         do_action('AHEE__EE_Base_Class__save__begin', $this);
865
-        if (! $this->allow_persist()) {
865
+        if ( ! $this->allow_persist()) {
866 866
             return 0;
867 867
         }
868 868
         // now get current attribute values
@@ -877,10 +877,10 @@  discard block
 block discarded – undo
877 877
         if ($this->_model->has_primary_key_field()) {
878 878
             if ($this->_model->get_primary_key_field()->is_auto_increment()) {
879 879
                 // ok check if it's set, if so: update; if not, insert
880
-                if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
880
+                if ( ! empty($save_cols_n_values[$this->_model->primary_key_name()])) {
881 881
                     $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
882 882
                 } else {
883
-                    unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
883
+                    unset($save_cols_n_values[$this->_model->primary_key_name()]);
884 884
                     $results = $this->_model->insert($save_cols_n_values);
885 885
                     if ($results) {
886 886
                         // if successful, set the primary key
@@ -890,7 +890,7 @@  discard block
 block discarded – undo
890 890
                         // will get added to the mapper before we can add this one!
891 891
                         // but if we just avoid using the SET method, all that headache can be avoided
892 892
                         $pk_field_name                   = $this->_model->primary_key_name();
893
-                        $this->_fields[ $pk_field_name ] = $results;
893
+                        $this->_fields[$pk_field_name] = $results;
894 894
                         $this->_clear_cached_property($pk_field_name);
895 895
                         $this->_model->add_to_entity_map($this);
896 896
                         $this->_update_cached_related_model_objs_fks();
@@ -908,8 +908,8 @@  discard block
 block discarded – undo
908 908
                                     'event_espresso'
909 909
                                 ),
910 910
                                 get_class($this),
911
-                                get_class($this->_model) . '::instance()->add_to_entity_map()',
912
-                                get_class($this->_model) . '::instance()->get_one_by_ID()',
911
+                                get_class($this->_model).'::instance()->add_to_entity_map()',
912
+                                get_class($this->_model).'::instance()->get_one_by_ID()',
913 913
                                 '<br />'
914 914
                             )
915 915
                         );
@@ -934,7 +934,7 @@  discard block
 block discarded – undo
934 934
                     $save_cols_n_values,
935 935
                     $this->_model->get_combined_primary_key_fields()
936 936
                 );
937
-                $results                     = $this->_model->update(
937
+                $results = $this->_model->update(
938 938
                     $save_cols_n_values,
939 939
                     $combined_pk_fields_n_values
940 940
                 );
@@ -987,7 +987,7 @@  discard block
 block discarded – undo
987 987
             $query_params[0]['EXM_value'] = $meta_value;
988 988
         }
989 989
         $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
990
-        if (! $existing_rows_like_that) {
990
+        if ( ! $existing_rows_like_that) {
991 991
             return $this->add_extra_meta($meta_key, $meta_value);
992 992
         }
993 993
         foreach ($existing_rows_like_that as $existing_row) {
@@ -1118,7 +1118,7 @@  discard block
 block discarded – undo
1118 1118
      */
1119 1119
     public function get_all_from_cache($relationName)
1120 1120
     {
1121
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
1121
+        $objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : [];
1122 1122
         // if the result is not an array, but exists, make it an array
1123 1123
         $objects = is_array($objects) ? $objects : [$objects];
1124 1124
         // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
@@ -1213,7 +1213,7 @@  discard block
 block discarded – undo
1213 1213
                 $values = [];
1214 1214
                 foreach ($results as $result) {
1215 1215
                     if ($result instanceof EE_Extra_Meta) {
1216
-                        $values[ $result->ID() ] = $result->value();
1216
+                        $values[$result->ID()] = $result->value();
1217 1217
                     }
1218 1218
                 }
1219 1219
                 return $values;
@@ -1246,7 +1246,7 @@  discard block
 block discarded – undo
1246 1246
     public function get_first_related($relationName, $query_params = [])
1247 1247
     {
1248 1248
         $model_relation = $this->_model->related_settings_for($relationName);
1249
-        if (! $this->ID()) {
1249
+        if ( ! $this->ID()) {
1250 1250
             // this doesn't exist in the DB,
1251 1251
             // but maybe the relation type is "belongs to" and the related object might
1252 1252
             if ($model_relation instanceof EE_Belongs_To_Relation) {
@@ -1309,7 +1309,7 @@  discard block
 block discarded – undo
1309 1309
      */
1310 1310
     public function get_many_related($relationName, $query_params = [])
1311 1311
     {
1312
-        if (! $this->ID()) {
1312
+        if ( ! $this->ID()) {
1313 1313
             // this doesn't exist in the DB, so just get the related things from the cache
1314 1314
             return $this->get_all_from_cache($relationName);
1315 1315
         }
@@ -1351,8 +1351,8 @@  discard block
 block discarded – undo
1351 1351
      */
1352 1352
     public function get_one_from_cache($relationName)
1353 1353
     {
1354
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
1355
-            ? $this->_model_relations[ $relationName ]
1354
+        $cached_array_or_object = isset($this->_model_relations[$relationName])
1355
+            ? $this->_model_relations[$relationName]
1356 1356
             : null;
1357 1357
         if (is_array($cached_array_or_object)) {
1358 1358
             $cached_array_or_object = array_shift($cached_array_or_object);
@@ -1383,11 +1383,11 @@  discard block
 block discarded – undo
1383 1383
     public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
1384 1384
     {
1385 1385
         // its entirely possible that there IS no related object yet in which case there is nothing to cache.
1386
-        if (! $object_to_cache instanceof EE_Base_Class) {
1386
+        if ( ! $object_to_cache instanceof EE_Base_Class) {
1387 1387
             return false;
1388 1388
         }
1389 1389
         // also get "how" the object is related, or throw an error
1390
-        if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1390
+        if ( ! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1391 1391
             throw new EE_Error(
1392 1392
                 sprintf(
1393 1393
                     esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
@@ -1401,38 +1401,38 @@  discard block
 block discarded – undo
1401 1401
             // if it's a "belongs to" relationship, then there's only one related model object
1402 1402
             // eg, if this is a registration, there's only 1 attendee for it
1403 1403
             // so for these model objects just set it to be cached
1404
-            $this->_model_relations[ $relationName ] = $object_to_cache;
1404
+            $this->_model_relations[$relationName] = $object_to_cache;
1405 1405
             return true;
1406 1406
         }
1407 1407
         // otherwise, this is the "many" side of a one to many relationship,
1408 1408
         // so we'll add the object to the array of related objects for that type.
1409 1409
         // eg: if this is an event, there are many registrations for that event,
1410 1410
         // so we cache the registrations in an array
1411
-        if (! is_array($this->_model_relations[ $relationName ])) {
1411
+        if ( ! is_array($this->_model_relations[$relationName])) {
1412 1412
             // if for some reason, the cached item is a model object,
1413 1413
             // then stick that in the array, otherwise start with an empty array
1414
-            $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ] instanceof EE_Base_Class
1415
-                ? [$this->_model_relations[ $relationName ]]
1414
+            $this->_model_relations[$relationName] = $this->_model_relations[$relationName] instanceof EE_Base_Class
1415
+                ? [$this->_model_relations[$relationName]]
1416 1416
                 : [];
1417 1417
         }
1418 1418
         // first check for a cache_id which is normally empty
1419
-        if (! empty($cache_id)) {
1419
+        if ( ! empty($cache_id)) {
1420 1420
             // if the cache_id exists, then it means we are purposely trying to cache this
1421 1421
             // with a known key that can then be used to retrieve the object later on
1422
-            $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
1422
+            $this->_model_relations[$relationName][$cache_id] = $object_to_cache;
1423 1423
             return $cache_id;
1424 1424
         }
1425 1425
         if ($object_to_cache->ID()) {
1426 1426
             // OR the cached object originally came from the db, so let's just use it's PK for an ID
1427
-            $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
1427
+            $this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
1428 1428
             return $object_to_cache->ID();
1429 1429
         }
1430 1430
         // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
1431
-        $this->_model_relations[ $relationName ][] = $object_to_cache;
1431
+        $this->_model_relations[$relationName][] = $object_to_cache;
1432 1432
         // move the internal pointer to the end of the array
1433
-        end($this->_model_relations[ $relationName ]);
1433
+        end($this->_model_relations[$relationName]);
1434 1434
         // and grab the key so that we can return it
1435
-        return key($this->_model_relations[ $relationName ]);
1435
+        return key($this->_model_relations[$relationName]);
1436 1436
     }
1437 1437
 
1438 1438
 
@@ -1464,7 +1464,7 @@  discard block
 block discarded – undo
1464 1464
     {
1465 1465
         static $set_in_progress = false;
1466 1466
         // don't update the timezone if it's already set ?
1467
-        if (($only_if_not_set && $this->_timezone !== '') ) {
1467
+        if (($only_if_not_set && $this->_timezone !== '')) {
1468 1468
             return;
1469 1469
         }
1470 1470
         $valid_timezone = ! empty($timezone) && $this->_timezone && $timezone !== $this->_timezone
@@ -1494,8 +1494,8 @@  discard block
 block discarded – undo
1494 1494
         foreach ($model_fields as $field_name => $field_obj) {
1495 1495
             if ($field_obj instanceof EE_Datetime_Field) {
1496 1496
                 $field_obj->set_timezone($this->_timezone);
1497
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
1498
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], $TZ);
1497
+                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
1498
+                    EEH_DTT_Helper::setTimezone($this->_fields[$field_name], $TZ);
1499 1499
                 }
1500 1500
             }
1501 1501
         }
@@ -1530,7 +1530,7 @@  discard block
 block discarded – undo
1530 1530
      */
1531 1531
     public function get_format($full = true)
1532 1532
     {
1533
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1533
+        return $full ? $this->_dt_frmt.' '.$this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1534 1534
     }
1535 1535
 
1536 1536
 
@@ -1554,8 +1554,8 @@  discard block
 block discarded – undo
1554 1554
         $current_cache_id = ''
1555 1555
     ) {
1556 1556
         // verify that incoming object is of the correct type
1557
-        $obj_class = 'EE_' . $relationName;
1558
-        if (! $newly_saved_object instanceof $obj_class) {
1557
+        $obj_class = 'EE_'.$relationName;
1558
+        if ( ! $newly_saved_object instanceof $obj_class) {
1559 1559
             return false;
1560 1560
         }
1561 1561
 		$this->updateTimezoneOnRelated($newly_saved_object);
@@ -1565,18 +1565,18 @@  discard block
 block discarded – undo
1565 1565
         // if this is a 1:1 relationship
1566 1566
         if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1567 1567
             // then just replace the cached object with the newly saved object
1568
-            $this->_model_relations[ $relationName ] = $newly_saved_object;
1568
+            $this->_model_relations[$relationName] = $newly_saved_object;
1569 1569
             return true;
1570 1570
         }
1571 1571
         // or if it's some kind of sordid feral polyamorous relationship...
1572 1572
         if (
1573
-            is_array($this->_model_relations[ $relationName ])
1574
-            && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
1573
+            is_array($this->_model_relations[$relationName])
1574
+            && isset($this->_model_relations[$relationName][$current_cache_id])
1575 1575
         ) {
1576 1576
             // then remove the current cached item
1577
-            unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
1577
+            unset($this->_model_relations[$relationName][$current_cache_id]);
1578 1578
             // and cache the newly saved object using it's new ID
1579
-            $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
1579
+            $this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
1580 1580
             return true;
1581 1581
         }
1582 1582
         return false;
@@ -1718,7 +1718,7 @@  discard block
 block discarded – undo
1718 1718
     public function get_DateTime_object(string $field_name)
1719 1719
     {
1720 1720
         $field_settings = $this->_model->field_settings_for($field_name);
1721
-        if (! $field_settings instanceof EE_Datetime_Field) {
1721
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1722 1722
             EE_Error::add_error(
1723 1723
                 sprintf(
1724 1724
                     esc_html__(
@@ -1733,8 +1733,8 @@  discard block
 block discarded – undo
1733 1733
             );
1734 1734
             return false;
1735 1735
         }
1736
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1737
-            ? clone $this->_fields[ $field_name ]
1736
+        return isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime
1737
+            ? clone $this->_fields[$field_name]
1738 1738
             : null;
1739 1739
     }
1740 1740
 
@@ -1980,7 +1980,7 @@  discard block
 block discarded – undo
1980 1980
      */
1981 1981
     public function get_i18n_datetime(string $field_name, $format = '')
1982 1982
     {
1983
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1983
+        $format = empty($format) ? $this->_dt_frmt.' '.$this->_tm_frmt : $format;
1984 1984
         return date_i18n(
1985 1985
             $format,
1986 1986
             EEH_DTT_Helper::get_timestamp_with_offset(
@@ -2004,9 +2004,9 @@  discard block
 block discarded – undo
2004 2004
     public function get_raw(string $field_name)
2005 2005
     {
2006 2006
         $field_settings = $this->_model->field_settings_for($field_name);
2007
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
2008
-            ? $this->_fields[ $field_name ]->format('U')
2009
-            : $this->_fields[ $field_name ];
2007
+        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
2008
+            ? $this->_fields[$field_name]->format('U')
2009
+            : $this->_fields[$field_name];
2010 2010
     }
2011 2011
 
2012 2012
 
@@ -2046,7 +2046,7 @@  discard block
 block discarded – undo
2046 2046
         $this->set_timezone($timezone);
2047 2047
         $fn   = (array) $field_name;
2048 2048
         $args = array_merge($fn, (array) $args);
2049
-        if (! method_exists($this, $callback)) {
2049
+        if ( ! method_exists($this, $callback)) {
2050 2050
             throw new EE_Error(
2051 2051
                 sprintf(
2052 2052
                     esc_html__(
@@ -2058,7 +2058,7 @@  discard block
 block discarded – undo
2058 2058
             );
2059 2059
         }
2060 2060
         $args   = (array) $args;
2061
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
2061
+        $return = $prepend.call_user_func_array([$this, $callback], $args).$append;
2062 2062
         $this->set_timezone($original_timezone);
2063 2063
         return $return;
2064 2064
     }
@@ -2171,8 +2171,8 @@  discard block
 block discarded – undo
2171 2171
     public function refresh_cache_of_related_objects()
2172 2172
     {
2173 2173
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
2174
-            if (! empty($this->_model_relations[ $relation_name ])) {
2175
-                $related_objects = $this->_model_relations[ $relation_name ];
2174
+            if ( ! empty($this->_model_relations[$relation_name])) {
2175
+                $related_objects = $this->_model_relations[$relation_name];
2176 2176
                 if ($relation_obj instanceof EE_Belongs_To_Relation) {
2177 2177
                     // this relation only stores a single model object, not an array
2178 2178
                     // but let's make it consistent
@@ -2217,7 +2217,7 @@  discard block
 block discarded – undo
2217 2217
     {
2218 2218
         $relationship_to_model = $this->_model->related_settings_for($relationName);
2219 2219
         $index_in_cache        = '';
2220
-        if (! $relationship_to_model) {
2220
+        if ( ! $relationship_to_model) {
2221 2221
             throw new EE_Error(
2222 2222
                 sprintf(
2223 2223
                     esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
@@ -2228,10 +2228,10 @@  discard block
 block discarded – undo
2228 2228
         }
2229 2229
         if ($clear_all) {
2230 2230
             $obj_removed                             = true;
2231
-            $this->_model_relations[ $relationName ] = null;
2231
+            $this->_model_relations[$relationName] = null;
2232 2232
         } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
2233
-            $obj_removed                             = $this->_model_relations[ $relationName ];
2234
-            $this->_model_relations[ $relationName ] = null;
2233
+            $obj_removed                             = $this->_model_relations[$relationName];
2234
+            $this->_model_relations[$relationName] = null;
2235 2235
         } else {
2236 2236
             if (
2237 2237
                 $object_to_remove_or_index_into_array instanceof EE_Base_Class
@@ -2239,12 +2239,12 @@  discard block
 block discarded – undo
2239 2239
             ) {
2240 2240
                 $index_in_cache = $object_to_remove_or_index_into_array->ID();
2241 2241
                 if (
2242
-                    is_array($this->_model_relations[ $relationName ])
2243
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
2242
+                    is_array($this->_model_relations[$relationName])
2243
+                    && ! isset($this->_model_relations[$relationName][$index_in_cache])
2244 2244
                 ) {
2245 2245
                     $index_found_at = null;
2246 2246
                     // find this object in the array even though it has a different key
2247
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
2247
+                    foreach ($this->_model_relations[$relationName] as $index => $obj) {
2248 2248
                         /** @noinspection TypeUnsafeComparisonInspection */
2249 2249
                         if (
2250 2250
                             $obj instanceof EE_Base_Class
@@ -2278,9 +2278,9 @@  discard block
 block discarded – undo
2278 2278
             }
2279 2279
             // supposedly we've found it. But it could just be that the client code
2280 2280
             // provided a bad index/object
2281
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
2282
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
2283
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
2281
+            if (isset($this->_model_relations[$relationName][$index_in_cache])) {
2282
+                $obj_removed = $this->_model_relations[$relationName][$index_in_cache];
2283
+                unset($this->_model_relations[$relationName][$index_in_cache]);
2284 2284
             } else {
2285 2285
                 // that thing was never cached anyways.
2286 2286
                 $obj_removed = null;
@@ -2308,27 +2308,27 @@  discard block
 block discarded – undo
2308 2308
     public function save_new_cached_related_model_objs()
2309 2309
     {
2310 2310
         // make sure this has been saved
2311
-        if (! $this->ID()) {
2311
+        if ( ! $this->ID()) {
2312 2312
             $id = $this->save();
2313 2313
         } else {
2314 2314
             $id = $this->ID();
2315 2315
         }
2316 2316
         // now save all the NEW cached model objects  (ie they don't exist in the DB)
2317 2317
         foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
2318
-            if ($this->_model_relations[ $relationName ]) {
2318
+            if ($this->_model_relations[$relationName]) {
2319 2319
                 // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2320 2320
                 // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2321 2321
                 /* @var $related_model_obj EE_Base_Class */
2322 2322
                 if ($relationObj instanceof EE_Belongs_To_Relation) {
2323 2323
                     // add a relation to that relation type (which saves the appropriate thing in the process)
2324 2324
                     // but ONLY if it DOES NOT exist in the DB
2325
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2325
+                    $related_model_obj = $this->_model_relations[$relationName];
2326 2326
                     // if( ! $related_model_obj->ID()){
2327 2327
                     $this->_add_relation_to($related_model_obj, $relationName);
2328 2328
                     $related_model_obj->save_new_cached_related_model_objs();
2329 2329
                     // }
2330 2330
                 } else {
2331
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2331
+                    foreach ($this->_model_relations[$relationName] as $related_model_obj) {
2332 2332
                         // add a relation to that relation type (which saves the appropriate thing in the process)
2333 2333
                         // but ONLY if it DOES NOT exist in the DB
2334 2334
                         // if( ! $related_model_obj->ID()){
@@ -2389,7 +2389,7 @@  discard block
 block discarded – undo
2389 2389
             }
2390 2390
         } else {
2391 2391
             // this thing doesn't exist in the DB,  so just cache it
2392
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2392
+            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2393 2393
                 throw new EE_Error(
2394 2394
                     sprintf(
2395 2395
                         esc_html__(
@@ -2656,7 +2656,7 @@  discard block
 block discarded – undo
2656 2656
      */
2657 2657
     public function is_set(string $field_name)
2658 2658
     {
2659
-        return isset($this->_fields[ $field_name ]);
2659
+        return isset($this->_fields[$field_name]);
2660 2660
     }
2661 2661
 
2662 2662
 
@@ -2691,7 +2691,7 @@  discard block
 block discarded – undo
2691 2691
     {
2692 2692
         $className = get_class($this);
2693 2693
         $tagName   = "FHEE__{$className}__{$methodName}";
2694
-        if (! has_filter($tagName)) {
2694
+        if ( ! has_filter($tagName)) {
2695 2695
             throw new EE_Error(
2696 2696
                 sprintf(
2697 2697
                     esc_html__(
@@ -2765,17 +2765,17 @@  discard block
 block discarded – undo
2765 2765
             );
2766 2766
             foreach ($extra_meta_objs as $extra_meta_obj) {
2767 2767
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2768
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2768
+                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2769 2769
                 }
2770 2770
             }
2771 2771
         } else {
2772 2772
             $extra_meta_objs = $this->get_many_related('Extra_Meta');
2773 2773
             foreach ($extra_meta_objs as $extra_meta_obj) {
2774 2774
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2775
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2776
-                        $return_array[ $extra_meta_obj->key() ] = [];
2775
+                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2776
+                        $return_array[$extra_meta_obj->key()] = [];
2777 2777
                     }
2778
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2778
+                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2779 2779
                 }
2780 2780
             }
2781 2781
         }
@@ -2811,8 +2811,8 @@  discard block
 block discarded – undo
2811 2811
                             'event_espresso'
2812 2812
                         ),
2813 2813
                         $this->ID(),
2814
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2815
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2814
+                        get_class($this->get_model()).'::instance()->add_to_entity_map()',
2815
+                        get_class($this->get_model()).'::instance()->refresh_entity_map()'
2816 2816
                     )
2817 2817
                 );
2818 2818
             }
@@ -2901,7 +2901,7 @@  discard block
 block discarded – undo
2901 2901
     {
2902 2902
         // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
2903 2903
         // if it wasn't even there to start off.
2904
-        if (! $this->ID()) {
2904
+        if ( ! $this->ID()) {
2905 2905
             $this->save();
2906 2906
         }
2907 2907
         global $wpdb;
@@ -2941,7 +2941,7 @@  discard block
 block discarded – undo
2941 2941
             $table_pk_field_name = $table_obj->get_pk_column();
2942 2942
         }
2943 2943
 
2944
-        $query  =
2944
+        $query =
2945 2945
             "UPDATE `{$table_name}`
2946 2946
             SET "
2947 2947
             . $new_value_sql
@@ -2989,7 +2989,7 @@  discard block
 block discarded – undo
2989 2989
         $properties = [];
2990 2990
         // remove prepended underscore
2991 2991
         foreach ($fields as $field_name => $settings) {
2992
-            $properties[ $field_name ] = $this->get($field_name);
2992
+            $properties[$field_name] = $this->get($field_name);
2993 2993
         }
2994 2994
         return $properties;
2995 2995
     }
@@ -3125,7 +3125,7 @@  discard block
 block discarded – undo
3125 3125
     {
3126 3126
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3127 3127
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
3128
-                $classname = 'EE_' . $this->_model->get_this_model_name();
3128
+                $classname = 'EE_'.$this->_model->get_this_model_name();
3129 3129
                 if (
3130 3130
                     $this->get_one_from_cache($relation_name) instanceof $classname
3131 3131
                     && $this->get_one_from_cache($relation_name)->ID()
@@ -3170,7 +3170,7 @@  discard block
 block discarded – undo
3170 3170
         // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3171 3171
         foreach ($this->_fields as $field => $value) {
3172 3172
             if ($value instanceof DateTime) {
3173
-                $this->_fields[ $field ] = clone $value;
3173
+                $this->_fields[$field] = clone $value;
3174 3174
             }
3175 3175
         }
3176 3176
     }
@@ -3237,21 +3237,21 @@  discard block
 block discarded – undo
3237 3237
         $what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
3238 3238
         switch ($what) {
3239 3239
             case 'T':
3240
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
3240
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_time(
3241 3241
                     $datetime_value,
3242
-                    $this->_fields[ $field_name ]
3242
+                    $this->_fields[$field_name]
3243 3243
                 );
3244 3244
                 $this->_has_changes           = true;
3245 3245
                 break;
3246 3246
             case 'D':
3247
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
3247
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_date(
3248 3248
                     $datetime_value,
3249
-                    $this->_fields[ $field_name ]
3249
+                    $this->_fields[$field_name]
3250 3250
                 );
3251 3251
                 $this->_has_changes           = true;
3252 3252
                 break;
3253 3253
             case 'B':
3254
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
3254
+                $this->_fields[$field_name] = $field->prepare_for_set($datetime_value);
3255 3255
                 $this->_has_changes           = true;
3256 3256
                 break;
3257 3257
         }
@@ -3320,7 +3320,7 @@  discard block
 block discarded – undo
3320 3320
     {
3321 3321
         foreach ((array) $properties as $property_name) {
3322 3322
             // first make sure this property exists
3323
-            if (! $this->_fields[ $property_name ]) {
3323
+            if ( ! $this->_fields[$property_name]) {
3324 3324
                 throw new EE_Error(
3325 3325
                     sprintf(
3326 3326
                         esc_html__(
@@ -3342,7 +3342,7 @@  discard block
 block discarded – undo
3342 3342
      */
3343 3343
     private function setDateAndTimeFormats(array $date_formats)
3344 3344
     {
3345
-        if (! empty($date_formats) && is_array($date_formats)) {
3345
+        if ( ! empty($date_formats) && is_array($date_formats)) {
3346 3346
             [$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
3347 3347
         } else {
3348 3348
             // set default formats for date and time
@@ -3362,7 +3362,7 @@  discard block
 block discarded – undo
3362 3362
     {
3363 3363
         // verify client code has not passed any invalid field names
3364 3364
         foreach ($fieldValues as $field_name => $field_value) {
3365
-            if (! isset($model_fields[ $field_name ])) {
3365
+            if ( ! isset($model_fields[$field_name])) {
3366 3366
                 throw new EE_Error(
3367 3367
                     sprintf(
3368 3368
                         esc_html__(
@@ -3395,14 +3395,14 @@  discard block
 block discarded – undo
3395 3395
                 // client code has indicated these field values are from the database
3396 3396
                 $this->set_from_db(
3397 3397
                     $fieldName,
3398
-                    $fieldValues[ $fieldName ] ?? null
3398
+                    $fieldValues[$fieldName] ?? null
3399 3399
                 );
3400 3400
             } else {
3401 3401
                 // we're constructing a brand new instance of the model object.
3402 3402
                 // Generally, this means we'll need to do more field validation
3403 3403
                 $this->set(
3404 3404
                     $fieldName,
3405
-                    $fieldValues[ $fieldName ] ?? null,
3405
+                    $fieldValues[$fieldName] ?? null,
3406 3406
                     true
3407 3407
                 );
3408 3408
             }
Please login to merge, or discard this patch.
core/db_classes/EE_Ticket.class.php 1 patch
Indentation   +1998 added lines, -1998 removed lines patch added patch discarded remove patch
@@ -14,2006 +14,2006 @@
 block discarded – undo
14 14
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
15 15
 {
16 16
 
17
-    /**
18
-     * TicKet Sold out:
19
-     * constant used by ticket_status() to indicate that a ticket is sold out
20
-     * and no longer available for purchases
21
-     */
22
-    const sold_out = 'TKS';
23
-
24
-    /**
25
-     * TicKet Expired:
26
-     * constant used by ticket_status() to indicate that a ticket is expired
27
-     * and no longer available for purchase
28
-     */
29
-    const expired = 'TKE';
30
-
31
-    /**
32
-     * TicKet Archived:
33
-     * constant used by ticket_status() to indicate that a ticket is archived
34
-     * and no longer available for purchase
35
-     */
36
-    const archived = 'TKA';
37
-
38
-    /**
39
-     * TicKet Pending:
40
-     * constant used by ticket_status() to indicate that a ticket is pending
41
-     * and is NOT YET available for purchase
42
-     */
43
-    const pending = 'TKP';
44
-
45
-    /**
46
-     * TicKet On sale:
47
-     * constant used by ticket_status() to indicate that a ticket is On Sale
48
-     * and IS available for purchase
49
-     */
50
-    const onsale = 'TKO';
51
-
52
-    /**
53
-     * extra meta key for tracking ticket reservations
54
-     *
55
-     * @type string
56
-     */
57
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
58
-
59
-    /**
60
-     * override of parent property
61
-     *
62
-     * @var EEM_Ticket
63
-     */
64
-    protected $_model;
65
-
66
-    /**
67
-     * cached result from method of the same name
68
-     *
69
-     * @var float $_ticket_total_with_taxes
70
-     */
71
-    private $_ticket_total_with_taxes;
72
-
73
-
74
-    /**
75
-     * @param array  $props_n_values          incoming values
76
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
77
-     *                                        used.)
78
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
79
-     *                                        date_format and the second value is the time format
80
-     * @return EE_Ticket
81
-     * @throws EE_Error
82
-     * @throws ReflectionException
83
-     */
84
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
85
-    {
86
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
87
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
88
-    }
89
-
90
-
91
-    /**
92
-     * @param array  $props_n_values  incoming values from the database
93
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
94
-     *                                the website will be used.
95
-     * @return EE_Ticket
96
-     * @throws EE_Error
97
-     * @throws ReflectionException
98
-     */
99
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
100
-    {
101
-        return new self($props_n_values, true, $timezone);
102
-    }
103
-
104
-
105
-    /**
106
-     * @return bool
107
-     * @throws EE_Error
108
-     * @throws ReflectionException
109
-     */
110
-    public function parent()
111
-    {
112
-        return $this->get('TKT_parent');
113
-    }
114
-
115
-
116
-    /**
117
-     * return if a ticket has quantities available for purchase
118
-     *
119
-     * @param int $DTT_ID the primary key for a particular datetime
120
-     * @return boolean
121
-     * @throws EE_Error
122
-     * @throws ReflectionException
123
-     */
124
-    public function available($DTT_ID = 0)
125
-    {
126
-        // are we checking availability for a particular datetime ?
127
-        if ($DTT_ID) {
128
-            // get that datetime object
129
-            $datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
130
-            // if  ticket sales for this datetime have exceeded the reg limit...
131
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
132
-                return false;
133
-            }
134
-        }
135
-        // datetime is still open for registration, but is this ticket sold out ?
136
-        return $this->qty() < 1 || $this->qty() > $this->sold();
137
-    }
138
-
139
-
140
-    /**
141
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
142
-     *
143
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
144
-     *                               relevant status const
145
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
146
-     *                               further processing
147
-     * @return mixed status int if the display string isn't requested
148
-     * @throws EE_Error
149
-     * @throws ReflectionException
150
-     */
151
-    public function ticket_status($display = false, $remaining = null)
152
-    {
153
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
154
-        if (! $remaining) {
155
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
156
-        }
157
-        if ($this->get('TKT_deleted')) {
158
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
159
-        }
160
-        if ($this->is_expired()) {
161
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
162
-        }
163
-        if ($this->is_pending()) {
164
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
165
-        }
166
-        if ($this->is_on_sale()) {
167
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
168
-        }
169
-        return '';
170
-    }
171
-
172
-
173
-    /**
174
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
175
-     * considering ALL the factors used for figuring that out.
176
-     *
177
-     * @access public
178
-     * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
179
-     * @return boolean         true = tickets remaining, false not.
180
-     * @throws EE_Error
181
-     * @throws ReflectionException
182
-     */
183
-    public function is_remaining($DTT_ID = 0)
184
-    {
185
-        $num_remaining = $this->remaining($DTT_ID);
186
-        if ($num_remaining === 0) {
187
-            return false;
188
-        }
189
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
190
-            return false;
191
-        }
192
-        return true;
193
-    }
194
-
195
-
196
-    /**
197
-     * return the total number of tickets available for purchase
198
-     *
199
-     * @param int $DTT_ID  the primary key for a particular datetime.
200
-     *                     set to 0 for all related datetimes
201
-     * @return int
202
-     * @throws EE_Error
203
-     * @throws ReflectionException
204
-     */
205
-    public function remaining($DTT_ID = 0)
206
-    {
207
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
208
-    }
209
-
210
-
211
-    /**
212
-     * Gets min
213
-     *
214
-     * @return int
215
-     * @throws EE_Error
216
-     * @throws ReflectionException
217
-     */
218
-    public function min()
219
-    {
220
-        return $this->get('TKT_min');
221
-    }
222
-
223
-
224
-    /**
225
-     * return if a ticket is no longer available cause its available dates have expired.
226
-     *
227
-     * @return boolean
228
-     * @throws EE_Error
229
-     * @throws ReflectionException
230
-     */
231
-    public function is_expired()
232
-    {
233
-        return ($this->get_raw('TKT_end_date') < time());
234
-    }
235
-
236
-
237
-    /**
238
-     * Return if a ticket is yet to go on sale or not
239
-     *
240
-     * @return boolean
241
-     * @throws EE_Error
242
-     * @throws ReflectionException
243
-     */
244
-    public function is_pending()
245
-    {
246
-        return ($this->get_raw('TKT_start_date') >= time());
247
-    }
248
-
249
-
250
-    /**
251
-     * Return if a ticket is on sale or not
252
-     *
253
-     * @return boolean
254
-     * @throws EE_Error
255
-     * @throws ReflectionException
256
-     */
257
-    public function is_on_sale()
258
-    {
259
-        return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
260
-    }
261
-
262
-
263
-    /**
264
-     * This returns the chronologically last datetime that this ticket is associated with
265
-     *
266
-     * @param string $date_format
267
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
268
-     *                            the end date ie: Jan 01 "to" Dec 31
269
-     * @return string
270
-     * @throws EE_Error
271
-     * @throws ReflectionException
272
-     */
273
-    public function date_range($date_format = '', $conjunction = ' - ')
274
-    {
275
-        $date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
276
-        $first_date  = $this->first_datetime() instanceof EE_Datetime
277
-            ? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
278
-            : '';
279
-        $last_date   = $this->last_datetime() instanceof EE_Datetime
280
-            ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
281
-            : '';
282
-
283
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
284
-    }
285
-
286
-
287
-    /**
288
-     * This returns the chronologically first datetime that this ticket is associated with
289
-     *
290
-     * @return EE_Datetime
291
-     * @throws EE_Error
292
-     * @throws ReflectionException
293
-     */
294
-    public function first_datetime()
295
-    {
296
-        $datetimes = $this->datetimes(['limit' => 1]);
297
-        return reset($datetimes);
298
-    }
299
-
300
-
301
-    /**
302
-     * Gets all the datetimes this ticket can be used for attending.
303
-     * Unless otherwise specified, orders datetimes by start date.
304
-     *
305
-     * @param array $query_params @see
306
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
307
-     * @return EE_Datetime[]|EE_Base_Class[]
308
-     * @throws EE_Error
309
-     * @throws ReflectionException
310
-     */
311
-    public function datetimes($query_params = [])
312
-    {
313
-        if (! isset($query_params['order_by'])) {
314
-            $query_params['order_by']['DTT_order'] = 'ASC';
315
-        }
316
-        return $this->get_many_related('Datetime', $query_params);
317
-    }
318
-
319
-
320
-    /**
321
-     * This returns the chronologically last datetime that this ticket is associated with
322
-     *
323
-     * @return EE_Datetime
324
-     * @throws EE_Error
325
-     * @throws ReflectionException
326
-     */
327
-    public function last_datetime()
328
-    {
329
-        $datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
330
-        return end($datetimes);
331
-    }
332
-
333
-
334
-    /**
335
-     * This returns the total tickets sold depending on the given parameters.
336
-     *
337
-     * @param string $what    Can be one of two options: 'ticket', 'datetime'.
338
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
339
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
340
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
341
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
342
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
343
-     * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
344
-     * @return mixed (array|int)          how many tickets have sold
345
-     * @throws EE_Error
346
-     * @throws ReflectionException
347
-     */
348
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
349
-    {
350
-        $total        = 0;
351
-        $tickets_sold = $this->_all_tickets_sold();
352
-        switch ($what) {
353
-            case 'ticket':
354
-                return $tickets_sold['ticket'];
355
-                break;
356
-            case 'datetime':
357
-                if (empty($tickets_sold['datetime'])) {
358
-                    return $total;
359
-                }
360
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
361
-                    EE_Error::add_error(
362
-                        __(
363
-                            '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?',
364
-                            'event_espresso'
365
-                        ),
366
-                        __FILE__,
367
-                        __FUNCTION__,
368
-                        __LINE__
369
-                    );
370
-                    return $total;
371
-                }
372
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
373
-                break;
374
-            default:
375
-                return $total;
376
-        }
377
-    }
378
-
379
-
380
-    /**
381
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
382
-     *
383
-     * @return EE_Ticket[]
384
-     * @throws EE_Error
385
-     * @throws ReflectionException
386
-     */
387
-    protected function _all_tickets_sold()
388
-    {
389
-        $datetimes    = $this->get_many_related('Datetime');
390
-        $tickets_sold = [];
391
-        if (! empty($datetimes)) {
392
-            foreach ($datetimes as $datetime) {
393
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
394
-            }
395
-        }
396
-        // Tickets sold
397
-        $tickets_sold['ticket'] = $this->sold();
398
-        return $tickets_sold;
399
-    }
400
-
401
-
402
-    /**
403
-     * This returns the base price object for the ticket.
404
-     *
405
-     * @param bool $return_array whether to return as an array indexed by price id or just the object.
406
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
407
-     * @throws EE_Error
408
-     * @throws ReflectionException
409
-     */
410
-    public function base_price($return_array = false)
411
-    {
412
-        $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
413
-        return $return_array
414
-            ? $this->get_many_related('Price', [$_where])
415
-            : $this->get_first_related('Price', [$_where]);
416
-    }
417
-
418
-
419
-    /**
420
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
421
-     *
422
-     * @access public
423
-     * @return EE_Price[]
424
-     * @throws EE_Error
425
-     * @throws ReflectionException
426
-     */
427
-    public function price_modifiers()
428
-    {
429
-        $query_params = [
430
-            0 => [
431
-                'Price_Type.PBT_ID' => [
432
-                    'NOT IN',
433
-                    [EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
434
-                ],
435
-            ],
436
-        ];
437
-        return $this->prices($query_params);
438
-    }
439
-
440
-
441
-    /**
442
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
443
-     *
444
-     * @access public
445
-     * @return EE_Price[]
446
-     * @throws EE_Error
447
-     * @throws ReflectionException
448
-     */
449
-    public function tax_price_modifiers()
450
-    {
451
-        $query_params = [
452
-            0 => [
453
-                'Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax,
454
-            ],
455
-        ];
456
-        return $this->prices($query_params);
457
-    }
458
-
459
-
460
-    /**
461
-     * Gets all the prices that combine to form the final price of this ticket
462
-     *
463
-     * @param array $query_params @see
464
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
465
-     * @return EE_Price[]|EE_Base_Class[]
466
-     * @throws EE_Error
467
-     * @throws ReflectionException
468
-     */
469
-    public function prices($query_params = [])
470
-    {
471
-        return $this->get_many_related('Price', $query_params);
472
-    }
473
-
474
-
475
-    /**
476
-     * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
477
-     *
478
-     * @param array $query_params @see
479
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
480
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
481
-     * @throws EE_Error
482
-     * @throws ReflectionException
483
-     */
484
-    public function datetime_tickets($query_params = [])
485
-    {
486
-        return $this->get_many_related('Datetime_Ticket', $query_params);
487
-    }
488
-
489
-
490
-    /**
491
-     * Gets all the datetimes from the db ordered by DTT_order
492
-     *
493
-     * @param boolean $show_expired
494
-     * @param boolean $show_deleted
495
-     * @return EE_Datetime[]
496
-     * @throws EE_Error
497
-     * @throws ReflectionException
498
-     */
499
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
500
-    {
501
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
502
-            $this->ID(),
503
-            $show_expired,
504
-            $show_deleted
505
-        );
506
-    }
507
-
508
-
509
-    /**
510
-     * Gets ID
511
-     *
512
-     * @return string
513
-     * @throws EE_Error
514
-     * @throws ReflectionException
515
-     */
516
-    public function ID()
517
-    {
518
-        return $this->get('TKT_ID');
519
-    }
520
-
521
-
522
-    /**
523
-     * get the author of the ticket.
524
-     *
525
-     * @return int
526
-     * @throws EE_Error
527
-     * @throws ReflectionException
528
-     * @since 4.5.0
529
-     */
530
-    public function wp_user()
531
-    {
532
-        return $this->get('TKT_wp_user');
533
-    }
534
-
535
-
536
-    /**
537
-     * Gets the template for the ticket
538
-     *
539
-     * @return EE_Ticket_Template|EE_Base_Class
540
-     * @throws EE_Error
541
-     * @throws ReflectionException
542
-     */
543
-    public function template()
544
-    {
545
-        return $this->get_first_related('Ticket_Template');
546
-    }
547
-
548
-
549
-    /**
550
-     * Simply returns an array of EE_Price objects that are taxes.
551
-     *
552
-     * @return EE_Price[]
553
-     * @throws EE_Error
554
-     */
555
-    public function get_ticket_taxes_for_admin()
556
-    {
557
-        return EE_Taxes::get_taxes_for_admin();
558
-    }
559
-
560
-
561
-    /**
562
-     * @return float
563
-     * @throws EE_Error
564
-     * @throws ReflectionException
565
-     */
566
-    public function ticket_price()
567
-    {
568
-        return $this->get('TKT_price');
569
-    }
570
-
571
-
572
-    /**
573
-     * @return mixed
574
-     * @throws EE_Error
575
-     * @throws ReflectionException
576
-     */
577
-    public function pretty_price()
578
-    {
579
-        return $this->get_pretty('TKT_price');
580
-    }
581
-
582
-
583
-    /**
584
-     * @return bool
585
-     * @throws EE_Error
586
-     * @throws ReflectionException
587
-     */
588
-    public function is_free()
589
-    {
590
-        return $this->get_ticket_total_with_taxes() === (float) 0;
591
-    }
592
-
593
-
594
-    /**
595
-     * get_ticket_total_with_taxes
596
-     *
597
-     * @param bool $no_cache
598
-     * @return float
599
-     * @throws EE_Error
600
-     * @throws ReflectionException
601
-     */
602
-    public function get_ticket_total_with_taxes($no_cache = false)
603
-    {
604
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
605
-            $this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
606
-        }
607
-        return (float) $this->_ticket_total_with_taxes;
608
-    }
609
-
610
-
611
-    /**
612
-     * @throws EE_Error
613
-     * @throws ReflectionException
614
-     */
615
-    public function ensure_TKT_Price_correct()
616
-    {
617
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
618
-        $this->save();
619
-    }
620
-
621
-
622
-    /**
623
-     * @return float
624
-     * @throws EE_Error
625
-     * @throws ReflectionException
626
-     */
627
-    public function get_ticket_subtotal()
628
-    {
629
-        return EE_Taxes::get_subtotal_for_admin($this);
630
-    }
631
-
632
-
633
-    /**
634
-     * Returns the total taxes applied to this ticket
635
-     *
636
-     * @return float
637
-     * @throws EE_Error
638
-     * @throws ReflectionException
639
-     */
640
-    public function get_ticket_taxes_total_for_admin()
641
-    {
642
-        return EE_Taxes::get_total_taxes_for_admin($this);
643
-    }
644
-
645
-
646
-    /**
647
-     * Sets name
648
-     *
649
-     * @param string $name
650
-     * @throws EE_Error
651
-     * @throws ReflectionException
652
-     */
653
-    public function set_name($name)
654
-    {
655
-        $this->set('TKT_name', $name);
656
-    }
657
-
658
-
659
-    /**
660
-     * Gets description
661
-     *
662
-     * @return string
663
-     * @throws EE_Error
664
-     * @throws ReflectionException
665
-     */
666
-    public function description()
667
-    {
668
-        return $this->get('TKT_description');
669
-    }
670
-
671
-
672
-    /**
673
-     * Sets description
674
-     *
675
-     * @param string $description
676
-     * @throws EE_Error
677
-     * @throws ReflectionException
678
-     */
679
-    public function set_description($description)
680
-    {
681
-        $this->set('TKT_description', $description);
682
-    }
683
-
684
-
685
-    /**
686
-     * Gets start_date
687
-     *
688
-     * @param string $date_format
689
-     * @param string $time_format
690
-     * @return string
691
-     * @throws EE_Error
692
-     * @throws ReflectionException
693
-     */
694
-    public function start_date($date_format = '', $time_format = '')
695
-    {
696
-        return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
697
-    }
698
-
699
-
700
-    /**
701
-     * Sets start_date
702
-     *
703
-     * @param string $start_date
704
-     * @return void
705
-     * @throws EE_Error
706
-     * @throws ReflectionException
707
-     */
708
-    public function set_start_date($start_date)
709
-    {
710
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
711
-    }
712
-
713
-
714
-    /**
715
-     * Gets end_date
716
-     *
717
-     * @param string $date_format
718
-     * @param string $time_format
719
-     * @return string
720
-     * @throws EE_Error
721
-     * @throws ReflectionException
722
-     */
723
-    public function end_date($date_format = '', $time_format = '')
724
-    {
725
-        return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
726
-    }
727
-
728
-
729
-    /**
730
-     * Sets end_date
731
-     *
732
-     * @param string $end_date
733
-     * @return void
734
-     * @throws EE_Error
735
-     * @throws ReflectionException
736
-     */
737
-    public function set_end_date($end_date)
738
-    {
739
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
740
-    }
741
-
742
-
743
-    /**
744
-     * Sets sell until time
745
-     *
746
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
747
-     * @throws EE_Error
748
-     * @throws ReflectionException
749
-     * @since 4.5.0
750
-     */
751
-    public function set_end_time($time)
752
-    {
753
-        $this->_set_time_for($time, 'TKT_end_date');
754
-    }
755
-
756
-
757
-    /**
758
-     * Sets min
759
-     *
760
-     * @param int $min
761
-     * @return void
762
-     * @throws EE_Error
763
-     * @throws ReflectionException
764
-     */
765
-    public function set_min($min)
766
-    {
767
-        $this->set('TKT_min', $min);
768
-    }
769
-
770
-
771
-    /**
772
-     * Gets max
773
-     *
774
-     * @return int
775
-     * @throws EE_Error
776
-     * @throws ReflectionException
777
-     */
778
-    public function max()
779
-    {
780
-        return $this->get('TKT_max');
781
-    }
782
-
783
-
784
-    /**
785
-     * Sets max
786
-     *
787
-     * @param int $max
788
-     * @return void
789
-     * @throws EE_Error
790
-     * @throws ReflectionException
791
-     */
792
-    public function set_max($max)
793
-    {
794
-        $this->set('TKT_max', $max);
795
-    }
796
-
797
-
798
-    /**
799
-     * Sets price
800
-     *
801
-     * @param float $price
802
-     * @return void
803
-     * @throws EE_Error
804
-     * @throws ReflectionException
805
-     */
806
-    public function set_price($price)
807
-    {
808
-        $this->set('TKT_price', $price);
809
-    }
810
-
811
-
812
-    /**
813
-     * Gets sold
814
-     *
815
-     * @return int
816
-     * @throws EE_Error
817
-     * @throws ReflectionException
818
-     */
819
-    public function sold()
820
-    {
821
-        return $this->get_raw('TKT_sold');
822
-    }
823
-
824
-
825
-    /**
826
-     * Sets sold
827
-     *
828
-     * @param int $sold
829
-     * @return void
830
-     * @throws EE_Error
831
-     * @throws ReflectionException
832
-     */
833
-    public function set_sold($sold)
834
-    {
835
-        // sold can not go below zero
836
-        $sold = max(0, $sold);
837
-        $this->set('TKT_sold', $sold);
838
-    }
839
-
840
-
841
-    /**
842
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
843
-     * associated datetimes.
844
-     *
845
-     * @param int $qty
846
-     * @return boolean
847
-     * @throws EE_Error
848
-     * @throws InvalidArgumentException
849
-     * @throws InvalidDataTypeException
850
-     * @throws InvalidInterfaceException
851
-     * @throws ReflectionException
852
-     * @since 4.9.80.p
853
-     */
854
-    public function increaseSold($qty = 1)
855
-    {
856
-        $qty = absint($qty);
857
-        // increment sold and decrement reserved datetime quantities simultaneously
858
-        // don't worry about failures, because they must have already had a spot reserved
859
-        $this->increaseSoldForDatetimes($qty);
860
-        // Increment and decrement ticket quantities simultaneously
861
-        $success = $this->adjustNumericFieldsInDb(
862
-            [
863
-                'TKT_reserved' => $qty * -1,
864
-                'TKT_sold'     => $qty,
865
-            ]
866
-        );
867
-        do_action(
868
-            'AHEE__EE_Ticket__increase_sold',
869
-            $this,
870
-            $qty,
871
-            $this->sold(),
872
-            $success
873
-        );
874
-        return $success;
875
-    }
876
-
877
-
878
-    /**
879
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
880
-     *
881
-     * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
882
-     *                           counts), Negative means to decreases old counts (and increase reserved counts).
883
-     * @param EE_Datetime[] $datetimes
884
-     * @throws EE_Error
885
-     * @throws InvalidArgumentException
886
-     * @throws InvalidDataTypeException
887
-     * @throws InvalidInterfaceException
888
-     * @throws ReflectionException
889
-     * @since 4.9.80.p
890
-     */
891
-    protected function increaseSoldForDatetimes($qty, array $datetimes = [])
892
-    {
893
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
894
-        foreach ($datetimes as $datetime) {
895
-            $datetime->increaseSold($qty);
896
-        }
897
-    }
898
-
899
-
900
-    /**
901
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
902
-     * DB and then updates the model objects.
903
-     * Does not affect the reserved counts.
904
-     *
905
-     * @param int $qty
906
-     * @return boolean
907
-     * @throws EE_Error
908
-     * @throws InvalidArgumentException
909
-     * @throws InvalidDataTypeException
910
-     * @throws InvalidInterfaceException
911
-     * @throws ReflectionException
912
-     * @since 4.9.80.p
913
-     */
914
-    public function decreaseSold($qty = 1)
915
-    {
916
-        $qty = absint($qty);
917
-        $this->decreaseSoldForDatetimes($qty);
918
-        $success = $this->adjustNumericFieldsInDb(
919
-            [
920
-                'TKT_sold' => $qty * -1,
921
-            ]
922
-        );
923
-        do_action(
924
-            'AHEE__EE_Ticket__decrease_sold',
925
-            $this,
926
-            $qty,
927
-            $this->sold(),
928
-            $success
929
-        );
930
-        return $success;
931
-    }
932
-
933
-
934
-    /**
935
-     * Decreases sold on related datetimes
936
-     *
937
-     * @param int           $qty
938
-     * @param EE_Datetime[] $datetimes
939
-     * @return void
940
-     * @throws EE_Error
941
-     * @throws InvalidArgumentException
942
-     * @throws InvalidDataTypeException
943
-     * @throws InvalidInterfaceException
944
-     * @throws ReflectionException
945
-     * @since 4.9.80.p
946
-     */
947
-    protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
948
-    {
949
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
950
-        if (is_array($datetimes)) {
951
-            foreach ($datetimes as $datetime) {
952
-                if ($datetime instanceof EE_Datetime) {
953
-                    $datetime->decreaseSold($qty);
954
-                }
955
-            }
956
-        }
957
-    }
958
-
959
-
960
-    /**
961
-     * Gets qty of reserved tickets
962
-     *
963
-     * @return int
964
-     * @throws EE_Error
965
-     * @throws ReflectionException
966
-     */
967
-    public function reserved()
968
-    {
969
-        return $this->get_raw('TKT_reserved');
970
-    }
971
-
972
-
973
-    /**
974
-     * Sets reserved
975
-     *
976
-     * @param int $reserved
977
-     * @return void
978
-     * @throws EE_Error
979
-     * @throws ReflectionException
980
-     */
981
-    public function set_reserved($reserved)
982
-    {
983
-        // reserved can not go below zero
984
-        $reserved = max(0, (int) $reserved);
985
-        $this->set('TKT_reserved', $reserved);
986
-    }
987
-
988
-
989
-    /**
990
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
991
-     *
992
-     * @param int    $qty
993
-     * @param string $source
994
-     * @return bool whether we successfully reserved the ticket or not.
995
-     * @throws EE_Error
996
-     * @throws InvalidArgumentException
997
-     * @throws ReflectionException
998
-     * @throws InvalidDataTypeException
999
-     * @throws InvalidInterfaceException
1000
-     * @since 4.9.80.p
1001
-     */
1002
-    public function increaseReserved($qty = 1, $source = 'unknown')
1003
-    {
1004
-        $qty = absint($qty);
1005
-        do_action(
1006
-            'AHEE__EE_Ticket__increase_reserved__begin',
1007
-            $this,
1008
-            $qty,
1009
-            $source
1010
-        );
1011
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1012
-        $success                         = false;
1013
-        $datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1014
-        if ($datetimes_adjusted_successfully) {
1015
-            $success = $this->incrementFieldConditionallyInDb(
1016
-                'TKT_reserved',
1017
-                'TKT_sold',
1018
-                'TKT_qty',
1019
-                $qty
1020
-            );
1021
-            if (! $success) {
1022
-                // The datetimes were successfully bumped, but not the
1023
-                // ticket. So we need to manually rollback the datetimes.
1024
-                $this->decreaseReservedForDatetimes($qty);
1025
-            }
1026
-        }
1027
-        do_action(
1028
-            'AHEE__EE_Ticket__increase_reserved',
1029
-            $this,
1030
-            $qty,
1031
-            $this->reserved(),
1032
-            $success
1033
-        );
1034
-        return $success;
1035
-    }
1036
-
1037
-
1038
-    /**
1039
-     * Increases reserved counts on related datetimes
1040
-     *
1041
-     * @param int           $qty
1042
-     * @param EE_Datetime[] $datetimes
1043
-     * @return boolean indicating success
1044
-     * @throws EE_Error
1045
-     * @throws InvalidArgumentException
1046
-     * @throws InvalidDataTypeException
1047
-     * @throws InvalidInterfaceException
1048
-     * @throws ReflectionException
1049
-     * @since 4.9.80.p
1050
-     */
1051
-    protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1052
-    {
1053
-        $datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1054
-        $datetimes_updated = [];
1055
-        $limit_exceeded    = false;
1056
-        if (is_array($datetimes)) {
1057
-            foreach ($datetimes as $datetime) {
1058
-                if ($datetime instanceof EE_Datetime) {
1059
-                    if ($datetime->increaseReserved($qty)) {
1060
-                        $datetimes_updated[] = $datetime;
1061
-                    } else {
1062
-                        $limit_exceeded = true;
1063
-                        break;
1064
-                    }
1065
-                }
1066
-            }
1067
-            // If somewhere along the way we detected a datetime whose
1068
-            // limit was exceeded, do a manual rollback.
1069
-            if ($limit_exceeded) {
1070
-                $this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1071
-                return false;
1072
-            }
1073
-        }
1074
-        return true;
1075
-    }
1076
-
1077
-
1078
-    /**
1079
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1080
-     *
1081
-     * @param int    $qty
1082
-     * @param bool   $adjust_datetimes
1083
-     * @param string $source
1084
-     * @return boolean
1085
-     * @throws EE_Error
1086
-     * @throws InvalidArgumentException
1087
-     * @throws ReflectionException
1088
-     * @throws InvalidDataTypeException
1089
-     * @throws InvalidInterfaceException
1090
-     * @since 4.9.80.p
1091
-     */
1092
-    public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1093
-    {
1094
-        $qty = absint($qty);
1095
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1096
-        if ($adjust_datetimes) {
1097
-            $this->decreaseReservedForDatetimes($qty);
1098
-        }
1099
-        $success = $this->adjustNumericFieldsInDb(
1100
-            [
1101
-                'TKT_reserved' => $qty * -1,
1102
-            ]
1103
-        );
1104
-        do_action(
1105
-            'AHEE__EE_Ticket__decrease_reserved',
1106
-            $this,
1107
-            $qty,
1108
-            $this->reserved(),
1109
-            $success
1110
-        );
1111
-        return $success;
1112
-    }
1113
-
1114
-
1115
-    /**
1116
-     * Decreases the reserved count on the specified datetimes.
1117
-     *
1118
-     * @param int           $qty
1119
-     * @param EE_Datetime[] $datetimes
1120
-     * @throws EE_Error
1121
-     * @throws InvalidArgumentException
1122
-     * @throws ReflectionException
1123
-     * @throws InvalidDataTypeException
1124
-     * @throws InvalidInterfaceException
1125
-     * @since 4.9.80.p
1126
-     */
1127
-    protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1128
-    {
1129
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1130
-        foreach ($datetimes as $datetime) {
1131
-            if ($datetime instanceof EE_Datetime) {
1132
-                $datetime->decreaseReserved($qty);
1133
-            }
1134
-        }
1135
-    }
1136
-
1137
-
1138
-    /**
1139
-     * Gets ticket quantity
1140
-     *
1141
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1142
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1143
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1144
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1145
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1146
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1147
-     * @return int
1148
-     * @throws EE_Error
1149
-     * @throws ReflectionException
1150
-     */
1151
-    public function qty($context = '')
1152
-    {
1153
-        switch ($context) {
1154
-            case 'reg_limit':
1155
-                return $this->real_quantity_on_ticket();
1156
-            case 'saleable':
1157
-                return $this->real_quantity_on_ticket('saleable');
1158
-            default:
1159
-                return $this->get_raw('TKT_qty');
1160
-        }
1161
-    }
1162
-
1163
-
1164
-    /**
1165
-     * Gets ticket quantity
1166
-     *
1167
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1168
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1169
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1170
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1171
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1172
-     * @param int    $DTT_ID      the primary key for a particular datetime.
1173
-     *                            set to 0 for all related datetimes
1174
-     * @return int
1175
-     * @throws EE_Error
1176
-     * @throws ReflectionException
1177
-     */
1178
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1179
-    {
1180
-        $raw = $this->get_raw('TKT_qty');
1181
-        // return immediately if it's zero
1182
-        if ($raw === 0) {
1183
-            return $raw;
1184
-        }
1185
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1186
-        // ensure qty doesn't exceed raw value for THIS ticket
1187
-        $qty = min(EE_INF, $raw);
1188
-        // echo "\n . qty: " . $qty . '<br />';
1189
-        // calculate this ticket's total sales and reservations
1190
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1191
-        // echo "\n . sold: " . $this->sold() . '<br />';
1192
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1193
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1194
-        // first we need to calculate the maximum number of tickets available for the datetime
1195
-        // do we want data for one datetime or all of them ?
1196
-        $query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1197
-        $datetimes    = $this->datetimes($query_params);
1198
-        if (is_array($datetimes) && ! empty($datetimes)) {
1199
-            foreach ($datetimes as $datetime) {
1200
-                if ($datetime instanceof EE_Datetime) {
1201
-                    $datetime->refresh_from_db();
1202
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1203
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1204
-                    // initialize with no restrictions for each datetime
1205
-                    // but adjust datetime qty based on datetime reg limit
1206
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1207
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1208
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1209
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1210
-                    // and reservations for this datetime, that do NOT include sales and reservations
1211
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1212
-                    if ($context === 'saleable') {
1213
-                        $datetime_qty = max(
1214
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1215
-                            0
1216
-                        );
1217
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1218
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1219
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1220
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1221
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1222
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1223
-                    }
1224
-                    $qty = min($datetime_qty, $qty);
1225
-                    // echo "\n . . qty: " . $qty . '<br />';
1226
-                }
1227
-            }
1228
-        }
1229
-        // NOW that we know the  maximum number of tickets available for the datetime
1230
-        // we can finally factor in the details for this specific ticket
1231
-        if ($qty > 0 && $context === 'saleable') {
1232
-            // and subtract the sales for THIS ticket
1233
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1234
-            // echo "\n . qty: " . $qty . '<br />';
1235
-        }
1236
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1237
-        return $qty;
1238
-    }
1239
-
1240
-
1241
-    /**
1242
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1243
-     *
1244
-     * @param int $qty
1245
-     * @return void
1246
-     * @throws EE_Error
1247
-     * @throws ReflectionException
1248
-     */
1249
-    public function set_qty($qty)
1250
-    {
1251
-        $datetimes = $this->datetimes();
1252
-        foreach ($datetimes as $datetime) {
1253
-            if ($datetime instanceof EE_Datetime) {
1254
-                $qty = min($qty, $datetime->reg_limit());
1255
-            }
1256
-        }
1257
-        $this->set('TKT_qty', $qty);
1258
-    }
1259
-
1260
-
1261
-    /**
1262
-     * Gets uses
1263
-     *
1264
-     * @return int
1265
-     * @throws EE_Error
1266
-     * @throws ReflectionException
1267
-     */
1268
-    public function uses()
1269
-    {
1270
-        return $this->get('TKT_uses');
1271
-    }
1272
-
1273
-
1274
-    /**
1275
-     * Sets uses
1276
-     *
1277
-     * @param int $uses
1278
-     * @return void
1279
-     * @throws EE_Error
1280
-     * @throws ReflectionException
1281
-     */
1282
-    public function set_uses($uses)
1283
-    {
1284
-        $this->set('TKT_uses', $uses);
1285
-    }
1286
-
1287
-
1288
-    /**
1289
-     * returns whether ticket is required or not.
1290
-     *
1291
-     * @return boolean
1292
-     * @throws EE_Error
1293
-     * @throws ReflectionException
1294
-     */
1295
-    public function required()
1296
-    {
1297
-        return $this->get('TKT_required');
1298
-    }
1299
-
1300
-
1301
-    /**
1302
-     * sets the TKT_required property
1303
-     *
1304
-     * @param boolean $required
1305
-     * @return void
1306
-     * @throws EE_Error
1307
-     * @throws ReflectionException
1308
-     */
1309
-    public function set_required($required)
1310
-    {
1311
-        $this->set('TKT_required', $required);
1312
-    }
1313
-
1314
-
1315
-    /**
1316
-     * Gets taxable
1317
-     *
1318
-     * @return boolean
1319
-     * @throws EE_Error
1320
-     * @throws ReflectionException
1321
-     */
1322
-    public function taxable()
1323
-    {
1324
-        return $this->get('TKT_taxable');
1325
-    }
1326
-
1327
-
1328
-    /**
1329
-     * Sets taxable
1330
-     *
1331
-     * @param boolean $taxable
1332
-     * @return void
1333
-     * @throws EE_Error
1334
-     * @throws ReflectionException
1335
-     */
1336
-    public function set_taxable($taxable)
1337
-    {
1338
-        $this->set('TKT_taxable', $taxable);
1339
-    }
1340
-
1341
-
1342
-    /**
1343
-     * Gets is_default
1344
-     *
1345
-     * @return boolean
1346
-     * @throws EE_Error
1347
-     * @throws ReflectionException
1348
-     */
1349
-    public function is_default()
1350
-    {
1351
-        return $this->get('TKT_is_default');
1352
-    }
1353
-
1354
-
1355
-    /**
1356
-     * Sets is_default
1357
-     *
1358
-     * @param boolean $is_default
1359
-     * @return void
1360
-     * @throws EE_Error
1361
-     * @throws ReflectionException
1362
-     */
1363
-    public function set_is_default($is_default)
1364
-    {
1365
-        $this->set('TKT_is_default', $is_default);
1366
-    }
1367
-
1368
-
1369
-    /**
1370
-     * Gets order
1371
-     *
1372
-     * @return int
1373
-     * @throws EE_Error
1374
-     * @throws ReflectionException
1375
-     */
1376
-    public function order()
1377
-    {
1378
-        return $this->get('TKT_order');
1379
-    }
1380
-
1381
-
1382
-    /**
1383
-     * Sets order
1384
-     *
1385
-     * @param int $order
1386
-     * @return void
1387
-     * @throws EE_Error
1388
-     * @throws ReflectionException
1389
-     */
1390
-    public function set_order($order)
1391
-    {
1392
-        $this->set('TKT_order', $order);
1393
-    }
1394
-
1395
-
1396
-    /**
1397
-     * Gets row
1398
-     *
1399
-     * @return int
1400
-     * @throws EE_Error
1401
-     * @throws ReflectionException
1402
-     */
1403
-    public function row()
1404
-    {
1405
-        return $this->get('TKT_row');
1406
-    }
1407
-
1408
-
1409
-    /**
1410
-     * Sets row
1411
-     *
1412
-     * @param int $row
1413
-     * @return void
1414
-     * @throws EE_Error
1415
-     * @throws ReflectionException
1416
-     */
1417
-    public function set_row($row)
1418
-    {
1419
-        $this->set('TKT_row', $row);
1420
-    }
1421
-
1422
-
1423
-    /**
1424
-     * Gets deleted
1425
-     *
1426
-     * @return boolean
1427
-     * @throws EE_Error
1428
-     * @throws ReflectionException
1429
-     */
1430
-    public function deleted()
1431
-    {
1432
-        return $this->get('TKT_deleted');
1433
-    }
1434
-
1435
-
1436
-    /**
1437
-     * Sets deleted
1438
-     *
1439
-     * @param boolean $deleted
1440
-     * @return void
1441
-     * @throws EE_Error
1442
-     * @throws ReflectionException
1443
-     */
1444
-    public function set_deleted($deleted)
1445
-    {
1446
-        $this->set('TKT_deleted', $deleted);
1447
-    }
1448
-
1449
-
1450
-    /**
1451
-     * Gets parent
1452
-     *
1453
-     * @return int
1454
-     * @throws EE_Error
1455
-     * @throws ReflectionException
1456
-     */
1457
-    public function parent_ID()
1458
-    {
1459
-        return $this->get('TKT_parent');
1460
-    }
1461
-
1462
-
1463
-    /**
1464
-     * Sets parent
1465
-     *
1466
-     * @param int $parent
1467
-     * @return void
1468
-     * @throws EE_Error
1469
-     * @throws ReflectionException
1470
-     */
1471
-    public function set_parent_ID($parent)
1472
-    {
1473
-        $this->set('TKT_parent', $parent);
1474
-    }
1475
-
1476
-
1477
-    /**
1478
-     * @return boolean
1479
-     * @throws EE_Error
1480
-     * @throws InvalidArgumentException
1481
-     * @throws InvalidDataTypeException
1482
-     * @throws InvalidInterfaceException
1483
-     * @throws ReflectionException
1484
-     */
1485
-    public function reverse_calculate()
1486
-    {
1487
-        return $this->get('TKT_reverse_calculate');
1488
-    }
1489
-
1490
-
1491
-    /**
1492
-     * @param boolean $reverse_calculate
1493
-     * @throws EE_Error
1494
-     * @throws InvalidArgumentException
1495
-     * @throws InvalidDataTypeException
1496
-     * @throws InvalidInterfaceException
1497
-     * @throws ReflectionException
1498
-     */
1499
-    public function set_reverse_calculate($reverse_calculate)
1500
-    {
1501
-        $this->set('TKT_reverse_calculate', $reverse_calculate);
1502
-    }
1503
-
1504
-
1505
-    /**
1506
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1507
-     *
1508
-     * @return string
1509
-     * @throws EE_Error
1510
-     * @throws ReflectionException
1511
-     */
1512
-    public function name_and_info()
1513
-    {
1514
-        $times = [];
1515
-        foreach ($this->datetimes() as $datetime) {
1516
-            $times[] = $datetime->start_date_and_time();
1517
-        }
1518
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1519
-    }
1520
-
1521
-
1522
-    /**
1523
-     * Gets name
1524
-     *
1525
-     * @return string
1526
-     * @throws EE_Error
1527
-     * @throws ReflectionException
1528
-     */
1529
-    public function name()
1530
-    {
1531
-        return $this->get('TKT_name');
1532
-    }
1533
-
1534
-
1535
-    /**
1536
-     * Gets price
1537
-     *
1538
-     * @return float
1539
-     * @throws EE_Error
1540
-     * @throws ReflectionException
1541
-     */
1542
-    public function price()
1543
-    {
1544
-        return $this->get('TKT_price');
1545
-    }
1546
-
1547
-
1548
-    /**
1549
-     * Gets all the registrations for this ticket
1550
-     *
1551
-     * @param array $query_params @see
1552
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1553
-     * @return EE_Registration[]|EE_Base_Class[]
1554
-     * @throws EE_Error
1555
-     * @throws ReflectionException
1556
-     */
1557
-    public function registrations($query_params = [])
1558
-    {
1559
-        return $this->get_many_related('Registration', $query_params);
1560
-    }
1561
-
1562
-
1563
-    /**
1564
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1565
-     *
1566
-     * @return int
1567
-     * @throws EE_Error
1568
-     * @throws ReflectionException
1569
-     */
1570
-    public function update_tickets_sold()
1571
-    {
1572
-        $count_regs_for_this_ticket = $this->count_registrations(
1573
-            [
1574
-                [
1575
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1576
-                    'REG_deleted' => 0,
1577
-                ],
1578
-            ]
1579
-        );
1580
-        $this->set_sold($count_regs_for_this_ticket);
1581
-        $this->save();
1582
-        return $count_regs_for_this_ticket;
1583
-    }
1584
-
1585
-
1586
-    /**
1587
-     * Counts the registrations for this ticket
1588
-     *
1589
-     * @param array $query_params @see
1590
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1591
-     * @return int
1592
-     * @throws EE_Error
1593
-     * @throws ReflectionException
1594
-     */
1595
-    public function count_registrations($query_params = [])
1596
-    {
1597
-        return $this->count_related('Registration', $query_params);
1598
-    }
1599
-
1600
-
1601
-    /**
1602
-     * Implementation for EEI_Has_Icon interface method.
1603
-     *
1604
-     * @return string
1605
-     * @see EEI_Visual_Representation for comments
1606
-     */
1607
-    public function get_icon()
1608
-    {
1609
-        return '<span class="dashicons dashicons-tickets-alt"/>';
1610
-    }
1611
-
1612
-
1613
-    /**
1614
-     * Implementation of the EEI_Event_Relation interface method
1615
-     *
1616
-     * @return EE_Event
1617
-     * @throws EE_Error
1618
-     * @throws UnexpectedEntityException
1619
-     * @throws ReflectionException
1620
-     * @see EEI_Event_Relation for comments
1621
-     */
1622
-    public function get_related_event()
1623
-    {
1624
-        // get one datetime to use for getting the event
1625
-        $datetime = $this->first_datetime();
1626
-        if (! $datetime instanceof EE_Datetime) {
1627
-            throw new UnexpectedEntityException(
1628
-                $datetime,
1629
-                'EE_Datetime',
1630
-                sprintf(
1631
-                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1632
-                    $this->name()
1633
-                )
1634
-            );
1635
-        }
1636
-        $event = $datetime->event();
1637
-        if (! $event instanceof EE_Event) {
1638
-            throw new UnexpectedEntityException(
1639
-                $event,
1640
-                'EE_Event',
1641
-                sprintf(
1642
-                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1643
-                    $this->name()
1644
-                )
1645
-            );
1646
-        }
1647
-        return $event;
1648
-    }
1649
-
1650
-
1651
-    /**
1652
-     * Implementation of the EEI_Event_Relation interface method
1653
-     *
1654
-     * @return string
1655
-     * @throws UnexpectedEntityException
1656
-     * @throws EE_Error
1657
-     * @throws ReflectionException
1658
-     * @see EEI_Event_Relation for comments
1659
-     */
1660
-    public function get_event_name()
1661
-    {
1662
-        $event = $this->get_related_event();
1663
-        return $event instanceof EE_Event ? $event->name() : '';
1664
-    }
1665
-
1666
-
1667
-    /**
1668
-     * Implementation of the EEI_Event_Relation interface method
1669
-     *
1670
-     * @return int
1671
-     * @throws UnexpectedEntityException
1672
-     * @throws EE_Error
1673
-     * @throws ReflectionException
1674
-     * @see EEI_Event_Relation for comments
1675
-     */
1676
-    public function get_event_ID()
1677
-    {
1678
-        $event = $this->get_related_event();
1679
-        return $event instanceof EE_Event ? $event->ID() : 0;
1680
-    }
1681
-
1682
-
1683
-    /**
1684
-     * This simply returns whether a ticket can be permanently deleted or not.
1685
-     * The criteria for determining this is whether the ticket has any related registrations.
1686
-     * If there are none then it can be permanently deleted.
1687
-     *
1688
-     * @return bool
1689
-     * @throws EE_Error
1690
-     * @throws ReflectionException
1691
-     */
1692
-    public function is_permanently_deleteable()
1693
-    {
1694
-        return $this->count_registrations() === 0;
1695
-    }
1696
-
1697
-
1698
-    /**
1699
-     * @return int
1700
-     * @throws EE_Error
1701
-     * @throws ReflectionException
1702
-     * @since   $VID:$
1703
-     */
1704
-    public function visibility(): int
1705
-    {
1706
-        return $this->get('TKT_visibility');
1707
-    }
1708
-
1709
-
1710
-    /**
1711
-     * @return int
1712
-     * @throws EE_Error
1713
-     * @throws ReflectionException
1714
-     * @since   $VID:$
1715
-     */
1716
-    public function isHidden(): int
1717
-    {
1718
-        return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1719
-    }
1720
-
1721
-
1722
-    /**
1723
-     * @return int
1724
-     * @throws EE_Error
1725
-     * @throws ReflectionException
1726
-     * @since   $VID:$
1727
-     */
1728
-    public function isNotHidden(): int
1729
-    {
1730
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1731
-    }
1732
-
1733
-
1734
-    /**
1735
-     * @return int
1736
-     * @throws EE_Error
1737
-     * @throws ReflectionException
1738
-     * @since   $VID:$
1739
-     */
1740
-    public function isPublicOnly(): int
1741
-    {
1742
-        return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1743
-    }
1744
-
1745
-
1746
-    /**
1747
-     * @return int
1748
-     * @throws EE_Error
1749
-     * @throws ReflectionException
1750
-     * @since   $VID:$
1751
-     */
1752
-    public function isMembersOnly(): int
1753
-    {
1754
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1755
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1756
-    }
1757
-
1758
-
1759
-    /**
1760
-     * @return int
1761
-     * @throws EE_Error
1762
-     * @throws ReflectionException
1763
-     * @since   $VID:$
1764
-     */
1765
-    public function isAdminsOnly(): int
1766
-    {
1767
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1768
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1769
-    }
1770
-
1771
-
1772
-    /**
1773
-     * @return int
1774
-     * @throws EE_Error
1775
-     * @throws ReflectionException
1776
-     * @since   $VID:$
1777
-     */
1778
-    public function isAdminUiOnly(): int
1779
-    {
1780
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1781
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1782
-    }
1783
-
1784
-
1785
-    /**
1786
-     * @param int $visibility
1787
-     * @throws EE_Error
1788
-     * @throws ReflectionException
1789
-     * @since   $VID:$
1790
-     */
1791
-    public function set_visibility(int $visibility)
1792
-    {
1793
-
1794
-        $ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1795
-        $ticket_visibility         = -1;
1796
-        foreach ($ticket_visibility_options as $ticket_visibility_option) {
1797
-            if ($visibility === $ticket_visibility_option) {
1798
-                $ticket_visibility = $visibility;
1799
-            }
1800
-        }
1801
-        if ($ticket_visibility === -1) {
1802
-            throw new DomainException(
1803
-                sprintf(
1804
-                    esc_html__(
1805
-                        'The supplied ticket visibility setting of "%1$s" is not valid. It needs to match one of the keys in the following array:%2$s %3$s ',
1806
-                        'event_espresso'
1807
-                    ),
1808
-                    $visibility,
1809
-                    '<br />',
1810
-                    var_export($ticket_visibility_options, true)
1811
-                )
1812
-            );
1813
-        }
1814
-        $this->set('TKT_visibility', $ticket_visibility);
1815
-    }
1816
-
1817
-
1818
-    /*******************************************************************
17
+	/**
18
+	 * TicKet Sold out:
19
+	 * constant used by ticket_status() to indicate that a ticket is sold out
20
+	 * and no longer available for purchases
21
+	 */
22
+	const sold_out = 'TKS';
23
+
24
+	/**
25
+	 * TicKet Expired:
26
+	 * constant used by ticket_status() to indicate that a ticket is expired
27
+	 * and no longer available for purchase
28
+	 */
29
+	const expired = 'TKE';
30
+
31
+	/**
32
+	 * TicKet Archived:
33
+	 * constant used by ticket_status() to indicate that a ticket is archived
34
+	 * and no longer available for purchase
35
+	 */
36
+	const archived = 'TKA';
37
+
38
+	/**
39
+	 * TicKet Pending:
40
+	 * constant used by ticket_status() to indicate that a ticket is pending
41
+	 * and is NOT YET available for purchase
42
+	 */
43
+	const pending = 'TKP';
44
+
45
+	/**
46
+	 * TicKet On sale:
47
+	 * constant used by ticket_status() to indicate that a ticket is On Sale
48
+	 * and IS available for purchase
49
+	 */
50
+	const onsale = 'TKO';
51
+
52
+	/**
53
+	 * extra meta key for tracking ticket reservations
54
+	 *
55
+	 * @type string
56
+	 */
57
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
58
+
59
+	/**
60
+	 * override of parent property
61
+	 *
62
+	 * @var EEM_Ticket
63
+	 */
64
+	protected $_model;
65
+
66
+	/**
67
+	 * cached result from method of the same name
68
+	 *
69
+	 * @var float $_ticket_total_with_taxes
70
+	 */
71
+	private $_ticket_total_with_taxes;
72
+
73
+
74
+	/**
75
+	 * @param array  $props_n_values          incoming values
76
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
77
+	 *                                        used.)
78
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
79
+	 *                                        date_format and the second value is the time format
80
+	 * @return EE_Ticket
81
+	 * @throws EE_Error
82
+	 * @throws ReflectionException
83
+	 */
84
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
85
+	{
86
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
87
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
88
+	}
89
+
90
+
91
+	/**
92
+	 * @param array  $props_n_values  incoming values from the database
93
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
94
+	 *                                the website will be used.
95
+	 * @return EE_Ticket
96
+	 * @throws EE_Error
97
+	 * @throws ReflectionException
98
+	 */
99
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
100
+	{
101
+		return new self($props_n_values, true, $timezone);
102
+	}
103
+
104
+
105
+	/**
106
+	 * @return bool
107
+	 * @throws EE_Error
108
+	 * @throws ReflectionException
109
+	 */
110
+	public function parent()
111
+	{
112
+		return $this->get('TKT_parent');
113
+	}
114
+
115
+
116
+	/**
117
+	 * return if a ticket has quantities available for purchase
118
+	 *
119
+	 * @param int $DTT_ID the primary key for a particular datetime
120
+	 * @return boolean
121
+	 * @throws EE_Error
122
+	 * @throws ReflectionException
123
+	 */
124
+	public function available($DTT_ID = 0)
125
+	{
126
+		// are we checking availability for a particular datetime ?
127
+		if ($DTT_ID) {
128
+			// get that datetime object
129
+			$datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
130
+			// if  ticket sales for this datetime have exceeded the reg limit...
131
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
132
+				return false;
133
+			}
134
+		}
135
+		// datetime is still open for registration, but is this ticket sold out ?
136
+		return $this->qty() < 1 || $this->qty() > $this->sold();
137
+	}
138
+
139
+
140
+	/**
141
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
142
+	 *
143
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
144
+	 *                               relevant status const
145
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
146
+	 *                               further processing
147
+	 * @return mixed status int if the display string isn't requested
148
+	 * @throws EE_Error
149
+	 * @throws ReflectionException
150
+	 */
151
+	public function ticket_status($display = false, $remaining = null)
152
+	{
153
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
154
+		if (! $remaining) {
155
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
156
+		}
157
+		if ($this->get('TKT_deleted')) {
158
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
159
+		}
160
+		if ($this->is_expired()) {
161
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
162
+		}
163
+		if ($this->is_pending()) {
164
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
165
+		}
166
+		if ($this->is_on_sale()) {
167
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
168
+		}
169
+		return '';
170
+	}
171
+
172
+
173
+	/**
174
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
175
+	 * considering ALL the factors used for figuring that out.
176
+	 *
177
+	 * @access public
178
+	 * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
179
+	 * @return boolean         true = tickets remaining, false not.
180
+	 * @throws EE_Error
181
+	 * @throws ReflectionException
182
+	 */
183
+	public function is_remaining($DTT_ID = 0)
184
+	{
185
+		$num_remaining = $this->remaining($DTT_ID);
186
+		if ($num_remaining === 0) {
187
+			return false;
188
+		}
189
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
190
+			return false;
191
+		}
192
+		return true;
193
+	}
194
+
195
+
196
+	/**
197
+	 * return the total number of tickets available for purchase
198
+	 *
199
+	 * @param int $DTT_ID  the primary key for a particular datetime.
200
+	 *                     set to 0 for all related datetimes
201
+	 * @return int
202
+	 * @throws EE_Error
203
+	 * @throws ReflectionException
204
+	 */
205
+	public function remaining($DTT_ID = 0)
206
+	{
207
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
208
+	}
209
+
210
+
211
+	/**
212
+	 * Gets min
213
+	 *
214
+	 * @return int
215
+	 * @throws EE_Error
216
+	 * @throws ReflectionException
217
+	 */
218
+	public function min()
219
+	{
220
+		return $this->get('TKT_min');
221
+	}
222
+
223
+
224
+	/**
225
+	 * return if a ticket is no longer available cause its available dates have expired.
226
+	 *
227
+	 * @return boolean
228
+	 * @throws EE_Error
229
+	 * @throws ReflectionException
230
+	 */
231
+	public function is_expired()
232
+	{
233
+		return ($this->get_raw('TKT_end_date') < time());
234
+	}
235
+
236
+
237
+	/**
238
+	 * Return if a ticket is yet to go on sale or not
239
+	 *
240
+	 * @return boolean
241
+	 * @throws EE_Error
242
+	 * @throws ReflectionException
243
+	 */
244
+	public function is_pending()
245
+	{
246
+		return ($this->get_raw('TKT_start_date') >= time());
247
+	}
248
+
249
+
250
+	/**
251
+	 * Return if a ticket is on sale or not
252
+	 *
253
+	 * @return boolean
254
+	 * @throws EE_Error
255
+	 * @throws ReflectionException
256
+	 */
257
+	public function is_on_sale()
258
+	{
259
+		return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
260
+	}
261
+
262
+
263
+	/**
264
+	 * This returns the chronologically last datetime that this ticket is associated with
265
+	 *
266
+	 * @param string $date_format
267
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
268
+	 *                            the end date ie: Jan 01 "to" Dec 31
269
+	 * @return string
270
+	 * @throws EE_Error
271
+	 * @throws ReflectionException
272
+	 */
273
+	public function date_range($date_format = '', $conjunction = ' - ')
274
+	{
275
+		$date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
276
+		$first_date  = $this->first_datetime() instanceof EE_Datetime
277
+			? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
278
+			: '';
279
+		$last_date   = $this->last_datetime() instanceof EE_Datetime
280
+			? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
281
+			: '';
282
+
283
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
284
+	}
285
+
286
+
287
+	/**
288
+	 * This returns the chronologically first datetime that this ticket is associated with
289
+	 *
290
+	 * @return EE_Datetime
291
+	 * @throws EE_Error
292
+	 * @throws ReflectionException
293
+	 */
294
+	public function first_datetime()
295
+	{
296
+		$datetimes = $this->datetimes(['limit' => 1]);
297
+		return reset($datetimes);
298
+	}
299
+
300
+
301
+	/**
302
+	 * Gets all the datetimes this ticket can be used for attending.
303
+	 * Unless otherwise specified, orders datetimes by start date.
304
+	 *
305
+	 * @param array $query_params @see
306
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
307
+	 * @return EE_Datetime[]|EE_Base_Class[]
308
+	 * @throws EE_Error
309
+	 * @throws ReflectionException
310
+	 */
311
+	public function datetimes($query_params = [])
312
+	{
313
+		if (! isset($query_params['order_by'])) {
314
+			$query_params['order_by']['DTT_order'] = 'ASC';
315
+		}
316
+		return $this->get_many_related('Datetime', $query_params);
317
+	}
318
+
319
+
320
+	/**
321
+	 * This returns the chronologically last datetime that this ticket is associated with
322
+	 *
323
+	 * @return EE_Datetime
324
+	 * @throws EE_Error
325
+	 * @throws ReflectionException
326
+	 */
327
+	public function last_datetime()
328
+	{
329
+		$datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
330
+		return end($datetimes);
331
+	}
332
+
333
+
334
+	/**
335
+	 * This returns the total tickets sold depending on the given parameters.
336
+	 *
337
+	 * @param string $what    Can be one of two options: 'ticket', 'datetime'.
338
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
339
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
340
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
341
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
342
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
343
+	 * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
344
+	 * @return mixed (array|int)          how many tickets have sold
345
+	 * @throws EE_Error
346
+	 * @throws ReflectionException
347
+	 */
348
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
349
+	{
350
+		$total        = 0;
351
+		$tickets_sold = $this->_all_tickets_sold();
352
+		switch ($what) {
353
+			case 'ticket':
354
+				return $tickets_sold['ticket'];
355
+				break;
356
+			case 'datetime':
357
+				if (empty($tickets_sold['datetime'])) {
358
+					return $total;
359
+				}
360
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
361
+					EE_Error::add_error(
362
+						__(
363
+							'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?',
364
+							'event_espresso'
365
+						),
366
+						__FILE__,
367
+						__FUNCTION__,
368
+						__LINE__
369
+					);
370
+					return $total;
371
+				}
372
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
373
+				break;
374
+			default:
375
+				return $total;
376
+		}
377
+	}
378
+
379
+
380
+	/**
381
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
382
+	 *
383
+	 * @return EE_Ticket[]
384
+	 * @throws EE_Error
385
+	 * @throws ReflectionException
386
+	 */
387
+	protected function _all_tickets_sold()
388
+	{
389
+		$datetimes    = $this->get_many_related('Datetime');
390
+		$tickets_sold = [];
391
+		if (! empty($datetimes)) {
392
+			foreach ($datetimes as $datetime) {
393
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
394
+			}
395
+		}
396
+		// Tickets sold
397
+		$tickets_sold['ticket'] = $this->sold();
398
+		return $tickets_sold;
399
+	}
400
+
401
+
402
+	/**
403
+	 * This returns the base price object for the ticket.
404
+	 *
405
+	 * @param bool $return_array whether to return as an array indexed by price id or just the object.
406
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
407
+	 * @throws EE_Error
408
+	 * @throws ReflectionException
409
+	 */
410
+	public function base_price($return_array = false)
411
+	{
412
+		$_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
413
+		return $return_array
414
+			? $this->get_many_related('Price', [$_where])
415
+			: $this->get_first_related('Price', [$_where]);
416
+	}
417
+
418
+
419
+	/**
420
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
421
+	 *
422
+	 * @access public
423
+	 * @return EE_Price[]
424
+	 * @throws EE_Error
425
+	 * @throws ReflectionException
426
+	 */
427
+	public function price_modifiers()
428
+	{
429
+		$query_params = [
430
+			0 => [
431
+				'Price_Type.PBT_ID' => [
432
+					'NOT IN',
433
+					[EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
434
+				],
435
+			],
436
+		];
437
+		return $this->prices($query_params);
438
+	}
439
+
440
+
441
+	/**
442
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
443
+	 *
444
+	 * @access public
445
+	 * @return EE_Price[]
446
+	 * @throws EE_Error
447
+	 * @throws ReflectionException
448
+	 */
449
+	public function tax_price_modifiers()
450
+	{
451
+		$query_params = [
452
+			0 => [
453
+				'Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax,
454
+			],
455
+		];
456
+		return $this->prices($query_params);
457
+	}
458
+
459
+
460
+	/**
461
+	 * Gets all the prices that combine to form the final price of this ticket
462
+	 *
463
+	 * @param array $query_params @see
464
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
465
+	 * @return EE_Price[]|EE_Base_Class[]
466
+	 * @throws EE_Error
467
+	 * @throws ReflectionException
468
+	 */
469
+	public function prices($query_params = [])
470
+	{
471
+		return $this->get_many_related('Price', $query_params);
472
+	}
473
+
474
+
475
+	/**
476
+	 * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
477
+	 *
478
+	 * @param array $query_params @see
479
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
480
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
481
+	 * @throws EE_Error
482
+	 * @throws ReflectionException
483
+	 */
484
+	public function datetime_tickets($query_params = [])
485
+	{
486
+		return $this->get_many_related('Datetime_Ticket', $query_params);
487
+	}
488
+
489
+
490
+	/**
491
+	 * Gets all the datetimes from the db ordered by DTT_order
492
+	 *
493
+	 * @param boolean $show_expired
494
+	 * @param boolean $show_deleted
495
+	 * @return EE_Datetime[]
496
+	 * @throws EE_Error
497
+	 * @throws ReflectionException
498
+	 */
499
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
500
+	{
501
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
502
+			$this->ID(),
503
+			$show_expired,
504
+			$show_deleted
505
+		);
506
+	}
507
+
508
+
509
+	/**
510
+	 * Gets ID
511
+	 *
512
+	 * @return string
513
+	 * @throws EE_Error
514
+	 * @throws ReflectionException
515
+	 */
516
+	public function ID()
517
+	{
518
+		return $this->get('TKT_ID');
519
+	}
520
+
521
+
522
+	/**
523
+	 * get the author of the ticket.
524
+	 *
525
+	 * @return int
526
+	 * @throws EE_Error
527
+	 * @throws ReflectionException
528
+	 * @since 4.5.0
529
+	 */
530
+	public function wp_user()
531
+	{
532
+		return $this->get('TKT_wp_user');
533
+	}
534
+
535
+
536
+	/**
537
+	 * Gets the template for the ticket
538
+	 *
539
+	 * @return EE_Ticket_Template|EE_Base_Class
540
+	 * @throws EE_Error
541
+	 * @throws ReflectionException
542
+	 */
543
+	public function template()
544
+	{
545
+		return $this->get_first_related('Ticket_Template');
546
+	}
547
+
548
+
549
+	/**
550
+	 * Simply returns an array of EE_Price objects that are taxes.
551
+	 *
552
+	 * @return EE_Price[]
553
+	 * @throws EE_Error
554
+	 */
555
+	public function get_ticket_taxes_for_admin()
556
+	{
557
+		return EE_Taxes::get_taxes_for_admin();
558
+	}
559
+
560
+
561
+	/**
562
+	 * @return float
563
+	 * @throws EE_Error
564
+	 * @throws ReflectionException
565
+	 */
566
+	public function ticket_price()
567
+	{
568
+		return $this->get('TKT_price');
569
+	}
570
+
571
+
572
+	/**
573
+	 * @return mixed
574
+	 * @throws EE_Error
575
+	 * @throws ReflectionException
576
+	 */
577
+	public function pretty_price()
578
+	{
579
+		return $this->get_pretty('TKT_price');
580
+	}
581
+
582
+
583
+	/**
584
+	 * @return bool
585
+	 * @throws EE_Error
586
+	 * @throws ReflectionException
587
+	 */
588
+	public function is_free()
589
+	{
590
+		return $this->get_ticket_total_with_taxes() === (float) 0;
591
+	}
592
+
593
+
594
+	/**
595
+	 * get_ticket_total_with_taxes
596
+	 *
597
+	 * @param bool $no_cache
598
+	 * @return float
599
+	 * @throws EE_Error
600
+	 * @throws ReflectionException
601
+	 */
602
+	public function get_ticket_total_with_taxes($no_cache = false)
603
+	{
604
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
605
+			$this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
606
+		}
607
+		return (float) $this->_ticket_total_with_taxes;
608
+	}
609
+
610
+
611
+	/**
612
+	 * @throws EE_Error
613
+	 * @throws ReflectionException
614
+	 */
615
+	public function ensure_TKT_Price_correct()
616
+	{
617
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
618
+		$this->save();
619
+	}
620
+
621
+
622
+	/**
623
+	 * @return float
624
+	 * @throws EE_Error
625
+	 * @throws ReflectionException
626
+	 */
627
+	public function get_ticket_subtotal()
628
+	{
629
+		return EE_Taxes::get_subtotal_for_admin($this);
630
+	}
631
+
632
+
633
+	/**
634
+	 * Returns the total taxes applied to this ticket
635
+	 *
636
+	 * @return float
637
+	 * @throws EE_Error
638
+	 * @throws ReflectionException
639
+	 */
640
+	public function get_ticket_taxes_total_for_admin()
641
+	{
642
+		return EE_Taxes::get_total_taxes_for_admin($this);
643
+	}
644
+
645
+
646
+	/**
647
+	 * Sets name
648
+	 *
649
+	 * @param string $name
650
+	 * @throws EE_Error
651
+	 * @throws ReflectionException
652
+	 */
653
+	public function set_name($name)
654
+	{
655
+		$this->set('TKT_name', $name);
656
+	}
657
+
658
+
659
+	/**
660
+	 * Gets description
661
+	 *
662
+	 * @return string
663
+	 * @throws EE_Error
664
+	 * @throws ReflectionException
665
+	 */
666
+	public function description()
667
+	{
668
+		return $this->get('TKT_description');
669
+	}
670
+
671
+
672
+	/**
673
+	 * Sets description
674
+	 *
675
+	 * @param string $description
676
+	 * @throws EE_Error
677
+	 * @throws ReflectionException
678
+	 */
679
+	public function set_description($description)
680
+	{
681
+		$this->set('TKT_description', $description);
682
+	}
683
+
684
+
685
+	/**
686
+	 * Gets start_date
687
+	 *
688
+	 * @param string $date_format
689
+	 * @param string $time_format
690
+	 * @return string
691
+	 * @throws EE_Error
692
+	 * @throws ReflectionException
693
+	 */
694
+	public function start_date($date_format = '', $time_format = '')
695
+	{
696
+		return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
697
+	}
698
+
699
+
700
+	/**
701
+	 * Sets start_date
702
+	 *
703
+	 * @param string $start_date
704
+	 * @return void
705
+	 * @throws EE_Error
706
+	 * @throws ReflectionException
707
+	 */
708
+	public function set_start_date($start_date)
709
+	{
710
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
711
+	}
712
+
713
+
714
+	/**
715
+	 * Gets end_date
716
+	 *
717
+	 * @param string $date_format
718
+	 * @param string $time_format
719
+	 * @return string
720
+	 * @throws EE_Error
721
+	 * @throws ReflectionException
722
+	 */
723
+	public function end_date($date_format = '', $time_format = '')
724
+	{
725
+		return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
726
+	}
727
+
728
+
729
+	/**
730
+	 * Sets end_date
731
+	 *
732
+	 * @param string $end_date
733
+	 * @return void
734
+	 * @throws EE_Error
735
+	 * @throws ReflectionException
736
+	 */
737
+	public function set_end_date($end_date)
738
+	{
739
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
740
+	}
741
+
742
+
743
+	/**
744
+	 * Sets sell until time
745
+	 *
746
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
747
+	 * @throws EE_Error
748
+	 * @throws ReflectionException
749
+	 * @since 4.5.0
750
+	 */
751
+	public function set_end_time($time)
752
+	{
753
+		$this->_set_time_for($time, 'TKT_end_date');
754
+	}
755
+
756
+
757
+	/**
758
+	 * Sets min
759
+	 *
760
+	 * @param int $min
761
+	 * @return void
762
+	 * @throws EE_Error
763
+	 * @throws ReflectionException
764
+	 */
765
+	public function set_min($min)
766
+	{
767
+		$this->set('TKT_min', $min);
768
+	}
769
+
770
+
771
+	/**
772
+	 * Gets max
773
+	 *
774
+	 * @return int
775
+	 * @throws EE_Error
776
+	 * @throws ReflectionException
777
+	 */
778
+	public function max()
779
+	{
780
+		return $this->get('TKT_max');
781
+	}
782
+
783
+
784
+	/**
785
+	 * Sets max
786
+	 *
787
+	 * @param int $max
788
+	 * @return void
789
+	 * @throws EE_Error
790
+	 * @throws ReflectionException
791
+	 */
792
+	public function set_max($max)
793
+	{
794
+		$this->set('TKT_max', $max);
795
+	}
796
+
797
+
798
+	/**
799
+	 * Sets price
800
+	 *
801
+	 * @param float $price
802
+	 * @return void
803
+	 * @throws EE_Error
804
+	 * @throws ReflectionException
805
+	 */
806
+	public function set_price($price)
807
+	{
808
+		$this->set('TKT_price', $price);
809
+	}
810
+
811
+
812
+	/**
813
+	 * Gets sold
814
+	 *
815
+	 * @return int
816
+	 * @throws EE_Error
817
+	 * @throws ReflectionException
818
+	 */
819
+	public function sold()
820
+	{
821
+		return $this->get_raw('TKT_sold');
822
+	}
823
+
824
+
825
+	/**
826
+	 * Sets sold
827
+	 *
828
+	 * @param int $sold
829
+	 * @return void
830
+	 * @throws EE_Error
831
+	 * @throws ReflectionException
832
+	 */
833
+	public function set_sold($sold)
834
+	{
835
+		// sold can not go below zero
836
+		$sold = max(0, $sold);
837
+		$this->set('TKT_sold', $sold);
838
+	}
839
+
840
+
841
+	/**
842
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
843
+	 * associated datetimes.
844
+	 *
845
+	 * @param int $qty
846
+	 * @return boolean
847
+	 * @throws EE_Error
848
+	 * @throws InvalidArgumentException
849
+	 * @throws InvalidDataTypeException
850
+	 * @throws InvalidInterfaceException
851
+	 * @throws ReflectionException
852
+	 * @since 4.9.80.p
853
+	 */
854
+	public function increaseSold($qty = 1)
855
+	{
856
+		$qty = absint($qty);
857
+		// increment sold and decrement reserved datetime quantities simultaneously
858
+		// don't worry about failures, because they must have already had a spot reserved
859
+		$this->increaseSoldForDatetimes($qty);
860
+		// Increment and decrement ticket quantities simultaneously
861
+		$success = $this->adjustNumericFieldsInDb(
862
+			[
863
+				'TKT_reserved' => $qty * -1,
864
+				'TKT_sold'     => $qty,
865
+			]
866
+		);
867
+		do_action(
868
+			'AHEE__EE_Ticket__increase_sold',
869
+			$this,
870
+			$qty,
871
+			$this->sold(),
872
+			$success
873
+		);
874
+		return $success;
875
+	}
876
+
877
+
878
+	/**
879
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
880
+	 *
881
+	 * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
882
+	 *                           counts), Negative means to decreases old counts (and increase reserved counts).
883
+	 * @param EE_Datetime[] $datetimes
884
+	 * @throws EE_Error
885
+	 * @throws InvalidArgumentException
886
+	 * @throws InvalidDataTypeException
887
+	 * @throws InvalidInterfaceException
888
+	 * @throws ReflectionException
889
+	 * @since 4.9.80.p
890
+	 */
891
+	protected function increaseSoldForDatetimes($qty, array $datetimes = [])
892
+	{
893
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
894
+		foreach ($datetimes as $datetime) {
895
+			$datetime->increaseSold($qty);
896
+		}
897
+	}
898
+
899
+
900
+	/**
901
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
902
+	 * DB and then updates the model objects.
903
+	 * Does not affect the reserved counts.
904
+	 *
905
+	 * @param int $qty
906
+	 * @return boolean
907
+	 * @throws EE_Error
908
+	 * @throws InvalidArgumentException
909
+	 * @throws InvalidDataTypeException
910
+	 * @throws InvalidInterfaceException
911
+	 * @throws ReflectionException
912
+	 * @since 4.9.80.p
913
+	 */
914
+	public function decreaseSold($qty = 1)
915
+	{
916
+		$qty = absint($qty);
917
+		$this->decreaseSoldForDatetimes($qty);
918
+		$success = $this->adjustNumericFieldsInDb(
919
+			[
920
+				'TKT_sold' => $qty * -1,
921
+			]
922
+		);
923
+		do_action(
924
+			'AHEE__EE_Ticket__decrease_sold',
925
+			$this,
926
+			$qty,
927
+			$this->sold(),
928
+			$success
929
+		);
930
+		return $success;
931
+	}
932
+
933
+
934
+	/**
935
+	 * Decreases sold on related datetimes
936
+	 *
937
+	 * @param int           $qty
938
+	 * @param EE_Datetime[] $datetimes
939
+	 * @return void
940
+	 * @throws EE_Error
941
+	 * @throws InvalidArgumentException
942
+	 * @throws InvalidDataTypeException
943
+	 * @throws InvalidInterfaceException
944
+	 * @throws ReflectionException
945
+	 * @since 4.9.80.p
946
+	 */
947
+	protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
948
+	{
949
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
950
+		if (is_array($datetimes)) {
951
+			foreach ($datetimes as $datetime) {
952
+				if ($datetime instanceof EE_Datetime) {
953
+					$datetime->decreaseSold($qty);
954
+				}
955
+			}
956
+		}
957
+	}
958
+
959
+
960
+	/**
961
+	 * Gets qty of reserved tickets
962
+	 *
963
+	 * @return int
964
+	 * @throws EE_Error
965
+	 * @throws ReflectionException
966
+	 */
967
+	public function reserved()
968
+	{
969
+		return $this->get_raw('TKT_reserved');
970
+	}
971
+
972
+
973
+	/**
974
+	 * Sets reserved
975
+	 *
976
+	 * @param int $reserved
977
+	 * @return void
978
+	 * @throws EE_Error
979
+	 * @throws ReflectionException
980
+	 */
981
+	public function set_reserved($reserved)
982
+	{
983
+		// reserved can not go below zero
984
+		$reserved = max(0, (int) $reserved);
985
+		$this->set('TKT_reserved', $reserved);
986
+	}
987
+
988
+
989
+	/**
990
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
991
+	 *
992
+	 * @param int    $qty
993
+	 * @param string $source
994
+	 * @return bool whether we successfully reserved the ticket or not.
995
+	 * @throws EE_Error
996
+	 * @throws InvalidArgumentException
997
+	 * @throws ReflectionException
998
+	 * @throws InvalidDataTypeException
999
+	 * @throws InvalidInterfaceException
1000
+	 * @since 4.9.80.p
1001
+	 */
1002
+	public function increaseReserved($qty = 1, $source = 'unknown')
1003
+	{
1004
+		$qty = absint($qty);
1005
+		do_action(
1006
+			'AHEE__EE_Ticket__increase_reserved__begin',
1007
+			$this,
1008
+			$qty,
1009
+			$source
1010
+		);
1011
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1012
+		$success                         = false;
1013
+		$datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1014
+		if ($datetimes_adjusted_successfully) {
1015
+			$success = $this->incrementFieldConditionallyInDb(
1016
+				'TKT_reserved',
1017
+				'TKT_sold',
1018
+				'TKT_qty',
1019
+				$qty
1020
+			);
1021
+			if (! $success) {
1022
+				// The datetimes were successfully bumped, but not the
1023
+				// ticket. So we need to manually rollback the datetimes.
1024
+				$this->decreaseReservedForDatetimes($qty);
1025
+			}
1026
+		}
1027
+		do_action(
1028
+			'AHEE__EE_Ticket__increase_reserved',
1029
+			$this,
1030
+			$qty,
1031
+			$this->reserved(),
1032
+			$success
1033
+		);
1034
+		return $success;
1035
+	}
1036
+
1037
+
1038
+	/**
1039
+	 * Increases reserved counts on related datetimes
1040
+	 *
1041
+	 * @param int           $qty
1042
+	 * @param EE_Datetime[] $datetimes
1043
+	 * @return boolean indicating success
1044
+	 * @throws EE_Error
1045
+	 * @throws InvalidArgumentException
1046
+	 * @throws InvalidDataTypeException
1047
+	 * @throws InvalidInterfaceException
1048
+	 * @throws ReflectionException
1049
+	 * @since 4.9.80.p
1050
+	 */
1051
+	protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1052
+	{
1053
+		$datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1054
+		$datetimes_updated = [];
1055
+		$limit_exceeded    = false;
1056
+		if (is_array($datetimes)) {
1057
+			foreach ($datetimes as $datetime) {
1058
+				if ($datetime instanceof EE_Datetime) {
1059
+					if ($datetime->increaseReserved($qty)) {
1060
+						$datetimes_updated[] = $datetime;
1061
+					} else {
1062
+						$limit_exceeded = true;
1063
+						break;
1064
+					}
1065
+				}
1066
+			}
1067
+			// If somewhere along the way we detected a datetime whose
1068
+			// limit was exceeded, do a manual rollback.
1069
+			if ($limit_exceeded) {
1070
+				$this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1071
+				return false;
1072
+			}
1073
+		}
1074
+		return true;
1075
+	}
1076
+
1077
+
1078
+	/**
1079
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1080
+	 *
1081
+	 * @param int    $qty
1082
+	 * @param bool   $adjust_datetimes
1083
+	 * @param string $source
1084
+	 * @return boolean
1085
+	 * @throws EE_Error
1086
+	 * @throws InvalidArgumentException
1087
+	 * @throws ReflectionException
1088
+	 * @throws InvalidDataTypeException
1089
+	 * @throws InvalidInterfaceException
1090
+	 * @since 4.9.80.p
1091
+	 */
1092
+	public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1093
+	{
1094
+		$qty = absint($qty);
1095
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1096
+		if ($adjust_datetimes) {
1097
+			$this->decreaseReservedForDatetimes($qty);
1098
+		}
1099
+		$success = $this->adjustNumericFieldsInDb(
1100
+			[
1101
+				'TKT_reserved' => $qty * -1,
1102
+			]
1103
+		);
1104
+		do_action(
1105
+			'AHEE__EE_Ticket__decrease_reserved',
1106
+			$this,
1107
+			$qty,
1108
+			$this->reserved(),
1109
+			$success
1110
+		);
1111
+		return $success;
1112
+	}
1113
+
1114
+
1115
+	/**
1116
+	 * Decreases the reserved count on the specified datetimes.
1117
+	 *
1118
+	 * @param int           $qty
1119
+	 * @param EE_Datetime[] $datetimes
1120
+	 * @throws EE_Error
1121
+	 * @throws InvalidArgumentException
1122
+	 * @throws ReflectionException
1123
+	 * @throws InvalidDataTypeException
1124
+	 * @throws InvalidInterfaceException
1125
+	 * @since 4.9.80.p
1126
+	 */
1127
+	protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1128
+	{
1129
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1130
+		foreach ($datetimes as $datetime) {
1131
+			if ($datetime instanceof EE_Datetime) {
1132
+				$datetime->decreaseReserved($qty);
1133
+			}
1134
+		}
1135
+	}
1136
+
1137
+
1138
+	/**
1139
+	 * Gets ticket quantity
1140
+	 *
1141
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1142
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1143
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1144
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1145
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1146
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1147
+	 * @return int
1148
+	 * @throws EE_Error
1149
+	 * @throws ReflectionException
1150
+	 */
1151
+	public function qty($context = '')
1152
+	{
1153
+		switch ($context) {
1154
+			case 'reg_limit':
1155
+				return $this->real_quantity_on_ticket();
1156
+			case 'saleable':
1157
+				return $this->real_quantity_on_ticket('saleable');
1158
+			default:
1159
+				return $this->get_raw('TKT_qty');
1160
+		}
1161
+	}
1162
+
1163
+
1164
+	/**
1165
+	 * Gets ticket quantity
1166
+	 *
1167
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1168
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1169
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1170
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1171
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1172
+	 * @param int    $DTT_ID      the primary key for a particular datetime.
1173
+	 *                            set to 0 for all related datetimes
1174
+	 * @return int
1175
+	 * @throws EE_Error
1176
+	 * @throws ReflectionException
1177
+	 */
1178
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1179
+	{
1180
+		$raw = $this->get_raw('TKT_qty');
1181
+		// return immediately if it's zero
1182
+		if ($raw === 0) {
1183
+			return $raw;
1184
+		}
1185
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1186
+		// ensure qty doesn't exceed raw value for THIS ticket
1187
+		$qty = min(EE_INF, $raw);
1188
+		// echo "\n . qty: " . $qty . '<br />';
1189
+		// calculate this ticket's total sales and reservations
1190
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1191
+		// echo "\n . sold: " . $this->sold() . '<br />';
1192
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1193
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1194
+		// first we need to calculate the maximum number of tickets available for the datetime
1195
+		// do we want data for one datetime or all of them ?
1196
+		$query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1197
+		$datetimes    = $this->datetimes($query_params);
1198
+		if (is_array($datetimes) && ! empty($datetimes)) {
1199
+			foreach ($datetimes as $datetime) {
1200
+				if ($datetime instanceof EE_Datetime) {
1201
+					$datetime->refresh_from_db();
1202
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1203
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1204
+					// initialize with no restrictions for each datetime
1205
+					// but adjust datetime qty based on datetime reg limit
1206
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1207
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1208
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1209
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1210
+					// and reservations for this datetime, that do NOT include sales and reservations
1211
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1212
+					if ($context === 'saleable') {
1213
+						$datetime_qty = max(
1214
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1215
+							0
1216
+						);
1217
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1218
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1219
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1220
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1221
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1222
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1223
+					}
1224
+					$qty = min($datetime_qty, $qty);
1225
+					// echo "\n . . qty: " . $qty . '<br />';
1226
+				}
1227
+			}
1228
+		}
1229
+		// NOW that we know the  maximum number of tickets available for the datetime
1230
+		// we can finally factor in the details for this specific ticket
1231
+		if ($qty > 0 && $context === 'saleable') {
1232
+			// and subtract the sales for THIS ticket
1233
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1234
+			// echo "\n . qty: " . $qty . '<br />';
1235
+		}
1236
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1237
+		return $qty;
1238
+	}
1239
+
1240
+
1241
+	/**
1242
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1243
+	 *
1244
+	 * @param int $qty
1245
+	 * @return void
1246
+	 * @throws EE_Error
1247
+	 * @throws ReflectionException
1248
+	 */
1249
+	public function set_qty($qty)
1250
+	{
1251
+		$datetimes = $this->datetimes();
1252
+		foreach ($datetimes as $datetime) {
1253
+			if ($datetime instanceof EE_Datetime) {
1254
+				$qty = min($qty, $datetime->reg_limit());
1255
+			}
1256
+		}
1257
+		$this->set('TKT_qty', $qty);
1258
+	}
1259
+
1260
+
1261
+	/**
1262
+	 * Gets uses
1263
+	 *
1264
+	 * @return int
1265
+	 * @throws EE_Error
1266
+	 * @throws ReflectionException
1267
+	 */
1268
+	public function uses()
1269
+	{
1270
+		return $this->get('TKT_uses');
1271
+	}
1272
+
1273
+
1274
+	/**
1275
+	 * Sets uses
1276
+	 *
1277
+	 * @param int $uses
1278
+	 * @return void
1279
+	 * @throws EE_Error
1280
+	 * @throws ReflectionException
1281
+	 */
1282
+	public function set_uses($uses)
1283
+	{
1284
+		$this->set('TKT_uses', $uses);
1285
+	}
1286
+
1287
+
1288
+	/**
1289
+	 * returns whether ticket is required or not.
1290
+	 *
1291
+	 * @return boolean
1292
+	 * @throws EE_Error
1293
+	 * @throws ReflectionException
1294
+	 */
1295
+	public function required()
1296
+	{
1297
+		return $this->get('TKT_required');
1298
+	}
1299
+
1300
+
1301
+	/**
1302
+	 * sets the TKT_required property
1303
+	 *
1304
+	 * @param boolean $required
1305
+	 * @return void
1306
+	 * @throws EE_Error
1307
+	 * @throws ReflectionException
1308
+	 */
1309
+	public function set_required($required)
1310
+	{
1311
+		$this->set('TKT_required', $required);
1312
+	}
1313
+
1314
+
1315
+	/**
1316
+	 * Gets taxable
1317
+	 *
1318
+	 * @return boolean
1319
+	 * @throws EE_Error
1320
+	 * @throws ReflectionException
1321
+	 */
1322
+	public function taxable()
1323
+	{
1324
+		return $this->get('TKT_taxable');
1325
+	}
1326
+
1327
+
1328
+	/**
1329
+	 * Sets taxable
1330
+	 *
1331
+	 * @param boolean $taxable
1332
+	 * @return void
1333
+	 * @throws EE_Error
1334
+	 * @throws ReflectionException
1335
+	 */
1336
+	public function set_taxable($taxable)
1337
+	{
1338
+		$this->set('TKT_taxable', $taxable);
1339
+	}
1340
+
1341
+
1342
+	/**
1343
+	 * Gets is_default
1344
+	 *
1345
+	 * @return boolean
1346
+	 * @throws EE_Error
1347
+	 * @throws ReflectionException
1348
+	 */
1349
+	public function is_default()
1350
+	{
1351
+		return $this->get('TKT_is_default');
1352
+	}
1353
+
1354
+
1355
+	/**
1356
+	 * Sets is_default
1357
+	 *
1358
+	 * @param boolean $is_default
1359
+	 * @return void
1360
+	 * @throws EE_Error
1361
+	 * @throws ReflectionException
1362
+	 */
1363
+	public function set_is_default($is_default)
1364
+	{
1365
+		$this->set('TKT_is_default', $is_default);
1366
+	}
1367
+
1368
+
1369
+	/**
1370
+	 * Gets order
1371
+	 *
1372
+	 * @return int
1373
+	 * @throws EE_Error
1374
+	 * @throws ReflectionException
1375
+	 */
1376
+	public function order()
1377
+	{
1378
+		return $this->get('TKT_order');
1379
+	}
1380
+
1381
+
1382
+	/**
1383
+	 * Sets order
1384
+	 *
1385
+	 * @param int $order
1386
+	 * @return void
1387
+	 * @throws EE_Error
1388
+	 * @throws ReflectionException
1389
+	 */
1390
+	public function set_order($order)
1391
+	{
1392
+		$this->set('TKT_order', $order);
1393
+	}
1394
+
1395
+
1396
+	/**
1397
+	 * Gets row
1398
+	 *
1399
+	 * @return int
1400
+	 * @throws EE_Error
1401
+	 * @throws ReflectionException
1402
+	 */
1403
+	public function row()
1404
+	{
1405
+		return $this->get('TKT_row');
1406
+	}
1407
+
1408
+
1409
+	/**
1410
+	 * Sets row
1411
+	 *
1412
+	 * @param int $row
1413
+	 * @return void
1414
+	 * @throws EE_Error
1415
+	 * @throws ReflectionException
1416
+	 */
1417
+	public function set_row($row)
1418
+	{
1419
+		$this->set('TKT_row', $row);
1420
+	}
1421
+
1422
+
1423
+	/**
1424
+	 * Gets deleted
1425
+	 *
1426
+	 * @return boolean
1427
+	 * @throws EE_Error
1428
+	 * @throws ReflectionException
1429
+	 */
1430
+	public function deleted()
1431
+	{
1432
+		return $this->get('TKT_deleted');
1433
+	}
1434
+
1435
+
1436
+	/**
1437
+	 * Sets deleted
1438
+	 *
1439
+	 * @param boolean $deleted
1440
+	 * @return void
1441
+	 * @throws EE_Error
1442
+	 * @throws ReflectionException
1443
+	 */
1444
+	public function set_deleted($deleted)
1445
+	{
1446
+		$this->set('TKT_deleted', $deleted);
1447
+	}
1448
+
1449
+
1450
+	/**
1451
+	 * Gets parent
1452
+	 *
1453
+	 * @return int
1454
+	 * @throws EE_Error
1455
+	 * @throws ReflectionException
1456
+	 */
1457
+	public function parent_ID()
1458
+	{
1459
+		return $this->get('TKT_parent');
1460
+	}
1461
+
1462
+
1463
+	/**
1464
+	 * Sets parent
1465
+	 *
1466
+	 * @param int $parent
1467
+	 * @return void
1468
+	 * @throws EE_Error
1469
+	 * @throws ReflectionException
1470
+	 */
1471
+	public function set_parent_ID($parent)
1472
+	{
1473
+		$this->set('TKT_parent', $parent);
1474
+	}
1475
+
1476
+
1477
+	/**
1478
+	 * @return boolean
1479
+	 * @throws EE_Error
1480
+	 * @throws InvalidArgumentException
1481
+	 * @throws InvalidDataTypeException
1482
+	 * @throws InvalidInterfaceException
1483
+	 * @throws ReflectionException
1484
+	 */
1485
+	public function reverse_calculate()
1486
+	{
1487
+		return $this->get('TKT_reverse_calculate');
1488
+	}
1489
+
1490
+
1491
+	/**
1492
+	 * @param boolean $reverse_calculate
1493
+	 * @throws EE_Error
1494
+	 * @throws InvalidArgumentException
1495
+	 * @throws InvalidDataTypeException
1496
+	 * @throws InvalidInterfaceException
1497
+	 * @throws ReflectionException
1498
+	 */
1499
+	public function set_reverse_calculate($reverse_calculate)
1500
+	{
1501
+		$this->set('TKT_reverse_calculate', $reverse_calculate);
1502
+	}
1503
+
1504
+
1505
+	/**
1506
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1507
+	 *
1508
+	 * @return string
1509
+	 * @throws EE_Error
1510
+	 * @throws ReflectionException
1511
+	 */
1512
+	public function name_and_info()
1513
+	{
1514
+		$times = [];
1515
+		foreach ($this->datetimes() as $datetime) {
1516
+			$times[] = $datetime->start_date_and_time();
1517
+		}
1518
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1519
+	}
1520
+
1521
+
1522
+	/**
1523
+	 * Gets name
1524
+	 *
1525
+	 * @return string
1526
+	 * @throws EE_Error
1527
+	 * @throws ReflectionException
1528
+	 */
1529
+	public function name()
1530
+	{
1531
+		return $this->get('TKT_name');
1532
+	}
1533
+
1534
+
1535
+	/**
1536
+	 * Gets price
1537
+	 *
1538
+	 * @return float
1539
+	 * @throws EE_Error
1540
+	 * @throws ReflectionException
1541
+	 */
1542
+	public function price()
1543
+	{
1544
+		return $this->get('TKT_price');
1545
+	}
1546
+
1547
+
1548
+	/**
1549
+	 * Gets all the registrations for this ticket
1550
+	 *
1551
+	 * @param array $query_params @see
1552
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1553
+	 * @return EE_Registration[]|EE_Base_Class[]
1554
+	 * @throws EE_Error
1555
+	 * @throws ReflectionException
1556
+	 */
1557
+	public function registrations($query_params = [])
1558
+	{
1559
+		return $this->get_many_related('Registration', $query_params);
1560
+	}
1561
+
1562
+
1563
+	/**
1564
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1565
+	 *
1566
+	 * @return int
1567
+	 * @throws EE_Error
1568
+	 * @throws ReflectionException
1569
+	 */
1570
+	public function update_tickets_sold()
1571
+	{
1572
+		$count_regs_for_this_ticket = $this->count_registrations(
1573
+			[
1574
+				[
1575
+					'STS_ID'      => EEM_Registration::status_id_approved,
1576
+					'REG_deleted' => 0,
1577
+				],
1578
+			]
1579
+		);
1580
+		$this->set_sold($count_regs_for_this_ticket);
1581
+		$this->save();
1582
+		return $count_regs_for_this_ticket;
1583
+	}
1584
+
1585
+
1586
+	/**
1587
+	 * Counts the registrations for this ticket
1588
+	 *
1589
+	 * @param array $query_params @see
1590
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1591
+	 * @return int
1592
+	 * @throws EE_Error
1593
+	 * @throws ReflectionException
1594
+	 */
1595
+	public function count_registrations($query_params = [])
1596
+	{
1597
+		return $this->count_related('Registration', $query_params);
1598
+	}
1599
+
1600
+
1601
+	/**
1602
+	 * Implementation for EEI_Has_Icon interface method.
1603
+	 *
1604
+	 * @return string
1605
+	 * @see EEI_Visual_Representation for comments
1606
+	 */
1607
+	public function get_icon()
1608
+	{
1609
+		return '<span class="dashicons dashicons-tickets-alt"/>';
1610
+	}
1611
+
1612
+
1613
+	/**
1614
+	 * Implementation of the EEI_Event_Relation interface method
1615
+	 *
1616
+	 * @return EE_Event
1617
+	 * @throws EE_Error
1618
+	 * @throws UnexpectedEntityException
1619
+	 * @throws ReflectionException
1620
+	 * @see EEI_Event_Relation for comments
1621
+	 */
1622
+	public function get_related_event()
1623
+	{
1624
+		// get one datetime to use for getting the event
1625
+		$datetime = $this->first_datetime();
1626
+		if (! $datetime instanceof EE_Datetime) {
1627
+			throw new UnexpectedEntityException(
1628
+				$datetime,
1629
+				'EE_Datetime',
1630
+				sprintf(
1631
+					__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1632
+					$this->name()
1633
+				)
1634
+			);
1635
+		}
1636
+		$event = $datetime->event();
1637
+		if (! $event instanceof EE_Event) {
1638
+			throw new UnexpectedEntityException(
1639
+				$event,
1640
+				'EE_Event',
1641
+				sprintf(
1642
+					__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1643
+					$this->name()
1644
+				)
1645
+			);
1646
+		}
1647
+		return $event;
1648
+	}
1649
+
1650
+
1651
+	/**
1652
+	 * Implementation of the EEI_Event_Relation interface method
1653
+	 *
1654
+	 * @return string
1655
+	 * @throws UnexpectedEntityException
1656
+	 * @throws EE_Error
1657
+	 * @throws ReflectionException
1658
+	 * @see EEI_Event_Relation for comments
1659
+	 */
1660
+	public function get_event_name()
1661
+	{
1662
+		$event = $this->get_related_event();
1663
+		return $event instanceof EE_Event ? $event->name() : '';
1664
+	}
1665
+
1666
+
1667
+	/**
1668
+	 * Implementation of the EEI_Event_Relation interface method
1669
+	 *
1670
+	 * @return int
1671
+	 * @throws UnexpectedEntityException
1672
+	 * @throws EE_Error
1673
+	 * @throws ReflectionException
1674
+	 * @see EEI_Event_Relation for comments
1675
+	 */
1676
+	public function get_event_ID()
1677
+	{
1678
+		$event = $this->get_related_event();
1679
+		return $event instanceof EE_Event ? $event->ID() : 0;
1680
+	}
1681
+
1682
+
1683
+	/**
1684
+	 * This simply returns whether a ticket can be permanently deleted or not.
1685
+	 * The criteria for determining this is whether the ticket has any related registrations.
1686
+	 * If there are none then it can be permanently deleted.
1687
+	 *
1688
+	 * @return bool
1689
+	 * @throws EE_Error
1690
+	 * @throws ReflectionException
1691
+	 */
1692
+	public function is_permanently_deleteable()
1693
+	{
1694
+		return $this->count_registrations() === 0;
1695
+	}
1696
+
1697
+
1698
+	/**
1699
+	 * @return int
1700
+	 * @throws EE_Error
1701
+	 * @throws ReflectionException
1702
+	 * @since   $VID:$
1703
+	 */
1704
+	public function visibility(): int
1705
+	{
1706
+		return $this->get('TKT_visibility');
1707
+	}
1708
+
1709
+
1710
+	/**
1711
+	 * @return int
1712
+	 * @throws EE_Error
1713
+	 * @throws ReflectionException
1714
+	 * @since   $VID:$
1715
+	 */
1716
+	public function isHidden(): int
1717
+	{
1718
+		return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1719
+	}
1720
+
1721
+
1722
+	/**
1723
+	 * @return int
1724
+	 * @throws EE_Error
1725
+	 * @throws ReflectionException
1726
+	 * @since   $VID:$
1727
+	 */
1728
+	public function isNotHidden(): int
1729
+	{
1730
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1731
+	}
1732
+
1733
+
1734
+	/**
1735
+	 * @return int
1736
+	 * @throws EE_Error
1737
+	 * @throws ReflectionException
1738
+	 * @since   $VID:$
1739
+	 */
1740
+	public function isPublicOnly(): int
1741
+	{
1742
+		return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1743
+	}
1744
+
1745
+
1746
+	/**
1747
+	 * @return int
1748
+	 * @throws EE_Error
1749
+	 * @throws ReflectionException
1750
+	 * @since   $VID:$
1751
+	 */
1752
+	public function isMembersOnly(): int
1753
+	{
1754
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1755
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1756
+	}
1757
+
1758
+
1759
+	/**
1760
+	 * @return int
1761
+	 * @throws EE_Error
1762
+	 * @throws ReflectionException
1763
+	 * @since   $VID:$
1764
+	 */
1765
+	public function isAdminsOnly(): int
1766
+	{
1767
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1768
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1769
+	}
1770
+
1771
+
1772
+	/**
1773
+	 * @return int
1774
+	 * @throws EE_Error
1775
+	 * @throws ReflectionException
1776
+	 * @since   $VID:$
1777
+	 */
1778
+	public function isAdminUiOnly(): int
1779
+	{
1780
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1781
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1782
+	}
1783
+
1784
+
1785
+	/**
1786
+	 * @param int $visibility
1787
+	 * @throws EE_Error
1788
+	 * @throws ReflectionException
1789
+	 * @since   $VID:$
1790
+	 */
1791
+	public function set_visibility(int $visibility)
1792
+	{
1793
+
1794
+		$ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1795
+		$ticket_visibility         = -1;
1796
+		foreach ($ticket_visibility_options as $ticket_visibility_option) {
1797
+			if ($visibility === $ticket_visibility_option) {
1798
+				$ticket_visibility = $visibility;
1799
+			}
1800
+		}
1801
+		if ($ticket_visibility === -1) {
1802
+			throw new DomainException(
1803
+				sprintf(
1804
+					esc_html__(
1805
+						'The supplied ticket visibility setting of "%1$s" is not valid. It needs to match one of the keys in the following array:%2$s %3$s ',
1806
+						'event_espresso'
1807
+					),
1808
+					$visibility,
1809
+					'<br />',
1810
+					var_export($ticket_visibility_options, true)
1811
+				)
1812
+			);
1813
+		}
1814
+		$this->set('TKT_visibility', $ticket_visibility);
1815
+	}
1816
+
1817
+
1818
+	/*******************************************************************
1819 1819
      ***********************  DEPRECATED METHODS  **********************
1820 1820
      *******************************************************************/
1821 1821
 
1822 1822
 
1823
-    /**
1824
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1825
-     * associated datetimes.
1826
-     *
1827
-     * @param int $qty
1828
-     * @return void
1829
-     * @throws EE_Error
1830
-     * @throws InvalidArgumentException
1831
-     * @throws InvalidDataTypeException
1832
-     * @throws InvalidInterfaceException
1833
-     * @throws ReflectionException
1834
-     * @deprecated 4.9.80.p
1835
-     */
1836
-    public function increase_sold($qty = 1)
1837
-    {
1838
-        EE_Error::doing_it_wrong(
1839
-            __FUNCTION__,
1840
-            esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1841
-            '4.9.80.p',
1842
-            '5.0.0.p'
1843
-        );
1844
-        $this->increaseSold($qty);
1845
-    }
1846
-
1847
-
1848
-    /**
1849
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1850
-     *
1851
-     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1852
-     *                 Negative means to decreases old counts (and increase reserved counts).
1853
-     * @throws EE_Error
1854
-     * @throws InvalidArgumentException
1855
-     * @throws InvalidDataTypeException
1856
-     * @throws InvalidInterfaceException
1857
-     * @throws ReflectionException
1858
-     * @deprecated 4.9.80.p
1859
-     */
1860
-    protected function _increase_sold_for_datetimes($qty)
1861
-    {
1862
-        EE_Error::doing_it_wrong(
1863
-            __FUNCTION__,
1864
-            esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1865
-            '4.9.80.p',
1866
-            '5.0.0.p'
1867
-        );
1868
-        $this->increaseSoldForDatetimes($qty);
1869
-    }
1870
-
1871
-
1872
-    /**
1873
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1874
-     * DB and then updates the model objects.
1875
-     * Does not affect the reserved counts.
1876
-     *
1877
-     * @param int $qty
1878
-     * @return void
1879
-     * @throws EE_Error
1880
-     * @throws InvalidArgumentException
1881
-     * @throws InvalidDataTypeException
1882
-     * @throws InvalidInterfaceException
1883
-     * @throws ReflectionException
1884
-     * @deprecated 4.9.80.p
1885
-     */
1886
-    public function decrease_sold($qty = 1)
1887
-    {
1888
-        EE_Error::doing_it_wrong(
1889
-            __FUNCTION__,
1890
-            esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1891
-            '4.9.80.p',
1892
-            '5.0.0.p'
1893
-        );
1894
-        $this->decreaseSold($qty);
1895
-    }
1896
-
1897
-
1898
-    /**
1899
-     * Decreases sold on related datetimes
1900
-     *
1901
-     * @param int $qty
1902
-     * @return void
1903
-     * @throws EE_Error
1904
-     * @throws InvalidArgumentException
1905
-     * @throws InvalidDataTypeException
1906
-     * @throws InvalidInterfaceException
1907
-     * @throws ReflectionException
1908
-     * @deprecated 4.9.80.p
1909
-     */
1910
-    protected function _decrease_sold_for_datetimes($qty = 1)
1911
-    {
1912
-        EE_Error::doing_it_wrong(
1913
-            __FUNCTION__,
1914
-            esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1915
-            '4.9.80.p',
1916
-            '5.0.0.p'
1917
-        );
1918
-        $this->decreaseSoldForDatetimes($qty);
1919
-    }
1920
-
1921
-
1922
-    /**
1923
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1924
-     *
1925
-     * @param int    $qty
1926
-     * @param string $source
1927
-     * @return bool whether we successfully reserved the ticket or not.
1928
-     * @throws EE_Error
1929
-     * @throws InvalidArgumentException
1930
-     * @throws ReflectionException
1931
-     * @throws InvalidDataTypeException
1932
-     * @throws InvalidInterfaceException
1933
-     * @deprecated 4.9.80.p
1934
-     */
1935
-    public function increase_reserved($qty = 1, $source = 'unknown')
1936
-    {
1937
-        EE_Error::doing_it_wrong(
1938
-            __FUNCTION__,
1939
-            esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1940
-            '4.9.80.p',
1941
-            '5.0.0.p'
1942
-        );
1943
-        return $this->increaseReserved($qty);
1944
-    }
1945
-
1946
-
1947
-    /**
1948
-     * Increases sold on related datetimes
1949
-     *
1950
-     * @param int $qty
1951
-     * @return boolean indicating success
1952
-     * @throws EE_Error
1953
-     * @throws InvalidArgumentException
1954
-     * @throws InvalidDataTypeException
1955
-     * @throws InvalidInterfaceException
1956
-     * @throws ReflectionException
1957
-     * @deprecated 4.9.80.p
1958
-     */
1959
-    protected function _increase_reserved_for_datetimes($qty = 1)
1960
-    {
1961
-        EE_Error::doing_it_wrong(
1962
-            __FUNCTION__,
1963
-            esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
1964
-            '4.9.80.p',
1965
-            '5.0.0.p'
1966
-        );
1967
-        return $this->increaseReservedForDatetimes($qty);
1968
-    }
1969
-
1970
-
1971
-    /**
1972
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1973
-     *
1974
-     * @param int    $qty
1975
-     * @param bool   $adjust_datetimes
1976
-     * @param string $source
1977
-     * @return void
1978
-     * @throws EE_Error
1979
-     * @throws InvalidArgumentException
1980
-     * @throws ReflectionException
1981
-     * @throws InvalidDataTypeException
1982
-     * @throws InvalidInterfaceException
1983
-     * @deprecated 4.9.80.p
1984
-     */
1985
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1986
-    {
1987
-        EE_Error::doing_it_wrong(
1988
-            __FUNCTION__,
1989
-            esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
1990
-            '4.9.80.p',
1991
-            '5.0.0.p'
1992
-        );
1993
-        $this->decreaseReserved($qty);
1994
-    }
1995
-
1996
-
1997
-    /**
1998
-     * Decreases reserved on related datetimes
1999
-     *
2000
-     * @param int $qty
2001
-     * @return void
2002
-     * @throws EE_Error
2003
-     * @throws InvalidArgumentException
2004
-     * @throws ReflectionException
2005
-     * @throws InvalidDataTypeException
2006
-     * @throws InvalidInterfaceException
2007
-     * @deprecated 4.9.80.p
2008
-     */
2009
-    protected function _decrease_reserved_for_datetimes($qty = 1)
2010
-    {
2011
-        EE_Error::doing_it_wrong(
2012
-            __FUNCTION__,
2013
-            esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2014
-            '4.9.80.p',
2015
-            '5.0.0.p'
2016
-        );
2017
-        $this->decreaseReservedForDatetimes($qty);
2018
-    }
1823
+	/**
1824
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1825
+	 * associated datetimes.
1826
+	 *
1827
+	 * @param int $qty
1828
+	 * @return void
1829
+	 * @throws EE_Error
1830
+	 * @throws InvalidArgumentException
1831
+	 * @throws InvalidDataTypeException
1832
+	 * @throws InvalidInterfaceException
1833
+	 * @throws ReflectionException
1834
+	 * @deprecated 4.9.80.p
1835
+	 */
1836
+	public function increase_sold($qty = 1)
1837
+	{
1838
+		EE_Error::doing_it_wrong(
1839
+			__FUNCTION__,
1840
+			esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1841
+			'4.9.80.p',
1842
+			'5.0.0.p'
1843
+		);
1844
+		$this->increaseSold($qty);
1845
+	}
1846
+
1847
+
1848
+	/**
1849
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1850
+	 *
1851
+	 * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1852
+	 *                 Negative means to decreases old counts (and increase reserved counts).
1853
+	 * @throws EE_Error
1854
+	 * @throws InvalidArgumentException
1855
+	 * @throws InvalidDataTypeException
1856
+	 * @throws InvalidInterfaceException
1857
+	 * @throws ReflectionException
1858
+	 * @deprecated 4.9.80.p
1859
+	 */
1860
+	protected function _increase_sold_for_datetimes($qty)
1861
+	{
1862
+		EE_Error::doing_it_wrong(
1863
+			__FUNCTION__,
1864
+			esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1865
+			'4.9.80.p',
1866
+			'5.0.0.p'
1867
+		);
1868
+		$this->increaseSoldForDatetimes($qty);
1869
+	}
1870
+
1871
+
1872
+	/**
1873
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1874
+	 * DB and then updates the model objects.
1875
+	 * Does not affect the reserved counts.
1876
+	 *
1877
+	 * @param int $qty
1878
+	 * @return void
1879
+	 * @throws EE_Error
1880
+	 * @throws InvalidArgumentException
1881
+	 * @throws InvalidDataTypeException
1882
+	 * @throws InvalidInterfaceException
1883
+	 * @throws ReflectionException
1884
+	 * @deprecated 4.9.80.p
1885
+	 */
1886
+	public function decrease_sold($qty = 1)
1887
+	{
1888
+		EE_Error::doing_it_wrong(
1889
+			__FUNCTION__,
1890
+			esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1891
+			'4.9.80.p',
1892
+			'5.0.0.p'
1893
+		);
1894
+		$this->decreaseSold($qty);
1895
+	}
1896
+
1897
+
1898
+	/**
1899
+	 * Decreases sold on related datetimes
1900
+	 *
1901
+	 * @param int $qty
1902
+	 * @return void
1903
+	 * @throws EE_Error
1904
+	 * @throws InvalidArgumentException
1905
+	 * @throws InvalidDataTypeException
1906
+	 * @throws InvalidInterfaceException
1907
+	 * @throws ReflectionException
1908
+	 * @deprecated 4.9.80.p
1909
+	 */
1910
+	protected function _decrease_sold_for_datetimes($qty = 1)
1911
+	{
1912
+		EE_Error::doing_it_wrong(
1913
+			__FUNCTION__,
1914
+			esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1915
+			'4.9.80.p',
1916
+			'5.0.0.p'
1917
+		);
1918
+		$this->decreaseSoldForDatetimes($qty);
1919
+	}
1920
+
1921
+
1922
+	/**
1923
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1924
+	 *
1925
+	 * @param int    $qty
1926
+	 * @param string $source
1927
+	 * @return bool whether we successfully reserved the ticket or not.
1928
+	 * @throws EE_Error
1929
+	 * @throws InvalidArgumentException
1930
+	 * @throws ReflectionException
1931
+	 * @throws InvalidDataTypeException
1932
+	 * @throws InvalidInterfaceException
1933
+	 * @deprecated 4.9.80.p
1934
+	 */
1935
+	public function increase_reserved($qty = 1, $source = 'unknown')
1936
+	{
1937
+		EE_Error::doing_it_wrong(
1938
+			__FUNCTION__,
1939
+			esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1940
+			'4.9.80.p',
1941
+			'5.0.0.p'
1942
+		);
1943
+		return $this->increaseReserved($qty);
1944
+	}
1945
+
1946
+
1947
+	/**
1948
+	 * Increases sold on related datetimes
1949
+	 *
1950
+	 * @param int $qty
1951
+	 * @return boolean indicating success
1952
+	 * @throws EE_Error
1953
+	 * @throws InvalidArgumentException
1954
+	 * @throws InvalidDataTypeException
1955
+	 * @throws InvalidInterfaceException
1956
+	 * @throws ReflectionException
1957
+	 * @deprecated 4.9.80.p
1958
+	 */
1959
+	protected function _increase_reserved_for_datetimes($qty = 1)
1960
+	{
1961
+		EE_Error::doing_it_wrong(
1962
+			__FUNCTION__,
1963
+			esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
1964
+			'4.9.80.p',
1965
+			'5.0.0.p'
1966
+		);
1967
+		return $this->increaseReservedForDatetimes($qty);
1968
+	}
1969
+
1970
+
1971
+	/**
1972
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1973
+	 *
1974
+	 * @param int    $qty
1975
+	 * @param bool   $adjust_datetimes
1976
+	 * @param string $source
1977
+	 * @return void
1978
+	 * @throws EE_Error
1979
+	 * @throws InvalidArgumentException
1980
+	 * @throws ReflectionException
1981
+	 * @throws InvalidDataTypeException
1982
+	 * @throws InvalidInterfaceException
1983
+	 * @deprecated 4.9.80.p
1984
+	 */
1985
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1986
+	{
1987
+		EE_Error::doing_it_wrong(
1988
+			__FUNCTION__,
1989
+			esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
1990
+			'4.9.80.p',
1991
+			'5.0.0.p'
1992
+		);
1993
+		$this->decreaseReserved($qty);
1994
+	}
1995
+
1996
+
1997
+	/**
1998
+	 * Decreases reserved on related datetimes
1999
+	 *
2000
+	 * @param int $qty
2001
+	 * @return void
2002
+	 * @throws EE_Error
2003
+	 * @throws InvalidArgumentException
2004
+	 * @throws ReflectionException
2005
+	 * @throws InvalidDataTypeException
2006
+	 * @throws InvalidInterfaceException
2007
+	 * @deprecated 4.9.80.p
2008
+	 */
2009
+	protected function _decrease_reserved_for_datetimes($qty = 1)
2010
+	{
2011
+		EE_Error::doing_it_wrong(
2012
+			__FUNCTION__,
2013
+			esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2014
+			'4.9.80.p',
2015
+			'5.0.0.p'
2016
+		);
2017
+		$this->decreaseReservedForDatetimes($qty);
2018
+	}
2019 2019
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Event.class.php 2 patches
Indentation   +1450 added lines, -1450 removed lines patch added patch discarded remove patch
@@ -15,1454 +15,1454 @@
 block discarded – undo
15 15
 class EE_Event extends EE_CPT_Base implements EEI_Line_Item_Object, EEI_Admin_Links, EEI_Has_Icon, EEI_Event
16 16
 {
17 17
 
18
-    /**
19
-     * cached value for the the logical active status for the event
20
-     *
21
-     * @see get_active_status()
22
-     * @var string
23
-     */
24
-    protected $_active_status = '';
25
-
26
-    /**
27
-     * This is just used for caching the Primary Datetime for the Event on initial retrieval
28
-     *
29
-     * @var EE_Datetime
30
-     */
31
-    protected $_Primary_Datetime;
32
-
33
-    /**
34
-     * @var EventSpacesCalculator $available_spaces_calculator
35
-     */
36
-    protected $available_spaces_calculator;
37
-
38
-
39
-    /**
40
-     * @param array  $props_n_values          incoming values
41
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
42
-     *                                        used.)
43
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
44
-     *                                        date_format and the second value is the time format
45
-     * @return EE_Event
46
-     * @throws EE_Error
47
-     * @throws ReflectionException
48
-     */
49
-    public static function new_instance(
50
-        array $props_n_values = [],
51
-        string $timezone = '',
52
-        array $date_formats = []
53
-    ): EE_Event {
54
-        /** @var EE_Event $has_object */
55
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
56
-        return $has_object
57
-            ?: new self($props_n_values, false, $timezone, $date_formats);
58
-    }
59
-
60
-
61
-    /**
62
-     * @param array  $props_n_values  incoming values from the database
63
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
64
-     *                                the website will be used.
65
-     * @return EE_Event
66
-     * @throws EE_Error
67
-     * @throws ReflectionException
68
-     */
69
-    public static function new_instance_from_db(array $props_n_values = [], string $timezone = ''): EE_Event
70
-    {
71
-        return new self($props_n_values, true, $timezone);
72
-    }
73
-
74
-
75
-    /**
76
-     * @return EventSpacesCalculator
77
-     * @throws EE_Error
78
-     */
79
-    public function getAvailableSpacesCalculator(): EventSpacesCalculator
80
-    {
81
-        if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
82
-            $this->available_spaces_calculator = new EventSpacesCalculator($this);
83
-        }
84
-        return $this->available_spaces_calculator;
85
-    }
86
-
87
-
88
-    /**
89
-     * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
90
-     *
91
-     * @param string $field_name
92
-     * @param mixed  $field_value
93
-     * @param bool   $use_default
94
-     * @throws EE_Error
95
-     * @throws ReflectionException
96
-     */
97
-    public function set(string $field_name, $field_value, bool $use_default = false)
98
-    {
99
-        switch ($field_name) {
100
-            case 'status':
101
-                $this->set_status($field_value, $use_default);
102
-                break;
103
-            default:
104
-                parent::set($field_name, $field_value, $use_default);
105
-        }
106
-    }
107
-
108
-
109
-    /**
110
-     * set_status
111
-     * Checks if event status is being changed to SOLD OUT
112
-     * and updates event meta data with previous event status
113
-     * so that we can revert things if/when the event is no longer sold out
114
-     *
115
-     * @param string|null $status
116
-     * @return void
117
-     * @throws EE_Error
118
-     * @throws ReflectionException
119
-     */
120
-    public function set_status(string $status = null, bool $use_default = false)
121
-    {
122
-        // if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
123
-        if (empty($status) && ! $use_default) {
124
-            return;
125
-        }
126
-        // get current Event status
127
-        $old_status = $this->status();
128
-        // if status has changed
129
-        if ($old_status !== $status) {
130
-            // TO sold_out
131
-            if ($status === EEM_Event::sold_out) {
132
-                // save the previous event status so that we can revert if the event is no longer sold out
133
-                $this->add_post_meta('_previous_event_status', $old_status);
134
-                do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $status);
135
-                // OR FROM  sold_out
136
-            } elseif ($old_status === EEM_Event::sold_out) {
137
-                $this->delete_post_meta('_previous_event_status');
138
-                do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $status);
139
-            }
140
-            // clear out the active status so that it gets reset the next time it is requested
141
-            $this->_active_status = null;
142
-            // update status
143
-            parent::set('status', $status, $use_default);
144
-            do_action('AHEE__EE_Event__set_status__after_update', $this);
145
-            return;
146
-        }
147
-        // even though the old value matches the new value, it's still good to
148
-        // allow the parent set method to have a say
149
-        parent::set('status', $status, $use_default);
150
-    }
151
-
152
-
153
-    /**
154
-     * Gets all the datetimes for this event
155
-     *
156
-     * @param array $query_params @see
157
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
158
-     * @return EE_Datetime[]
159
-     * @throws EE_Error
160
-     * @throws ReflectionException
161
-     */
162
-    public function datetimes(array $query_params = []): array
163
-    {
164
-        return $this->get_many_related('Datetime', $query_params);
165
-    }
166
-
167
-
168
-    /**
169
-     * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
170
-     *
171
-     * @return EE_Datetime[]
172
-     * @throws EE_Error
173
-     * @throws ReflectionException
174
-     */
175
-    public function datetimes_in_chronological_order(): array
176
-    {
177
-        return $this->get_many_related('Datetime', ['order_by' => ['DTT_EVT_start' => 'ASC']]);
178
-    }
179
-
180
-
181
-    /**
182
-     * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
183
-     * @darren, we should probably UNSET timezone on the EEM_Datetime model
184
-     * after running our query, so that this timezone isn't set for EVERY query
185
-     * on EEM_Datetime for the rest of the request, no?
186
-     *
187
-     * @param boolean $show_expired whether or not to include expired events
188
-     * @param boolean $show_deleted whether or not to include deleted events
189
-     * @param int    $limit
190
-     * @return EE_Datetime[]
191
-     * @throws EE_Error
192
-     * @throws ReflectionException
193
-     */
194
-    public function datetimes_ordered(bool $show_expired = true, bool $show_deleted = false, int $limit = 0): array
195
-    {
196
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
197
-            $this->ID(),
198
-            $show_expired,
199
-            $show_deleted,
200
-            $limit
201
-        );
202
-    }
203
-
204
-
205
-    /**
206
-     * Returns one related datetime. Mostly only used by some legacy code.
207
-     *
208
-     * @return EE_Datetime
209
-     * @throws EE_Error
210
-     * @throws ReflectionException
211
-     */
212
-    public function first_datetime(): EE_Datetime
213
-    {
214
-        return $this->get_first_related('Datetime');
215
-    }
216
-
217
-
218
-    /**
219
-     * Returns the 'primary' datetime for the event
220
-     *
221
-     * @param bool $try_to_exclude_expired
222
-     * @param bool $try_to_exclude_deleted
223
-     * @return EE_Datetime
224
-     * @throws EE_Error
225
-     * @throws ReflectionException
226
-     */
227
-    public function primary_datetime(
228
-        bool $try_to_exclude_expired = true,
229
-        bool $try_to_exclude_deleted = true
230
-    ): EE_Datetime {
231
-        if (! empty($this->_Primary_Datetime)) {
232
-            return $this->_Primary_Datetime;
233
-        }
234
-        $this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
235
-            $this->ID(),
236
-            $try_to_exclude_expired,
237
-            $try_to_exclude_deleted
238
-        );
239
-        return $this->_Primary_Datetime;
240
-    }
241
-
242
-
243
-    /**
244
-     * Gets all the tickets available for purchase of this event
245
-     *
246
-     * @param array $query_params @see
247
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
248
-     * @return EE_Ticket[]
249
-     * @throws EE_Error
250
-     * @throws ReflectionException
251
-     */
252
-    public function tickets(array $query_params = []): array
253
-    {
254
-        // first get all datetimes
255
-        $datetimes = $this->datetimes_ordered();
256
-        if (! $datetimes) {
257
-            return [];
258
-        }
259
-        $datetime_ids = [];
260
-        foreach ($datetimes as $datetime) {
261
-            $datetime_ids[] = $datetime->ID();
262
-        }
263
-        $where_params = ['Datetime.DTT_ID' => ['IN', $datetime_ids]];
264
-        // if incoming $query_params has where conditions let's merge but not override existing.
265
-        if (is_array($query_params) && isset($query_params[0])) {
266
-            $where_params = array_merge($query_params[0], $where_params);
267
-            unset($query_params[0]);
268
-        }
269
-        // now add $where_params to $query_params
270
-        $query_params[0] = $where_params;
271
-        return EEM_Ticket::instance()->get_all($query_params);
272
-    }
273
-
274
-
275
-    /**
276
-     * get all unexpired untrashed tickets
277
-     *
278
-     * @return EE_Ticket[]
279
-     * @throws EE_Error
280
-     * @throws ReflectionException
281
-     */
282
-    public function active_tickets(): array
283
-    {
284
-        return $this->tickets(
285
-            [
286
-                [
287
-                    'TKT_end_date' => ['>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')],
288
-                    'TKT_deleted'  => false,
289
-                ],
290
-            ]
291
-        );
292
-    }
293
-
294
-
295
-    /**
296
-     * @return bool
297
-     * @throws EE_Error
298
-     */
299
-    public function additional_limit(): bool
300
-    {
301
-        return $this->get('EVT_additional_limit');
302
-    }
303
-
304
-
305
-    /**
306
-     * @return bool
307
-     * @throws EE_Error
308
-     */
309
-    public function allow_overflow(): bool
310
-    {
311
-        return $this->get('EVT_allow_overflow');
312
-    }
313
-
314
-
315
-    /**
316
-     * @return bool
317
-     * @throws EE_Error
318
-     */
319
-    public function created(): bool
320
-    {
321
-        return $this->get('EVT_created');
322
-    }
323
-
324
-
325
-    /**
326
-     * @return bool
327
-     * @throws EE_Error
328
-     */
329
-    public function description(): bool
330
-    {
331
-        return $this->get('EVT_desc');
332
-    }
333
-
334
-
335
-    /**
336
-     * Runs do_shortcode and wpautop on the description
337
-     *
338
-     * @return string of html
339
-     * @throws EE_Error
340
-     */
341
-    public function description_filtered(): string
342
-    {
343
-        return $this->get_pretty('EVT_desc');
344
-    }
345
-
346
-
347
-    /**
348
-     * @return bool
349
-     * @throws EE_Error
350
-     */
351
-    public function display_description(): bool
352
-    {
353
-        return $this->get('EVT_display_desc');
354
-    }
355
-
356
-
357
-    /**
358
-     * @return bool
359
-     * @throws EE_Error
360
-     */
361
-    public function display_ticket_selector(): bool
362
-    {
363
-        return (bool) $this->get('EVT_display_ticket_selector');
364
-    }
365
-
366
-
367
-    /**
368
-     * @return bool
369
-     * @throws EE_Error
370
-     */
371
-    public function external_url(): bool
372
-    {
373
-        return $this->get('EVT_external_URL');
374
-    }
375
-
376
-
377
-    /**
378
-     * @return bool
379
-     * @throws EE_Error
380
-     */
381
-    public function member_only(): bool
382
-    {
383
-        return $this->get('EVT_member_only');
384
-    }
385
-
386
-
387
-    /**
388
-     * @return bool
389
-     * @throws EE_Error
390
-     */
391
-    public function phone(): bool
392
-    {
393
-        return $this->get('EVT_phone');
394
-    }
395
-
396
-
397
-    /**
398
-     * @return bool
399
-     * @throws EE_Error
400
-     */
401
-    public function modified(): bool
402
-    {
403
-        return $this->get('EVT_modified');
404
-    }
405
-
406
-
407
-    /**
408
-     * @return bool
409
-     * @throws EE_Error
410
-     */
411
-    public function name(): bool
412
-    {
413
-        return $this->get('EVT_name');
414
-    }
415
-
416
-
417
-    /**
418
-     * @return bool
419
-     * @throws EE_Error
420
-     */
421
-    public function order(): bool
422
-    {
423
-        return $this->get('EVT_order');
424
-    }
425
-
426
-
427
-    /**
428
-     * @return bool|string
429
-     * @throws EE_Error
430
-     */
431
-    public function default_registration_status()
432
-    {
433
-        $event_default_registration_status = $this->get('EVT_default_registration_status');
434
-        return ! empty($event_default_registration_status)
435
-            ? $event_default_registration_status
436
-            : EE_Registry::instance()->CFG->registration->default_STS_ID;
437
-    }
438
-
439
-
440
-    /**
441
-     * @param int  $num_words
442
-     * @param string|null $more
443
-     * @param bool $not_full_desc
444
-     * @return bool|string
445
-     * @throws EE_Error
446
-     */
447
-    public function short_description(int $num_words = 55, string $more = null, bool $not_full_desc = false)
448
-    {
449
-        $short_desc = $this->get('EVT_short_desc');
450
-        if (! empty($short_desc) || $not_full_desc) {
451
-            return $short_desc;
452
-        }
453
-        $full_desc = $this->get('EVT_desc');
454
-        return wp_trim_words($full_desc, $num_words, $more);
455
-    }
456
-
457
-
458
-    /**
459
-     * @return string
460
-     * @throws EE_Error
461
-     */
462
-    public function slug(): string
463
-    {
464
-        return $this->get('EVT_slug');
465
-    }
466
-
467
-
468
-    /**
469
-     * @return string
470
-     * @throws EE_Error
471
-     */
472
-    public function timezone_string(): string
473
-    {
474
-        return $this->get('EVT_timezone_string');
475
-    }
476
-
477
-
478
-    /**
479
-     * @return string
480
-     * @throws EE_Error
481
-     */
482
-    public function visible_on(): string
483
-    {
484
-        return $this->get('EVT_visible_on');
485
-    }
486
-
487
-
488
-    /**
489
-     * @return int
490
-     * @throws EE_Error
491
-     */
492
-    public function wp_user(): int
493
-    {
494
-        return $this->get('EVT_wp_user');
495
-    }
496
-
497
-
498
-    /**
499
-     * @return bool
500
-     * @throws EE_Error
501
-     */
502
-    public function donations(): bool
503
-    {
504
-        return $this->get('EVT_donations');
505
-    }
506
-
507
-
508
-    /**
509
-     * @param $limit
510
-     * @throws EE_Error
511
-     * @throws ReflectionException
512
-     */
513
-    public function set_additional_limit($limit)
514
-    {
515
-        $this->set('EVT_additional_limit', $limit);
516
-    }
517
-
518
-
519
-    /**
520
-     * @param $created
521
-     * @throws EE_Error
522
-     * @throws ReflectionException
523
-     */
524
-    public function set_created($created)
525
-    {
526
-        $this->set('EVT_created', $created);
527
-    }
528
-
529
-
530
-    /**
531
-     * @param $desc
532
-     * @throws EE_Error
533
-     * @throws ReflectionException
534
-     */
535
-    public function set_description($desc)
536
-    {
537
-        $this->set('EVT_desc', $desc);
538
-    }
539
-
540
-
541
-    /**
542
-     * @param $display_desc
543
-     * @throws EE_Error
544
-     * @throws ReflectionException
545
-     */
546
-    public function set_display_description($display_desc)
547
-    {
548
-        $this->set('EVT_display_desc', $display_desc);
549
-    }
550
-
551
-
552
-    /**
553
-     * @param $display_ticket_selector
554
-     * @throws EE_Error
555
-     * @throws ReflectionException
556
-     */
557
-    public function set_display_ticket_selector($display_ticket_selector)
558
-    {
559
-        $this->set('EVT_display_ticket_selector', $display_ticket_selector);
560
-    }
561
-
562
-
563
-    /**
564
-     * @param $external_url
565
-     * @throws EE_Error
566
-     * @throws ReflectionException
567
-     */
568
-    public function set_external_url($external_url)
569
-    {
570
-        $this->set('EVT_external_URL', $external_url);
571
-    }
572
-
573
-
574
-    /**
575
-     * @param $member_only
576
-     * @throws EE_Error
577
-     * @throws ReflectionException
578
-     */
579
-    public function set_member_only($member_only)
580
-    {
581
-        $this->set('EVT_member_only', $member_only);
582
-    }
583
-
584
-
585
-    /**
586
-     * @param $event_phone
587
-     * @throws EE_Error
588
-     * @throws ReflectionException
589
-     */
590
-    public function set_event_phone($event_phone)
591
-    {
592
-        $this->set('EVT_phone', $event_phone);
593
-    }
594
-
595
-
596
-    /**
597
-     * @param $modified
598
-     * @throws EE_Error
599
-     * @throws ReflectionException
600
-     */
601
-    public function set_modified($modified)
602
-    {
603
-        $this->set('EVT_modified', $modified);
604
-    }
605
-
606
-
607
-    /**
608
-     * @param $name
609
-     * @throws EE_Error
610
-     * @throws ReflectionException
611
-     */
612
-    public function set_name($name)
613
-    {
614
-        $this->set('EVT_name', $name);
615
-    }
616
-
617
-
618
-    /**
619
-     * @param $order
620
-     * @throws EE_Error
621
-     * @throws ReflectionException
622
-     */
623
-    public function set_order($order)
624
-    {
625
-        $this->set('EVT_order', $order);
626
-    }
627
-
628
-
629
-    /**
630
-     * @param $short_desc
631
-     * @throws EE_Error
632
-     * @throws ReflectionException
633
-     */
634
-    public function set_short_description($short_desc)
635
-    {
636
-        $this->set('EVT_short_desc', $short_desc);
637
-    }
638
-
639
-
640
-    /**
641
-     * @param $slug
642
-     * @throws EE_Error
643
-     * @throws ReflectionException
644
-     */
645
-    public function set_slug($slug)
646
-    {
647
-        $this->set('EVT_slug', $slug);
648
-    }
649
-
650
-
651
-    /**
652
-     * @param $timezone_string
653
-     * @throws EE_Error
654
-     * @throws ReflectionException
655
-     */
656
-    public function set_timezone_string($timezone_string)
657
-    {
658
-        $this->set('EVT_timezone_string', $timezone_string);
659
-    }
660
-
661
-
662
-    /**
663
-     * @param $visible_on
664
-     * @throws EE_Error
665
-     * @throws ReflectionException
666
-     */
667
-    public function set_visible_on($visible_on)
668
-    {
669
-        $this->set('EVT_visible_on', $visible_on);
670
-    }
671
-
672
-
673
-    /**
674
-     * @param $wp_user
675
-     * @throws EE_Error
676
-     * @throws ReflectionException
677
-     */
678
-    public function set_wp_user($wp_user)
679
-    {
680
-        $this->set('EVT_wp_user', $wp_user);
681
-    }
682
-
683
-
684
-    /**
685
-     * @param $default_registration_status
686
-     * @throws EE_Error
687
-     * @throws ReflectionException
688
-     */
689
-    public function set_default_registration_status($default_registration_status)
690
-    {
691
-        $this->set('EVT_default_registration_status', $default_registration_status);
692
-    }
693
-
694
-
695
-    /**
696
-     * @param $donations
697
-     * @throws EE_Error
698
-     * @throws ReflectionException
699
-     */
700
-    public function set_donations($donations)
701
-    {
702
-        $this->set('EVT_donations', $donations);
703
-    }
704
-
705
-
706
-    /**
707
-     * Adds a venue to this event
708
-     *
709
-     * @param EE_Venue /int $venue_id_or_obj
710
-     * @return EE_Venue
711
-     * @throws EE_Error
712
-     * @throws ReflectionException
713
-     */
714
-    public function add_venue($venue_id_or_obj): EE_Venue
715
-    {
716
-        return $this->_add_relation_to($venue_id_or_obj, 'Venue');
717
-    }
718
-
719
-
720
-    /**
721
-     * Removes a venue from the event
722
-     *
723
-     * @param EE_Venue /int $venue_id_or_obj
724
-     * @return EE_Venue|null
725
-     * @throws EE_Error
726
-     * @throws ReflectionException
727
-     */
728
-    public function remove_venue($venue_id_or_obj): ?EE_Venue
729
-    {
730
-        return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
731
-    }
732
-
733
-
734
-    /**
735
-     * Gets all the venues related ot the event. May provide additional $query_params if desired
736
-     *
737
-     * @param array $query_params @see
738
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
739
-     * @return EE_Venue[]
740
-     * @throws EE_Error
741
-     * @throws ReflectionException
742
-     */
743
-    public function venues(array $query_params = []): array
744
-    {
745
-        return $this->get_many_related('Venue', $query_params);
746
-    }
747
-
748
-
749
-    /**
750
-     * check if event id is present and if event is published
751
-     *
752
-     * @return boolean true yes, false no
753
-     * @throws EE_Error
754
-     */
755
-    private function _has_ID_and_is_published(): bool
756
-    {
757
-        // first check if event id is present and not NULL,
758
-        // then check if this event is published (or any of the equivalent "published" statuses)
759
-        return
760
-            $this->ID() && $this->ID() !== null
761
-            && (
762
-                $this->status() === 'publish'
763
-                || $this->status() === EEM_Event::sold_out
764
-                || $this->status() === EEM_Event::postponed
765
-                || $this->status() === EEM_Event::cancelled
766
-            );
767
-    }
768
-
769
-
770
-    /**
771
-     * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
772
-     *
773
-     * @return boolean true yes, false no
774
-     * @throws EE_Error
775
-     * @throws ReflectionException
776
-     */
777
-    public function is_upcoming(): bool
778
-    {
779
-        // check if event id is present and if this event is published
780
-        if ($this->is_inactive()) {
781
-            return false;
782
-        }
783
-        // set initial value
784
-        $upcoming = false;
785
-        // next let's get all datetimes and loop through them
786
-        $datetimes = $this->datetimes_in_chronological_order();
787
-        foreach ($datetimes as $datetime) {
788
-            if ($datetime instanceof EE_Datetime) {
789
-                // if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
790
-                if ($datetime->is_expired()) {
791
-                    continue;
792
-                }
793
-                // if this dtt is active then we return false.
794
-                if ($datetime->is_active()) {
795
-                    return false;
796
-                }
797
-                // otherwise let's check upcoming status
798
-                $upcoming = $datetime->is_upcoming();
799
-            }
800
-        }
801
-        return $upcoming;
802
-    }
803
-
804
-
805
-    /**
806
-     * @return bool
807
-     * @throws EE_Error
808
-     * @throws ReflectionException
809
-     */
810
-    public function is_active(): bool
811
-    {
812
-        // check if event id is present and if this event is published
813
-        if ($this->is_inactive()) {
814
-            return false;
815
-        }
816
-        // set initial value
817
-        $active = false;
818
-        // next let's get all datetimes and loop through them
819
-        $datetimes = $this->datetimes_in_chronological_order();
820
-        foreach ($datetimes as $datetime) {
821
-            if ($datetime instanceof EE_Datetime) {
822
-                // if this dtt is expired then we continue cause one of the other datetimes might be active.
823
-                if ($datetime->is_expired()) {
824
-                    continue;
825
-                }
826
-                // if this dtt is upcoming then we return false.
827
-                if ($datetime->is_upcoming()) {
828
-                    return false;
829
-                }
830
-                // otherwise let's check active status
831
-                $active = $datetime->is_active();
832
-            }
833
-        }
834
-        return $active;
835
-    }
836
-
837
-
838
-    /**
839
-     * @return bool
840
-     * @throws EE_Error
841
-     * @throws ReflectionException
842
-     */
843
-    public function is_expired(): bool
844
-    {
845
-        // check if event id is present and if this event is published
846
-        if ($this->is_inactive()) {
847
-            return false;
848
-        }
849
-        // set initial value
850
-        $expired = false;
851
-        // first let's get all datetimes and loop through them
852
-        $datetimes = $this->datetimes_in_chronological_order();
853
-        foreach ($datetimes as $datetime) {
854
-            if ($datetime instanceof EE_Datetime) {
855
-                // if this dtt is upcoming or active then we return false.
856
-                if ($datetime->is_upcoming() || $datetime->is_active()) {
857
-                    return false;
858
-                }
859
-                // otherwise let's check active status
860
-                $expired = $datetime->is_expired();
861
-            }
862
-        }
863
-        return $expired;
864
-    }
865
-
866
-
867
-    /**
868
-     * @return bool
869
-     * @throws EE_Error
870
-     */
871
-    public function is_inactive(): bool
872
-    {
873
-        // check if event id is present and if this event is published
874
-        if ($this->_has_ID_and_is_published()) {
875
-            return false;
876
-        }
877
-        return true;
878
-    }
879
-
880
-
881
-    /**
882
-     * calculate spaces remaining based on "saleable" tickets
883
-     *
884
-     * @param array $tickets
885
-     * @param bool  $filtered
886
-     * @return int|float
887
-     * @throws EE_Error
888
-     * @throws DomainException
889
-     * @throws UnexpectedEntityException
890
-     */
891
-    public function spaces_remaining(array $tickets = [], bool $filtered = true)
892
-    {
893
-        $this->getAvailableSpacesCalculator()->setActiveTickets($tickets);
894
-        $spaces_remaining = $this->getAvailableSpacesCalculator()->spacesRemaining();
895
-        return $filtered
896
-            ? apply_filters(
897
-                'FHEE_EE_Event__spaces_remaining',
898
-                $spaces_remaining,
899
-                $this,
900
-                $tickets
901
-            )
902
-            : $spaces_remaining;
903
-    }
904
-
905
-
906
-    /**
907
-     *    perform_sold_out_status_check
908
-     *    checks all of this event's datetime reg_limit - sold values to determine if ANY datetimes have spaces
909
-     *    available... if NOT, then the event status will get toggled to 'sold_out'
910
-     *
911
-     * @return bool    return the ACTUAL sold out state.
912
-     * @throws EE_Error
913
-     * @throws DomainException
914
-     * @throws UnexpectedEntityException
915
-     * @throws ReflectionException
916
-     */
917
-    public function perform_sold_out_status_check(): bool
918
-    {
919
-        // get all tickets
920
-        $tickets     = $this->tickets(
921
-            [
922
-                'default_where_conditions' => 'none',
923
-                'order_by'                 => ['TKT_qty' => 'ASC'],
924
-            ]
925
-        );
926
-        $all_expired = true;
927
-        foreach ($tickets as $ticket) {
928
-            if (! $ticket->is_expired()) {
929
-                $all_expired = false;
930
-                break;
931
-            }
932
-        }
933
-        // if all the tickets are just expired, then don't update the event status to sold out
934
-        if ($all_expired) {
935
-            return true;
936
-        }
937
-        $spaces_remaining = $this->spaces_remaining($tickets);
938
-        if ($spaces_remaining < 1) {
939
-            if ($this->status() !== EEM_Event::post_status_private) {
940
-                $this->set_status(EEM_Event::sold_out);
941
-                $this->save();
942
-            }
943
-            $sold_out = true;
944
-        } else {
945
-            $sold_out = false;
946
-            // was event previously marked as sold out ?
947
-            if ($this->status() === EEM_Event::sold_out) {
948
-                // revert status to previous value, if it was set
949
-                $previous_event_status = $this->get_post_meta('_previous_event_status', true);
950
-                if ($previous_event_status) {
951
-                    $this->set_status($previous_event_status);
952
-                    $this->save();
953
-                }
954
-            }
955
-        }
956
-        do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
957
-        return $sold_out;
958
-    }
959
-
960
-
961
-    /**
962
-     * This returns the total remaining spaces for sale on this event.
963
-     *
964
-     * @return float|int
965
-     * @throws EE_Error
966
-     * @throws DomainException
967
-     * @throws UnexpectedEntityException
968
-     * @uses EE_Event::total_available_spaces()
969
-     */
970
-    public function spaces_remaining_for_sale()
971
-    {
972
-        return $this->total_available_spaces(true);
973
-    }
974
-
975
-
976
-    /**
977
-     * This returns the total spaces available for an event
978
-     * while considering all the qtys on the tickets and the reg limits
979
-     * on the datetimes attached to this event.
980
-     *
981
-     * @param bool $consider_sold   Whether to consider any tickets that have already sold in our calculation.
982
-     *                              If this is false, then we return the most tickets that could ever be sold
983
-     *                              for this event with the datetime and tickets setup on the event under optimal
984
-     *                              selling conditions.  Otherwise we return a live calculation of spaces available
985
-     *                              based on tickets sold.  Depending on setup and stage of sales, this
986
-     *                              may appear to equal remaining tickets.  However, the more tickets are
987
-     *                              sold out, the more accurate the "live" total is.
988
-     * @return float|int
989
-     * @throws EE_Error
990
-     * @throws DomainException
991
-     * @throws UnexpectedEntityException
992
-     */
993
-    public function total_available_spaces(bool $consider_sold = false)
994
-    {
995
-        $spaces_available = $consider_sold
996
-            ? $this->getAvailableSpacesCalculator()->spacesRemaining()
997
-            : $this->getAvailableSpacesCalculator()->totalSpacesAvailable();
998
-        return apply_filters(
999
-            'FHEE_EE_Event__total_available_spaces__spaces_available',
1000
-            $spaces_available,
1001
-            $this,
1002
-            $this->getAvailableSpacesCalculator()->getDatetimes(),
1003
-            $this->getAvailableSpacesCalculator()->getActiveTickets()
1004
-        );
1005
-    }
1006
-
1007
-
1008
-    /**
1009
-     * Checks if the event is set to sold out
1010
-     *
1011
-     * @param bool $actual  whether or not to perform calculations to not only figure the
1012
-     *                      actual status but also to flip the status if necessary to sold
1013
-     *                      out If false, we just check the existing status of the event
1014
-     * @return boolean
1015
-     * @throws EE_Error
1016
-     * @throws ReflectionException
1017
-     */
1018
-    public function is_sold_out(bool $actual = false): bool
1019
-    {
1020
-        if (! $actual) {
1021
-            return $this->status() === EEM_Event::sold_out;
1022
-        }
1023
-        return $this->perform_sold_out_status_check();
1024
-    }
1025
-
1026
-
1027
-    /**
1028
-     * Checks if the event is marked as postponed
1029
-     *
1030
-     * @return boolean
1031
-     * @throws EE_Error
1032
-     * @throws EE_Error
1033
-     */
1034
-    public function is_postponed(): bool
1035
-    {
1036
-        return $this->status() === EEM_Event::postponed;
1037
-    }
1038
-
1039
-
1040
-    /**
1041
-     * Checks if the event is marked as cancelled
1042
-     *
1043
-     * @return boolean
1044
-     * @throws EE_Error
1045
-     * @throws EE_Error
1046
-     */
1047
-    public function is_cancelled(): bool
1048
-    {
1049
-        return $this->status() === EEM_Event::cancelled;
1050
-    }
1051
-
1052
-
1053
-    /**
1054
-     * Get the logical active status in a hierarchical order for all the datetimes.  Note
1055
-     * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1056
-     * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1057
-     * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1058
-     * the event is considered expired.
1059
-     * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a
1060
-     * status set on the EVENT when it is not published and thus is done
1061
-     *
1062
-     * @param bool $reset
1063
-     * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1064
-     * @throws EE_Error
1065
-     * @throws ReflectionException
1066
-     */
1067
-    public function get_active_status(bool $reset = false)
1068
-    {
1069
-        // if the active status has already been set, then just use that value (unless we are resetting it)
1070
-        if (! empty($this->_active_status) && ! $reset) {
1071
-            return $this->_active_status;
1072
-        }
1073
-        // first check if event id is present on this object
1074
-        if (! $this->ID()) {
1075
-            return false;
1076
-        }
1077
-        $where_params_for_event = [['EVT_ID' => $this->ID()]];
1078
-        // if event is published:
1079
-        if ($this->status() === EEM_Event::post_status_publish || $this->status() === EEM_Event::post_status_private) {
1080
-            // active?
1081
-            if (
1082
-                EEM_Datetime::instance()->get_datetime_count_for_status(
1083
-                    EE_Datetime::active,
1084
-                    $where_params_for_event
1085
-                ) > 0
1086
-            ) {
1087
-                $this->_active_status = EE_Datetime::active;
1088
-            } else {
1089
-                // upcoming?
1090
-                if (
1091
-                    EEM_Datetime::instance()->get_datetime_count_for_status(
1092
-                        EE_Datetime::upcoming,
1093
-                        $where_params_for_event
1094
-                    ) > 0
1095
-                ) {
1096
-                    $this->_active_status = EE_Datetime::upcoming;
1097
-                } else {
1098
-                    // expired?
1099
-                    if (
1100
-                        EEM_Datetime::instance()->get_datetime_count_for_status(
1101
-                            EE_Datetime::expired,
1102
-                            $where_params_for_event
1103
-                        ) > 0
1104
-                    ) {
1105
-                        $this->_active_status = EE_Datetime::expired;
1106
-                    } else {
1107
-                        // it would be odd if things make it this far because it basically means there are no datetime's
1108
-                        // attached to the event.  So in this case it will just be considered inactive.
1109
-                        $this->_active_status = EE_Datetime::inactive;
1110
-                    }
1111
-                }
1112
-            }
1113
-        } else {
1114
-            // the event is not published, so let's just set it's active status according to its' post status
1115
-            switch ($this->status()) {
1116
-                case EEM_Event::sold_out:
1117
-                    $this->_active_status = EE_Datetime::sold_out;
1118
-                    break;
1119
-                case EEM_Event::cancelled:
1120
-                    $this->_active_status = EE_Datetime::cancelled;
1121
-                    break;
1122
-                case EEM_Event::postponed:
1123
-                    $this->_active_status = EE_Datetime::postponed;
1124
-                    break;
1125
-                default:
1126
-                    $this->_active_status = EE_Datetime::inactive;
1127
-            }
1128
-        }
1129
-        return $this->_active_status;
1130
-    }
1131
-
1132
-
1133
-    /**
1134
-     *    pretty_active_status
1135
-     *
1136
-     * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1137
-     * @return string string
1138
-     * @throws EE_Error
1139
-     * @throws ReflectionException
1140
-     */
1141
-    public function pretty_active_status(bool $echo = true): string
1142
-    {
1143
-        $active_status = $this->get_active_status();
1144
-        $status        = '<span class="ee-status event-active-status-'
1145
-                         . $active_status
1146
-                         . '">'
1147
-                         . EEH_Template::pretty_status($active_status, false, 'sentence')
1148
-                         . '</span>';
1149
-        if ($echo) {
1150
-            echo $status;
1151
-            return '';
1152
-        }
1153
-        return $status;
1154
-    }
1155
-
1156
-
1157
-    /**
1158
-     * @return bool|int
1159
-     * @throws EE_Error
1160
-     * @throws ReflectionException
1161
-     */
1162
-    public function get_number_of_tickets_sold()
1163
-    {
1164
-        $tkt_sold = 0;
1165
-        if (! $this->ID()) {
1166
-            return 0;
1167
-        }
1168
-        $datetimes = $this->datetimes();
1169
-        foreach ($datetimes as $datetime) {
1170
-            if ($datetime instanceof EE_Datetime) {
1171
-                $tkt_sold += $datetime->sold();
1172
-            }
1173
-        }
1174
-        return $tkt_sold;
1175
-    }
1176
-
1177
-
1178
-    /**
1179
-     * This just returns a count of all the registrations for this event
1180
-     *
1181
-     * @return int
1182
-     * @throws EE_Error
1183
-     * @throws ReflectionException
1184
-     */
1185
-    public function get_count_of_all_registrations(): int
1186
-    {
1187
-        return EEM_Event::instance()->count_related($this, 'Registration');
1188
-    }
1189
-
1190
-
1191
-    /**
1192
-     * This returns the ticket with the earliest start time that is
1193
-     * available for this event (across all datetimes attached to the event)
1194
-     *
1195
-     * @return EE_Ticket|null
1196
-     * @throws EE_Error
1197
-     * @throws ReflectionException
1198
-     */
1199
-    public function get_ticket_with_earliest_start_time(): ?EE_Ticket
1200
-    {
1201
-        $where['Datetime.EVT_ID'] = $this->ID();
1202
-        $query_params             = [$where, 'order_by' => ['TKT_start_date' => 'ASC']];
1203
-        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1204
-    }
1205
-
1206
-
1207
-    /**
1208
-     * This returns the ticket with the latest end time that is available
1209
-     * for this event (across all datetimes attached to the event)
1210
-     *
1211
-     * @return EE_Ticket|null
1212
-     * @throws EE_Error
1213
-     * @throws ReflectionException
1214
-     */
1215
-    public function get_ticket_with_latest_end_time(): ?EE_Ticket
1216
-    {
1217
-        $where['Datetime.EVT_ID'] = $this->ID();
1218
-        $query_params             = [$where, 'order_by' => ['TKT_end_date' => 'DESC']];
1219
-        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1220
-    }
1221
-
1222
-
1223
-    /**
1224
-     * This returns the number of different ticket types currently on sale for this event.
1225
-     *
1226
-     * @return int
1227
-     * @throws EE_Error
1228
-     * @throws ReflectionException
1229
-     */
1230
-    public function countTicketsOnSale(): int
1231
-    {
1232
-        $where = [
1233
-            'Datetime.EVT_ID' => $this->ID(),
1234
-            'TKT_start_date'  => ['<', time()],
1235
-            'TKT_end_date'    => ['>', time()],
1236
-        ];
1237
-        return EEM_Ticket::instance()->count([$where]);
1238
-    }
1239
-
1240
-
1241
-    /**
1242
-     * This returns whether there are any tickets on sale for this event.
1243
-     *
1244
-     * @return bool true = YES tickets on sale.
1245
-     * @throws EE_Error
1246
-     * @throws ReflectionException
1247
-     */
1248
-    public function tickets_on_sale(): bool
1249
-    {
1250
-        return $this->countTicketsOnSale() > 0;
1251
-    }
1252
-
1253
-
1254
-    /**
1255
-     * Gets the URL for viewing this event on the front-end. Overrides parent
1256
-     * to check for an external URL first
1257
-     *
1258
-     * @return string
1259
-     * @throws EE_Error
1260
-     */
1261
-    public function get_permalink(): string
1262
-    {
1263
-        if ($this->external_url()) {
1264
-            return $this->external_url();
1265
-        }
1266
-        return parent::get_permalink();
1267
-    }
1268
-
1269
-
1270
-    /**
1271
-     * Gets the first term for 'espresso_event_categories' we can find
1272
-     *
1273
-     * @param array $query_params @see
1274
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1275
-     * @return EE_Term|null
1276
-     * @throws EE_Error
1277
-     * @throws ReflectionException
1278
-     */
1279
-    public function first_event_category(array $query_params = []): ?EE_Term
1280
-    {
1281
-        $query_params[0]['Term_Taxonomy.taxonomy']     = 'espresso_event_categories';
1282
-        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1283
-        return EEM_Term::instance()->get_one($query_params);
1284
-    }
1285
-
1286
-
1287
-    /**
1288
-     * Gets all terms for 'espresso_event_categories' we can find
1289
-     *
1290
-     * @param array $query_params
1291
-     * @return EE_Term[]
1292
-     * @throws EE_Error
1293
-     * @throws ReflectionException
1294
-     */
1295
-    public function get_all_event_categories(array $query_params = []): array
1296
-    {
1297
-        $query_params[0]['Term_Taxonomy.taxonomy']     = 'espresso_event_categories';
1298
-        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1299
-        return EEM_Term::instance()->get_all($query_params);
1300
-    }
1301
-
1302
-
1303
-    /**
1304
-     * Adds a question group to this event
1305
-     *
1306
-     * @param EE_Question_Group|int $question_group_id_or_obj
1307
-     * @param bool                  $for_primary if true, the question group will be added for the primary
1308
-     *                                           registrant, if false will be added for others. default: false
1309
-     * @return EE_Question_Group
1310
-     * @throws EE_Error
1311
-     * @throws InvalidArgumentException
1312
-     * @throws InvalidDataTypeException
1313
-     * @throws InvalidInterfaceException
1314
-     * @throws ReflectionException
1315
-     */
1316
-    public function add_question_group($question_group_id_or_obj, bool $for_primary = false): EE_Question_Group
1317
-    {
1318
-        // If the row already exists, it will be updated. If it doesn't, it will be inserted.
1319
-        // That's in EE_HABTM_Relation::add_relation_to().
1320
-        return $this->_add_relation_to(
1321
-            $question_group_id_or_obj,
1322
-            'Question_Group',
1323
-            [
1324
-                EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary) => true,
1325
-            ]
1326
-        );
1327
-    }
1328
-
1329
-
1330
-    /**
1331
-     * Removes a question group from the event
1332
-     *
1333
-     * @param EE_Question_Group|int $question_group_id_or_obj
1334
-     * @param bool                  $for_primary if true, the question group will be removed from the primary
1335
-     *                                           registrant, if false will be removed from others. default: false
1336
-     * @return EE_Question_Group
1337
-     * @throws EE_Error
1338
-     * @throws InvalidArgumentException
1339
-     * @throws ReflectionException
1340
-     * @throws InvalidDataTypeException
1341
-     * @throws InvalidInterfaceException
1342
-     */
1343
-    public function remove_question_group($question_group_id_or_obj, bool $for_primary = false): EE_Question_Group
1344
-    {
1345
-        // If the question group is used for the other type (primary or additional)
1346
-        // then just update it. If not, delete it outright.
1347
-        $existing_relation = $this->get_first_related(
1348
-            'Event_Question_Group',
1349
-            [
1350
-                [
1351
-                    'QSG_ID' => EEM_Question_Group::instance()->ensure_is_ID($question_group_id_or_obj),
1352
-                ],
1353
-            ]
1354
-        );
1355
-        $field_to_update   = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1356
-        $other_field       = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1357
-        if ($existing_relation->get($other_field) === false) {
1358
-            // Delete it. It's now no longer for primary or additional question groups.
1359
-            return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
1360
-        }
1361
-        // Just update it. They'll still use this question group for the other category
1362
-        $existing_relation->save(
1363
-            [
1364
-                $field_to_update => false,
1365
-            ]
1366
-        );
1367
-        return $question_group_id_or_obj;
1368
-    }
1369
-
1370
-
1371
-    /**
1372
-     * Gets all the question groups, ordering them by QSG_order ascending
1373
-     *
1374
-     * @param array $query_params @see
1375
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1376
-     * @return EE_Question_Group[]
1377
-     * @throws EE_Error
1378
-     * @throws ReflectionException
1379
-     */
1380
-    public function question_groups(array $query_params = []): array
1381
-    {
1382
-        $query_params = ! empty($query_params)
1383
-            ? $query_params
1384
-            : ['order_by' => ['QSG_order' => 'ASC']];
1385
-        return $this->get_many_related('Question_Group', $query_params);
1386
-    }
1387
-
1388
-
1389
-    /**
1390
-     * Implementation for EEI_Has_Icon interface method.
1391
-     *
1392
-     * @return string
1393
-     * @see EEI_Visual_Representation for comments
1394
-     */
1395
-    public function get_icon(): string
1396
-    {
1397
-        return '<span class="dashicons dashicons-flag"></span>';
1398
-    }
1399
-
1400
-
1401
-    /**
1402
-     * Implementation for EEI_Admin_Links interface method.
1403
-     *
1404
-     * @return string
1405
-     * @throws EE_Error
1406
-     * @see EEI_Admin_Links for comments
1407
-     */
1408
-    public function get_admin_details_link(): string
1409
-    {
1410
-        return $this->get_admin_edit_link();
1411
-    }
1412
-
1413
-
1414
-    /**
1415
-     * Implementation for EEI_Admin_Links interface method.
1416
-     *
1417
-     * @return string
1418
-     * @throws EE_Error
1419
-     * @see EEI_Admin_Links for comments
1420
-     */
1421
-    public function get_admin_edit_link(): string
1422
-    {
1423
-        return EEH_URL::add_query_args_and_nonce(
1424
-            [
1425
-                'page'   => 'espresso_events',
1426
-                'action' => 'edit',
1427
-                'post'   => $this->ID(),
1428
-            ],
1429
-            admin_url('admin.php')
1430
-        );
1431
-    }
1432
-
1433
-
1434
-    /**
1435
-     * Implementation for EEI_Admin_Links interface method.
1436
-     *
1437
-     * @return string
1438
-     * @see EEI_Admin_Links for comments
1439
-     */
1440
-    public function get_admin_settings_link(): string
1441
-    {
1442
-        return EEH_URL::add_query_args_and_nonce(
1443
-            [
1444
-                'page'   => 'espresso_events',
1445
-                'action' => 'default_event_settings',
1446
-            ],
1447
-            admin_url('admin.php')
1448
-        );
1449
-    }
1450
-
1451
-
1452
-    /**
1453
-     * Implementation for EEI_Admin_Links interface method.
1454
-     *
1455
-     * @return string
1456
-     * @see EEI_Admin_Links for comments
1457
-     */
1458
-    public function get_admin_overview_link(): string
1459
-    {
1460
-        return EEH_URL::add_query_args_and_nonce(
1461
-            [
1462
-                'page'   => 'espresso_events',
1463
-                'action' => 'default',
1464
-            ],
1465
-            admin_url('admin.php')
1466
-        );
1467
-    }
18
+	/**
19
+	 * cached value for the the logical active status for the event
20
+	 *
21
+	 * @see get_active_status()
22
+	 * @var string
23
+	 */
24
+	protected $_active_status = '';
25
+
26
+	/**
27
+	 * This is just used for caching the Primary Datetime for the Event on initial retrieval
28
+	 *
29
+	 * @var EE_Datetime
30
+	 */
31
+	protected $_Primary_Datetime;
32
+
33
+	/**
34
+	 * @var EventSpacesCalculator $available_spaces_calculator
35
+	 */
36
+	protected $available_spaces_calculator;
37
+
38
+
39
+	/**
40
+	 * @param array  $props_n_values          incoming values
41
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
42
+	 *                                        used.)
43
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
44
+	 *                                        date_format and the second value is the time format
45
+	 * @return EE_Event
46
+	 * @throws EE_Error
47
+	 * @throws ReflectionException
48
+	 */
49
+	public static function new_instance(
50
+		array $props_n_values = [],
51
+		string $timezone = '',
52
+		array $date_formats = []
53
+	): EE_Event {
54
+		/** @var EE_Event $has_object */
55
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
56
+		return $has_object
57
+			?: new self($props_n_values, false, $timezone, $date_formats);
58
+	}
59
+
60
+
61
+	/**
62
+	 * @param array  $props_n_values  incoming values from the database
63
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
64
+	 *                                the website will be used.
65
+	 * @return EE_Event
66
+	 * @throws EE_Error
67
+	 * @throws ReflectionException
68
+	 */
69
+	public static function new_instance_from_db(array $props_n_values = [], string $timezone = ''): EE_Event
70
+	{
71
+		return new self($props_n_values, true, $timezone);
72
+	}
73
+
74
+
75
+	/**
76
+	 * @return EventSpacesCalculator
77
+	 * @throws EE_Error
78
+	 */
79
+	public function getAvailableSpacesCalculator(): EventSpacesCalculator
80
+	{
81
+		if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
82
+			$this->available_spaces_calculator = new EventSpacesCalculator($this);
83
+		}
84
+		return $this->available_spaces_calculator;
85
+	}
86
+
87
+
88
+	/**
89
+	 * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
90
+	 *
91
+	 * @param string $field_name
92
+	 * @param mixed  $field_value
93
+	 * @param bool   $use_default
94
+	 * @throws EE_Error
95
+	 * @throws ReflectionException
96
+	 */
97
+	public function set(string $field_name, $field_value, bool $use_default = false)
98
+	{
99
+		switch ($field_name) {
100
+			case 'status':
101
+				$this->set_status($field_value, $use_default);
102
+				break;
103
+			default:
104
+				parent::set($field_name, $field_value, $use_default);
105
+		}
106
+	}
107
+
108
+
109
+	/**
110
+	 * set_status
111
+	 * Checks if event status is being changed to SOLD OUT
112
+	 * and updates event meta data with previous event status
113
+	 * so that we can revert things if/when the event is no longer sold out
114
+	 *
115
+	 * @param string|null $status
116
+	 * @return void
117
+	 * @throws EE_Error
118
+	 * @throws ReflectionException
119
+	 */
120
+	public function set_status(string $status = null, bool $use_default = false)
121
+	{
122
+		// if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
123
+		if (empty($status) && ! $use_default) {
124
+			return;
125
+		}
126
+		// get current Event status
127
+		$old_status = $this->status();
128
+		// if status has changed
129
+		if ($old_status !== $status) {
130
+			// TO sold_out
131
+			if ($status === EEM_Event::sold_out) {
132
+				// save the previous event status so that we can revert if the event is no longer sold out
133
+				$this->add_post_meta('_previous_event_status', $old_status);
134
+				do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $status);
135
+				// OR FROM  sold_out
136
+			} elseif ($old_status === EEM_Event::sold_out) {
137
+				$this->delete_post_meta('_previous_event_status');
138
+				do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $status);
139
+			}
140
+			// clear out the active status so that it gets reset the next time it is requested
141
+			$this->_active_status = null;
142
+			// update status
143
+			parent::set('status', $status, $use_default);
144
+			do_action('AHEE__EE_Event__set_status__after_update', $this);
145
+			return;
146
+		}
147
+		// even though the old value matches the new value, it's still good to
148
+		// allow the parent set method to have a say
149
+		parent::set('status', $status, $use_default);
150
+	}
151
+
152
+
153
+	/**
154
+	 * Gets all the datetimes for this event
155
+	 *
156
+	 * @param array $query_params @see
157
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
158
+	 * @return EE_Datetime[]
159
+	 * @throws EE_Error
160
+	 * @throws ReflectionException
161
+	 */
162
+	public function datetimes(array $query_params = []): array
163
+	{
164
+		return $this->get_many_related('Datetime', $query_params);
165
+	}
166
+
167
+
168
+	/**
169
+	 * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
170
+	 *
171
+	 * @return EE_Datetime[]
172
+	 * @throws EE_Error
173
+	 * @throws ReflectionException
174
+	 */
175
+	public function datetimes_in_chronological_order(): array
176
+	{
177
+		return $this->get_many_related('Datetime', ['order_by' => ['DTT_EVT_start' => 'ASC']]);
178
+	}
179
+
180
+
181
+	/**
182
+	 * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
183
+	 * @darren, we should probably UNSET timezone on the EEM_Datetime model
184
+	 * after running our query, so that this timezone isn't set for EVERY query
185
+	 * on EEM_Datetime for the rest of the request, no?
186
+	 *
187
+	 * @param boolean $show_expired whether or not to include expired events
188
+	 * @param boolean $show_deleted whether or not to include deleted events
189
+	 * @param int    $limit
190
+	 * @return EE_Datetime[]
191
+	 * @throws EE_Error
192
+	 * @throws ReflectionException
193
+	 */
194
+	public function datetimes_ordered(bool $show_expired = true, bool $show_deleted = false, int $limit = 0): array
195
+	{
196
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
197
+			$this->ID(),
198
+			$show_expired,
199
+			$show_deleted,
200
+			$limit
201
+		);
202
+	}
203
+
204
+
205
+	/**
206
+	 * Returns one related datetime. Mostly only used by some legacy code.
207
+	 *
208
+	 * @return EE_Datetime
209
+	 * @throws EE_Error
210
+	 * @throws ReflectionException
211
+	 */
212
+	public function first_datetime(): EE_Datetime
213
+	{
214
+		return $this->get_first_related('Datetime');
215
+	}
216
+
217
+
218
+	/**
219
+	 * Returns the 'primary' datetime for the event
220
+	 *
221
+	 * @param bool $try_to_exclude_expired
222
+	 * @param bool $try_to_exclude_deleted
223
+	 * @return EE_Datetime
224
+	 * @throws EE_Error
225
+	 * @throws ReflectionException
226
+	 */
227
+	public function primary_datetime(
228
+		bool $try_to_exclude_expired = true,
229
+		bool $try_to_exclude_deleted = true
230
+	): EE_Datetime {
231
+		if (! empty($this->_Primary_Datetime)) {
232
+			return $this->_Primary_Datetime;
233
+		}
234
+		$this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
235
+			$this->ID(),
236
+			$try_to_exclude_expired,
237
+			$try_to_exclude_deleted
238
+		);
239
+		return $this->_Primary_Datetime;
240
+	}
241
+
242
+
243
+	/**
244
+	 * Gets all the tickets available for purchase of this event
245
+	 *
246
+	 * @param array $query_params @see
247
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
248
+	 * @return EE_Ticket[]
249
+	 * @throws EE_Error
250
+	 * @throws ReflectionException
251
+	 */
252
+	public function tickets(array $query_params = []): array
253
+	{
254
+		// first get all datetimes
255
+		$datetimes = $this->datetimes_ordered();
256
+		if (! $datetimes) {
257
+			return [];
258
+		}
259
+		$datetime_ids = [];
260
+		foreach ($datetimes as $datetime) {
261
+			$datetime_ids[] = $datetime->ID();
262
+		}
263
+		$where_params = ['Datetime.DTT_ID' => ['IN', $datetime_ids]];
264
+		// if incoming $query_params has where conditions let's merge but not override existing.
265
+		if (is_array($query_params) && isset($query_params[0])) {
266
+			$where_params = array_merge($query_params[0], $where_params);
267
+			unset($query_params[0]);
268
+		}
269
+		// now add $where_params to $query_params
270
+		$query_params[0] = $where_params;
271
+		return EEM_Ticket::instance()->get_all($query_params);
272
+	}
273
+
274
+
275
+	/**
276
+	 * get all unexpired untrashed tickets
277
+	 *
278
+	 * @return EE_Ticket[]
279
+	 * @throws EE_Error
280
+	 * @throws ReflectionException
281
+	 */
282
+	public function active_tickets(): array
283
+	{
284
+		return $this->tickets(
285
+			[
286
+				[
287
+					'TKT_end_date' => ['>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')],
288
+					'TKT_deleted'  => false,
289
+				],
290
+			]
291
+		);
292
+	}
293
+
294
+
295
+	/**
296
+	 * @return bool
297
+	 * @throws EE_Error
298
+	 */
299
+	public function additional_limit(): bool
300
+	{
301
+		return $this->get('EVT_additional_limit');
302
+	}
303
+
304
+
305
+	/**
306
+	 * @return bool
307
+	 * @throws EE_Error
308
+	 */
309
+	public function allow_overflow(): bool
310
+	{
311
+		return $this->get('EVT_allow_overflow');
312
+	}
313
+
314
+
315
+	/**
316
+	 * @return bool
317
+	 * @throws EE_Error
318
+	 */
319
+	public function created(): bool
320
+	{
321
+		return $this->get('EVT_created');
322
+	}
323
+
324
+
325
+	/**
326
+	 * @return bool
327
+	 * @throws EE_Error
328
+	 */
329
+	public function description(): bool
330
+	{
331
+		return $this->get('EVT_desc');
332
+	}
333
+
334
+
335
+	/**
336
+	 * Runs do_shortcode and wpautop on the description
337
+	 *
338
+	 * @return string of html
339
+	 * @throws EE_Error
340
+	 */
341
+	public function description_filtered(): string
342
+	{
343
+		return $this->get_pretty('EVT_desc');
344
+	}
345
+
346
+
347
+	/**
348
+	 * @return bool
349
+	 * @throws EE_Error
350
+	 */
351
+	public function display_description(): bool
352
+	{
353
+		return $this->get('EVT_display_desc');
354
+	}
355
+
356
+
357
+	/**
358
+	 * @return bool
359
+	 * @throws EE_Error
360
+	 */
361
+	public function display_ticket_selector(): bool
362
+	{
363
+		return (bool) $this->get('EVT_display_ticket_selector');
364
+	}
365
+
366
+
367
+	/**
368
+	 * @return bool
369
+	 * @throws EE_Error
370
+	 */
371
+	public function external_url(): bool
372
+	{
373
+		return $this->get('EVT_external_URL');
374
+	}
375
+
376
+
377
+	/**
378
+	 * @return bool
379
+	 * @throws EE_Error
380
+	 */
381
+	public function member_only(): bool
382
+	{
383
+		return $this->get('EVT_member_only');
384
+	}
385
+
386
+
387
+	/**
388
+	 * @return bool
389
+	 * @throws EE_Error
390
+	 */
391
+	public function phone(): bool
392
+	{
393
+		return $this->get('EVT_phone');
394
+	}
395
+
396
+
397
+	/**
398
+	 * @return bool
399
+	 * @throws EE_Error
400
+	 */
401
+	public function modified(): bool
402
+	{
403
+		return $this->get('EVT_modified');
404
+	}
405
+
406
+
407
+	/**
408
+	 * @return bool
409
+	 * @throws EE_Error
410
+	 */
411
+	public function name(): bool
412
+	{
413
+		return $this->get('EVT_name');
414
+	}
415
+
416
+
417
+	/**
418
+	 * @return bool
419
+	 * @throws EE_Error
420
+	 */
421
+	public function order(): bool
422
+	{
423
+		return $this->get('EVT_order');
424
+	}
425
+
426
+
427
+	/**
428
+	 * @return bool|string
429
+	 * @throws EE_Error
430
+	 */
431
+	public function default_registration_status()
432
+	{
433
+		$event_default_registration_status = $this->get('EVT_default_registration_status');
434
+		return ! empty($event_default_registration_status)
435
+			? $event_default_registration_status
436
+			: EE_Registry::instance()->CFG->registration->default_STS_ID;
437
+	}
438
+
439
+
440
+	/**
441
+	 * @param int  $num_words
442
+	 * @param string|null $more
443
+	 * @param bool $not_full_desc
444
+	 * @return bool|string
445
+	 * @throws EE_Error
446
+	 */
447
+	public function short_description(int $num_words = 55, string $more = null, bool $not_full_desc = false)
448
+	{
449
+		$short_desc = $this->get('EVT_short_desc');
450
+		if (! empty($short_desc) || $not_full_desc) {
451
+			return $short_desc;
452
+		}
453
+		$full_desc = $this->get('EVT_desc');
454
+		return wp_trim_words($full_desc, $num_words, $more);
455
+	}
456
+
457
+
458
+	/**
459
+	 * @return string
460
+	 * @throws EE_Error
461
+	 */
462
+	public function slug(): string
463
+	{
464
+		return $this->get('EVT_slug');
465
+	}
466
+
467
+
468
+	/**
469
+	 * @return string
470
+	 * @throws EE_Error
471
+	 */
472
+	public function timezone_string(): string
473
+	{
474
+		return $this->get('EVT_timezone_string');
475
+	}
476
+
477
+
478
+	/**
479
+	 * @return string
480
+	 * @throws EE_Error
481
+	 */
482
+	public function visible_on(): string
483
+	{
484
+		return $this->get('EVT_visible_on');
485
+	}
486
+
487
+
488
+	/**
489
+	 * @return int
490
+	 * @throws EE_Error
491
+	 */
492
+	public function wp_user(): int
493
+	{
494
+		return $this->get('EVT_wp_user');
495
+	}
496
+
497
+
498
+	/**
499
+	 * @return bool
500
+	 * @throws EE_Error
501
+	 */
502
+	public function donations(): bool
503
+	{
504
+		return $this->get('EVT_donations');
505
+	}
506
+
507
+
508
+	/**
509
+	 * @param $limit
510
+	 * @throws EE_Error
511
+	 * @throws ReflectionException
512
+	 */
513
+	public function set_additional_limit($limit)
514
+	{
515
+		$this->set('EVT_additional_limit', $limit);
516
+	}
517
+
518
+
519
+	/**
520
+	 * @param $created
521
+	 * @throws EE_Error
522
+	 * @throws ReflectionException
523
+	 */
524
+	public function set_created($created)
525
+	{
526
+		$this->set('EVT_created', $created);
527
+	}
528
+
529
+
530
+	/**
531
+	 * @param $desc
532
+	 * @throws EE_Error
533
+	 * @throws ReflectionException
534
+	 */
535
+	public function set_description($desc)
536
+	{
537
+		$this->set('EVT_desc', $desc);
538
+	}
539
+
540
+
541
+	/**
542
+	 * @param $display_desc
543
+	 * @throws EE_Error
544
+	 * @throws ReflectionException
545
+	 */
546
+	public function set_display_description($display_desc)
547
+	{
548
+		$this->set('EVT_display_desc', $display_desc);
549
+	}
550
+
551
+
552
+	/**
553
+	 * @param $display_ticket_selector
554
+	 * @throws EE_Error
555
+	 * @throws ReflectionException
556
+	 */
557
+	public function set_display_ticket_selector($display_ticket_selector)
558
+	{
559
+		$this->set('EVT_display_ticket_selector', $display_ticket_selector);
560
+	}
561
+
562
+
563
+	/**
564
+	 * @param $external_url
565
+	 * @throws EE_Error
566
+	 * @throws ReflectionException
567
+	 */
568
+	public function set_external_url($external_url)
569
+	{
570
+		$this->set('EVT_external_URL', $external_url);
571
+	}
572
+
573
+
574
+	/**
575
+	 * @param $member_only
576
+	 * @throws EE_Error
577
+	 * @throws ReflectionException
578
+	 */
579
+	public function set_member_only($member_only)
580
+	{
581
+		$this->set('EVT_member_only', $member_only);
582
+	}
583
+
584
+
585
+	/**
586
+	 * @param $event_phone
587
+	 * @throws EE_Error
588
+	 * @throws ReflectionException
589
+	 */
590
+	public function set_event_phone($event_phone)
591
+	{
592
+		$this->set('EVT_phone', $event_phone);
593
+	}
594
+
595
+
596
+	/**
597
+	 * @param $modified
598
+	 * @throws EE_Error
599
+	 * @throws ReflectionException
600
+	 */
601
+	public function set_modified($modified)
602
+	{
603
+		$this->set('EVT_modified', $modified);
604
+	}
605
+
606
+
607
+	/**
608
+	 * @param $name
609
+	 * @throws EE_Error
610
+	 * @throws ReflectionException
611
+	 */
612
+	public function set_name($name)
613
+	{
614
+		$this->set('EVT_name', $name);
615
+	}
616
+
617
+
618
+	/**
619
+	 * @param $order
620
+	 * @throws EE_Error
621
+	 * @throws ReflectionException
622
+	 */
623
+	public function set_order($order)
624
+	{
625
+		$this->set('EVT_order', $order);
626
+	}
627
+
628
+
629
+	/**
630
+	 * @param $short_desc
631
+	 * @throws EE_Error
632
+	 * @throws ReflectionException
633
+	 */
634
+	public function set_short_description($short_desc)
635
+	{
636
+		$this->set('EVT_short_desc', $short_desc);
637
+	}
638
+
639
+
640
+	/**
641
+	 * @param $slug
642
+	 * @throws EE_Error
643
+	 * @throws ReflectionException
644
+	 */
645
+	public function set_slug($slug)
646
+	{
647
+		$this->set('EVT_slug', $slug);
648
+	}
649
+
650
+
651
+	/**
652
+	 * @param $timezone_string
653
+	 * @throws EE_Error
654
+	 * @throws ReflectionException
655
+	 */
656
+	public function set_timezone_string($timezone_string)
657
+	{
658
+		$this->set('EVT_timezone_string', $timezone_string);
659
+	}
660
+
661
+
662
+	/**
663
+	 * @param $visible_on
664
+	 * @throws EE_Error
665
+	 * @throws ReflectionException
666
+	 */
667
+	public function set_visible_on($visible_on)
668
+	{
669
+		$this->set('EVT_visible_on', $visible_on);
670
+	}
671
+
672
+
673
+	/**
674
+	 * @param $wp_user
675
+	 * @throws EE_Error
676
+	 * @throws ReflectionException
677
+	 */
678
+	public function set_wp_user($wp_user)
679
+	{
680
+		$this->set('EVT_wp_user', $wp_user);
681
+	}
682
+
683
+
684
+	/**
685
+	 * @param $default_registration_status
686
+	 * @throws EE_Error
687
+	 * @throws ReflectionException
688
+	 */
689
+	public function set_default_registration_status($default_registration_status)
690
+	{
691
+		$this->set('EVT_default_registration_status', $default_registration_status);
692
+	}
693
+
694
+
695
+	/**
696
+	 * @param $donations
697
+	 * @throws EE_Error
698
+	 * @throws ReflectionException
699
+	 */
700
+	public function set_donations($donations)
701
+	{
702
+		$this->set('EVT_donations', $donations);
703
+	}
704
+
705
+
706
+	/**
707
+	 * Adds a venue to this event
708
+	 *
709
+	 * @param EE_Venue /int $venue_id_or_obj
710
+	 * @return EE_Venue
711
+	 * @throws EE_Error
712
+	 * @throws ReflectionException
713
+	 */
714
+	public function add_venue($venue_id_or_obj): EE_Venue
715
+	{
716
+		return $this->_add_relation_to($venue_id_or_obj, 'Venue');
717
+	}
718
+
719
+
720
+	/**
721
+	 * Removes a venue from the event
722
+	 *
723
+	 * @param EE_Venue /int $venue_id_or_obj
724
+	 * @return EE_Venue|null
725
+	 * @throws EE_Error
726
+	 * @throws ReflectionException
727
+	 */
728
+	public function remove_venue($venue_id_or_obj): ?EE_Venue
729
+	{
730
+		return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
731
+	}
732
+
733
+
734
+	/**
735
+	 * Gets all the venues related ot the event. May provide additional $query_params if desired
736
+	 *
737
+	 * @param array $query_params @see
738
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
739
+	 * @return EE_Venue[]
740
+	 * @throws EE_Error
741
+	 * @throws ReflectionException
742
+	 */
743
+	public function venues(array $query_params = []): array
744
+	{
745
+		return $this->get_many_related('Venue', $query_params);
746
+	}
747
+
748
+
749
+	/**
750
+	 * check if event id is present and if event is published
751
+	 *
752
+	 * @return boolean true yes, false no
753
+	 * @throws EE_Error
754
+	 */
755
+	private function _has_ID_and_is_published(): bool
756
+	{
757
+		// first check if event id is present and not NULL,
758
+		// then check if this event is published (or any of the equivalent "published" statuses)
759
+		return
760
+			$this->ID() && $this->ID() !== null
761
+			&& (
762
+				$this->status() === 'publish'
763
+				|| $this->status() === EEM_Event::sold_out
764
+				|| $this->status() === EEM_Event::postponed
765
+				|| $this->status() === EEM_Event::cancelled
766
+			);
767
+	}
768
+
769
+
770
+	/**
771
+	 * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
772
+	 *
773
+	 * @return boolean true yes, false no
774
+	 * @throws EE_Error
775
+	 * @throws ReflectionException
776
+	 */
777
+	public function is_upcoming(): bool
778
+	{
779
+		// check if event id is present and if this event is published
780
+		if ($this->is_inactive()) {
781
+			return false;
782
+		}
783
+		// set initial value
784
+		$upcoming = false;
785
+		// next let's get all datetimes and loop through them
786
+		$datetimes = $this->datetimes_in_chronological_order();
787
+		foreach ($datetimes as $datetime) {
788
+			if ($datetime instanceof EE_Datetime) {
789
+				// if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
790
+				if ($datetime->is_expired()) {
791
+					continue;
792
+				}
793
+				// if this dtt is active then we return false.
794
+				if ($datetime->is_active()) {
795
+					return false;
796
+				}
797
+				// otherwise let's check upcoming status
798
+				$upcoming = $datetime->is_upcoming();
799
+			}
800
+		}
801
+		return $upcoming;
802
+	}
803
+
804
+
805
+	/**
806
+	 * @return bool
807
+	 * @throws EE_Error
808
+	 * @throws ReflectionException
809
+	 */
810
+	public function is_active(): bool
811
+	{
812
+		// check if event id is present and if this event is published
813
+		if ($this->is_inactive()) {
814
+			return false;
815
+		}
816
+		// set initial value
817
+		$active = false;
818
+		// next let's get all datetimes and loop through them
819
+		$datetimes = $this->datetimes_in_chronological_order();
820
+		foreach ($datetimes as $datetime) {
821
+			if ($datetime instanceof EE_Datetime) {
822
+				// if this dtt is expired then we continue cause one of the other datetimes might be active.
823
+				if ($datetime->is_expired()) {
824
+					continue;
825
+				}
826
+				// if this dtt is upcoming then we return false.
827
+				if ($datetime->is_upcoming()) {
828
+					return false;
829
+				}
830
+				// otherwise let's check active status
831
+				$active = $datetime->is_active();
832
+			}
833
+		}
834
+		return $active;
835
+	}
836
+
837
+
838
+	/**
839
+	 * @return bool
840
+	 * @throws EE_Error
841
+	 * @throws ReflectionException
842
+	 */
843
+	public function is_expired(): bool
844
+	{
845
+		// check if event id is present and if this event is published
846
+		if ($this->is_inactive()) {
847
+			return false;
848
+		}
849
+		// set initial value
850
+		$expired = false;
851
+		// first let's get all datetimes and loop through them
852
+		$datetimes = $this->datetimes_in_chronological_order();
853
+		foreach ($datetimes as $datetime) {
854
+			if ($datetime instanceof EE_Datetime) {
855
+				// if this dtt is upcoming or active then we return false.
856
+				if ($datetime->is_upcoming() || $datetime->is_active()) {
857
+					return false;
858
+				}
859
+				// otherwise let's check active status
860
+				$expired = $datetime->is_expired();
861
+			}
862
+		}
863
+		return $expired;
864
+	}
865
+
866
+
867
+	/**
868
+	 * @return bool
869
+	 * @throws EE_Error
870
+	 */
871
+	public function is_inactive(): bool
872
+	{
873
+		// check if event id is present and if this event is published
874
+		if ($this->_has_ID_and_is_published()) {
875
+			return false;
876
+		}
877
+		return true;
878
+	}
879
+
880
+
881
+	/**
882
+	 * calculate spaces remaining based on "saleable" tickets
883
+	 *
884
+	 * @param array $tickets
885
+	 * @param bool  $filtered
886
+	 * @return int|float
887
+	 * @throws EE_Error
888
+	 * @throws DomainException
889
+	 * @throws UnexpectedEntityException
890
+	 */
891
+	public function spaces_remaining(array $tickets = [], bool $filtered = true)
892
+	{
893
+		$this->getAvailableSpacesCalculator()->setActiveTickets($tickets);
894
+		$spaces_remaining = $this->getAvailableSpacesCalculator()->spacesRemaining();
895
+		return $filtered
896
+			? apply_filters(
897
+				'FHEE_EE_Event__spaces_remaining',
898
+				$spaces_remaining,
899
+				$this,
900
+				$tickets
901
+			)
902
+			: $spaces_remaining;
903
+	}
904
+
905
+
906
+	/**
907
+	 *    perform_sold_out_status_check
908
+	 *    checks all of this event's datetime reg_limit - sold values to determine if ANY datetimes have spaces
909
+	 *    available... if NOT, then the event status will get toggled to 'sold_out'
910
+	 *
911
+	 * @return bool    return the ACTUAL sold out state.
912
+	 * @throws EE_Error
913
+	 * @throws DomainException
914
+	 * @throws UnexpectedEntityException
915
+	 * @throws ReflectionException
916
+	 */
917
+	public function perform_sold_out_status_check(): bool
918
+	{
919
+		// get all tickets
920
+		$tickets     = $this->tickets(
921
+			[
922
+				'default_where_conditions' => 'none',
923
+				'order_by'                 => ['TKT_qty' => 'ASC'],
924
+			]
925
+		);
926
+		$all_expired = true;
927
+		foreach ($tickets as $ticket) {
928
+			if (! $ticket->is_expired()) {
929
+				$all_expired = false;
930
+				break;
931
+			}
932
+		}
933
+		// if all the tickets are just expired, then don't update the event status to sold out
934
+		if ($all_expired) {
935
+			return true;
936
+		}
937
+		$spaces_remaining = $this->spaces_remaining($tickets);
938
+		if ($spaces_remaining < 1) {
939
+			if ($this->status() !== EEM_Event::post_status_private) {
940
+				$this->set_status(EEM_Event::sold_out);
941
+				$this->save();
942
+			}
943
+			$sold_out = true;
944
+		} else {
945
+			$sold_out = false;
946
+			// was event previously marked as sold out ?
947
+			if ($this->status() === EEM_Event::sold_out) {
948
+				// revert status to previous value, if it was set
949
+				$previous_event_status = $this->get_post_meta('_previous_event_status', true);
950
+				if ($previous_event_status) {
951
+					$this->set_status($previous_event_status);
952
+					$this->save();
953
+				}
954
+			}
955
+		}
956
+		do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
957
+		return $sold_out;
958
+	}
959
+
960
+
961
+	/**
962
+	 * This returns the total remaining spaces for sale on this event.
963
+	 *
964
+	 * @return float|int
965
+	 * @throws EE_Error
966
+	 * @throws DomainException
967
+	 * @throws UnexpectedEntityException
968
+	 * @uses EE_Event::total_available_spaces()
969
+	 */
970
+	public function spaces_remaining_for_sale()
971
+	{
972
+		return $this->total_available_spaces(true);
973
+	}
974
+
975
+
976
+	/**
977
+	 * This returns the total spaces available for an event
978
+	 * while considering all the qtys on the tickets and the reg limits
979
+	 * on the datetimes attached to this event.
980
+	 *
981
+	 * @param bool $consider_sold   Whether to consider any tickets that have already sold in our calculation.
982
+	 *                              If this is false, then we return the most tickets that could ever be sold
983
+	 *                              for this event with the datetime and tickets setup on the event under optimal
984
+	 *                              selling conditions.  Otherwise we return a live calculation of spaces available
985
+	 *                              based on tickets sold.  Depending on setup and stage of sales, this
986
+	 *                              may appear to equal remaining tickets.  However, the more tickets are
987
+	 *                              sold out, the more accurate the "live" total is.
988
+	 * @return float|int
989
+	 * @throws EE_Error
990
+	 * @throws DomainException
991
+	 * @throws UnexpectedEntityException
992
+	 */
993
+	public function total_available_spaces(bool $consider_sold = false)
994
+	{
995
+		$spaces_available = $consider_sold
996
+			? $this->getAvailableSpacesCalculator()->spacesRemaining()
997
+			: $this->getAvailableSpacesCalculator()->totalSpacesAvailable();
998
+		return apply_filters(
999
+			'FHEE_EE_Event__total_available_spaces__spaces_available',
1000
+			$spaces_available,
1001
+			$this,
1002
+			$this->getAvailableSpacesCalculator()->getDatetimes(),
1003
+			$this->getAvailableSpacesCalculator()->getActiveTickets()
1004
+		);
1005
+	}
1006
+
1007
+
1008
+	/**
1009
+	 * Checks if the event is set to sold out
1010
+	 *
1011
+	 * @param bool $actual  whether or not to perform calculations to not only figure the
1012
+	 *                      actual status but also to flip the status if necessary to sold
1013
+	 *                      out If false, we just check the existing status of the event
1014
+	 * @return boolean
1015
+	 * @throws EE_Error
1016
+	 * @throws ReflectionException
1017
+	 */
1018
+	public function is_sold_out(bool $actual = false): bool
1019
+	{
1020
+		if (! $actual) {
1021
+			return $this->status() === EEM_Event::sold_out;
1022
+		}
1023
+		return $this->perform_sold_out_status_check();
1024
+	}
1025
+
1026
+
1027
+	/**
1028
+	 * Checks if the event is marked as postponed
1029
+	 *
1030
+	 * @return boolean
1031
+	 * @throws EE_Error
1032
+	 * @throws EE_Error
1033
+	 */
1034
+	public function is_postponed(): bool
1035
+	{
1036
+		return $this->status() === EEM_Event::postponed;
1037
+	}
1038
+
1039
+
1040
+	/**
1041
+	 * Checks if the event is marked as cancelled
1042
+	 *
1043
+	 * @return boolean
1044
+	 * @throws EE_Error
1045
+	 * @throws EE_Error
1046
+	 */
1047
+	public function is_cancelled(): bool
1048
+	{
1049
+		return $this->status() === EEM_Event::cancelled;
1050
+	}
1051
+
1052
+
1053
+	/**
1054
+	 * Get the logical active status in a hierarchical order for all the datetimes.  Note
1055
+	 * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1056
+	 * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1057
+	 * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1058
+	 * the event is considered expired.
1059
+	 * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a
1060
+	 * status set on the EVENT when it is not published and thus is done
1061
+	 *
1062
+	 * @param bool $reset
1063
+	 * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1064
+	 * @throws EE_Error
1065
+	 * @throws ReflectionException
1066
+	 */
1067
+	public function get_active_status(bool $reset = false)
1068
+	{
1069
+		// if the active status has already been set, then just use that value (unless we are resetting it)
1070
+		if (! empty($this->_active_status) && ! $reset) {
1071
+			return $this->_active_status;
1072
+		}
1073
+		// first check if event id is present on this object
1074
+		if (! $this->ID()) {
1075
+			return false;
1076
+		}
1077
+		$where_params_for_event = [['EVT_ID' => $this->ID()]];
1078
+		// if event is published:
1079
+		if ($this->status() === EEM_Event::post_status_publish || $this->status() === EEM_Event::post_status_private) {
1080
+			// active?
1081
+			if (
1082
+				EEM_Datetime::instance()->get_datetime_count_for_status(
1083
+					EE_Datetime::active,
1084
+					$where_params_for_event
1085
+				) > 0
1086
+			) {
1087
+				$this->_active_status = EE_Datetime::active;
1088
+			} else {
1089
+				// upcoming?
1090
+				if (
1091
+					EEM_Datetime::instance()->get_datetime_count_for_status(
1092
+						EE_Datetime::upcoming,
1093
+						$where_params_for_event
1094
+					) > 0
1095
+				) {
1096
+					$this->_active_status = EE_Datetime::upcoming;
1097
+				} else {
1098
+					// expired?
1099
+					if (
1100
+						EEM_Datetime::instance()->get_datetime_count_for_status(
1101
+							EE_Datetime::expired,
1102
+							$where_params_for_event
1103
+						) > 0
1104
+					) {
1105
+						$this->_active_status = EE_Datetime::expired;
1106
+					} else {
1107
+						// it would be odd if things make it this far because it basically means there are no datetime's
1108
+						// attached to the event.  So in this case it will just be considered inactive.
1109
+						$this->_active_status = EE_Datetime::inactive;
1110
+					}
1111
+				}
1112
+			}
1113
+		} else {
1114
+			// the event is not published, so let's just set it's active status according to its' post status
1115
+			switch ($this->status()) {
1116
+				case EEM_Event::sold_out:
1117
+					$this->_active_status = EE_Datetime::sold_out;
1118
+					break;
1119
+				case EEM_Event::cancelled:
1120
+					$this->_active_status = EE_Datetime::cancelled;
1121
+					break;
1122
+				case EEM_Event::postponed:
1123
+					$this->_active_status = EE_Datetime::postponed;
1124
+					break;
1125
+				default:
1126
+					$this->_active_status = EE_Datetime::inactive;
1127
+			}
1128
+		}
1129
+		return $this->_active_status;
1130
+	}
1131
+
1132
+
1133
+	/**
1134
+	 *    pretty_active_status
1135
+	 *
1136
+	 * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1137
+	 * @return string string
1138
+	 * @throws EE_Error
1139
+	 * @throws ReflectionException
1140
+	 */
1141
+	public function pretty_active_status(bool $echo = true): string
1142
+	{
1143
+		$active_status = $this->get_active_status();
1144
+		$status        = '<span class="ee-status event-active-status-'
1145
+						 . $active_status
1146
+						 . '">'
1147
+						 . EEH_Template::pretty_status($active_status, false, 'sentence')
1148
+						 . '</span>';
1149
+		if ($echo) {
1150
+			echo $status;
1151
+			return '';
1152
+		}
1153
+		return $status;
1154
+	}
1155
+
1156
+
1157
+	/**
1158
+	 * @return bool|int
1159
+	 * @throws EE_Error
1160
+	 * @throws ReflectionException
1161
+	 */
1162
+	public function get_number_of_tickets_sold()
1163
+	{
1164
+		$tkt_sold = 0;
1165
+		if (! $this->ID()) {
1166
+			return 0;
1167
+		}
1168
+		$datetimes = $this->datetimes();
1169
+		foreach ($datetimes as $datetime) {
1170
+			if ($datetime instanceof EE_Datetime) {
1171
+				$tkt_sold += $datetime->sold();
1172
+			}
1173
+		}
1174
+		return $tkt_sold;
1175
+	}
1176
+
1177
+
1178
+	/**
1179
+	 * This just returns a count of all the registrations for this event
1180
+	 *
1181
+	 * @return int
1182
+	 * @throws EE_Error
1183
+	 * @throws ReflectionException
1184
+	 */
1185
+	public function get_count_of_all_registrations(): int
1186
+	{
1187
+		return EEM_Event::instance()->count_related($this, 'Registration');
1188
+	}
1189
+
1190
+
1191
+	/**
1192
+	 * This returns the ticket with the earliest start time that is
1193
+	 * available for this event (across all datetimes attached to the event)
1194
+	 *
1195
+	 * @return EE_Ticket|null
1196
+	 * @throws EE_Error
1197
+	 * @throws ReflectionException
1198
+	 */
1199
+	public function get_ticket_with_earliest_start_time(): ?EE_Ticket
1200
+	{
1201
+		$where['Datetime.EVT_ID'] = $this->ID();
1202
+		$query_params             = [$where, 'order_by' => ['TKT_start_date' => 'ASC']];
1203
+		return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1204
+	}
1205
+
1206
+
1207
+	/**
1208
+	 * This returns the ticket with the latest end time that is available
1209
+	 * for this event (across all datetimes attached to the event)
1210
+	 *
1211
+	 * @return EE_Ticket|null
1212
+	 * @throws EE_Error
1213
+	 * @throws ReflectionException
1214
+	 */
1215
+	public function get_ticket_with_latest_end_time(): ?EE_Ticket
1216
+	{
1217
+		$where['Datetime.EVT_ID'] = $this->ID();
1218
+		$query_params             = [$where, 'order_by' => ['TKT_end_date' => 'DESC']];
1219
+		return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1220
+	}
1221
+
1222
+
1223
+	/**
1224
+	 * This returns the number of different ticket types currently on sale for this event.
1225
+	 *
1226
+	 * @return int
1227
+	 * @throws EE_Error
1228
+	 * @throws ReflectionException
1229
+	 */
1230
+	public function countTicketsOnSale(): int
1231
+	{
1232
+		$where = [
1233
+			'Datetime.EVT_ID' => $this->ID(),
1234
+			'TKT_start_date'  => ['<', time()],
1235
+			'TKT_end_date'    => ['>', time()],
1236
+		];
1237
+		return EEM_Ticket::instance()->count([$where]);
1238
+	}
1239
+
1240
+
1241
+	/**
1242
+	 * This returns whether there are any tickets on sale for this event.
1243
+	 *
1244
+	 * @return bool true = YES tickets on sale.
1245
+	 * @throws EE_Error
1246
+	 * @throws ReflectionException
1247
+	 */
1248
+	public function tickets_on_sale(): bool
1249
+	{
1250
+		return $this->countTicketsOnSale() > 0;
1251
+	}
1252
+
1253
+
1254
+	/**
1255
+	 * Gets the URL for viewing this event on the front-end. Overrides parent
1256
+	 * to check for an external URL first
1257
+	 *
1258
+	 * @return string
1259
+	 * @throws EE_Error
1260
+	 */
1261
+	public function get_permalink(): string
1262
+	{
1263
+		if ($this->external_url()) {
1264
+			return $this->external_url();
1265
+		}
1266
+		return parent::get_permalink();
1267
+	}
1268
+
1269
+
1270
+	/**
1271
+	 * Gets the first term for 'espresso_event_categories' we can find
1272
+	 *
1273
+	 * @param array $query_params @see
1274
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1275
+	 * @return EE_Term|null
1276
+	 * @throws EE_Error
1277
+	 * @throws ReflectionException
1278
+	 */
1279
+	public function first_event_category(array $query_params = []): ?EE_Term
1280
+	{
1281
+		$query_params[0]['Term_Taxonomy.taxonomy']     = 'espresso_event_categories';
1282
+		$query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1283
+		return EEM_Term::instance()->get_one($query_params);
1284
+	}
1285
+
1286
+
1287
+	/**
1288
+	 * Gets all terms for 'espresso_event_categories' we can find
1289
+	 *
1290
+	 * @param array $query_params
1291
+	 * @return EE_Term[]
1292
+	 * @throws EE_Error
1293
+	 * @throws ReflectionException
1294
+	 */
1295
+	public function get_all_event_categories(array $query_params = []): array
1296
+	{
1297
+		$query_params[0]['Term_Taxonomy.taxonomy']     = 'espresso_event_categories';
1298
+		$query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1299
+		return EEM_Term::instance()->get_all($query_params);
1300
+	}
1301
+
1302
+
1303
+	/**
1304
+	 * Adds a question group to this event
1305
+	 *
1306
+	 * @param EE_Question_Group|int $question_group_id_or_obj
1307
+	 * @param bool                  $for_primary if true, the question group will be added for the primary
1308
+	 *                                           registrant, if false will be added for others. default: false
1309
+	 * @return EE_Question_Group
1310
+	 * @throws EE_Error
1311
+	 * @throws InvalidArgumentException
1312
+	 * @throws InvalidDataTypeException
1313
+	 * @throws InvalidInterfaceException
1314
+	 * @throws ReflectionException
1315
+	 */
1316
+	public function add_question_group($question_group_id_or_obj, bool $for_primary = false): EE_Question_Group
1317
+	{
1318
+		// If the row already exists, it will be updated. If it doesn't, it will be inserted.
1319
+		// That's in EE_HABTM_Relation::add_relation_to().
1320
+		return $this->_add_relation_to(
1321
+			$question_group_id_or_obj,
1322
+			'Question_Group',
1323
+			[
1324
+				EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary) => true,
1325
+			]
1326
+		);
1327
+	}
1328
+
1329
+
1330
+	/**
1331
+	 * Removes a question group from the event
1332
+	 *
1333
+	 * @param EE_Question_Group|int $question_group_id_or_obj
1334
+	 * @param bool                  $for_primary if true, the question group will be removed from the primary
1335
+	 *                                           registrant, if false will be removed from others. default: false
1336
+	 * @return EE_Question_Group
1337
+	 * @throws EE_Error
1338
+	 * @throws InvalidArgumentException
1339
+	 * @throws ReflectionException
1340
+	 * @throws InvalidDataTypeException
1341
+	 * @throws InvalidInterfaceException
1342
+	 */
1343
+	public function remove_question_group($question_group_id_or_obj, bool $for_primary = false): EE_Question_Group
1344
+	{
1345
+		// If the question group is used for the other type (primary or additional)
1346
+		// then just update it. If not, delete it outright.
1347
+		$existing_relation = $this->get_first_related(
1348
+			'Event_Question_Group',
1349
+			[
1350
+				[
1351
+					'QSG_ID' => EEM_Question_Group::instance()->ensure_is_ID($question_group_id_or_obj),
1352
+				],
1353
+			]
1354
+		);
1355
+		$field_to_update   = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1356
+		$other_field       = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1357
+		if ($existing_relation->get($other_field) === false) {
1358
+			// Delete it. It's now no longer for primary or additional question groups.
1359
+			return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
1360
+		}
1361
+		// Just update it. They'll still use this question group for the other category
1362
+		$existing_relation->save(
1363
+			[
1364
+				$field_to_update => false,
1365
+			]
1366
+		);
1367
+		return $question_group_id_or_obj;
1368
+	}
1369
+
1370
+
1371
+	/**
1372
+	 * Gets all the question groups, ordering them by QSG_order ascending
1373
+	 *
1374
+	 * @param array $query_params @see
1375
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1376
+	 * @return EE_Question_Group[]
1377
+	 * @throws EE_Error
1378
+	 * @throws ReflectionException
1379
+	 */
1380
+	public function question_groups(array $query_params = []): array
1381
+	{
1382
+		$query_params = ! empty($query_params)
1383
+			? $query_params
1384
+			: ['order_by' => ['QSG_order' => 'ASC']];
1385
+		return $this->get_many_related('Question_Group', $query_params);
1386
+	}
1387
+
1388
+
1389
+	/**
1390
+	 * Implementation for EEI_Has_Icon interface method.
1391
+	 *
1392
+	 * @return string
1393
+	 * @see EEI_Visual_Representation for comments
1394
+	 */
1395
+	public function get_icon(): string
1396
+	{
1397
+		return '<span class="dashicons dashicons-flag"></span>';
1398
+	}
1399
+
1400
+
1401
+	/**
1402
+	 * Implementation for EEI_Admin_Links interface method.
1403
+	 *
1404
+	 * @return string
1405
+	 * @throws EE_Error
1406
+	 * @see EEI_Admin_Links for comments
1407
+	 */
1408
+	public function get_admin_details_link(): string
1409
+	{
1410
+		return $this->get_admin_edit_link();
1411
+	}
1412
+
1413
+
1414
+	/**
1415
+	 * Implementation for EEI_Admin_Links interface method.
1416
+	 *
1417
+	 * @return string
1418
+	 * @throws EE_Error
1419
+	 * @see EEI_Admin_Links for comments
1420
+	 */
1421
+	public function get_admin_edit_link(): string
1422
+	{
1423
+		return EEH_URL::add_query_args_and_nonce(
1424
+			[
1425
+				'page'   => 'espresso_events',
1426
+				'action' => 'edit',
1427
+				'post'   => $this->ID(),
1428
+			],
1429
+			admin_url('admin.php')
1430
+		);
1431
+	}
1432
+
1433
+
1434
+	/**
1435
+	 * Implementation for EEI_Admin_Links interface method.
1436
+	 *
1437
+	 * @return string
1438
+	 * @see EEI_Admin_Links for comments
1439
+	 */
1440
+	public function get_admin_settings_link(): string
1441
+	{
1442
+		return EEH_URL::add_query_args_and_nonce(
1443
+			[
1444
+				'page'   => 'espresso_events',
1445
+				'action' => 'default_event_settings',
1446
+			],
1447
+			admin_url('admin.php')
1448
+		);
1449
+	}
1450
+
1451
+
1452
+	/**
1453
+	 * Implementation for EEI_Admin_Links interface method.
1454
+	 *
1455
+	 * @return string
1456
+	 * @see EEI_Admin_Links for comments
1457
+	 */
1458
+	public function get_admin_overview_link(): string
1459
+	{
1460
+		return EEH_URL::add_query_args_and_nonce(
1461
+			[
1462
+				'page'   => 'espresso_events',
1463
+				'action' => 'default',
1464
+			],
1465
+			admin_url('admin.php')
1466
+		);
1467
+	}
1468 1468
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
      */
79 79
     public function getAvailableSpacesCalculator(): EventSpacesCalculator
80 80
     {
81
-        if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
81
+        if ( ! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
82 82
             $this->available_spaces_calculator = new EventSpacesCalculator($this);
83 83
         }
84 84
         return $this->available_spaces_calculator;
@@ -228,7 +228,7 @@  discard block
 block discarded – undo
228 228
         bool $try_to_exclude_expired = true,
229 229
         bool $try_to_exclude_deleted = true
230 230
     ): EE_Datetime {
231
-        if (! empty($this->_Primary_Datetime)) {
231
+        if ( ! empty($this->_Primary_Datetime)) {
232 232
             return $this->_Primary_Datetime;
233 233
         }
234 234
         $this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
@@ -253,7 +253,7 @@  discard block
 block discarded – undo
253 253
     {
254 254
         // first get all datetimes
255 255
         $datetimes = $this->datetimes_ordered();
256
-        if (! $datetimes) {
256
+        if ( ! $datetimes) {
257 257
             return [];
258 258
         }
259 259
         $datetime_ids = [];
@@ -447,7 +447,7 @@  discard block
 block discarded – undo
447 447
     public function short_description(int $num_words = 55, string $more = null, bool $not_full_desc = false)
448 448
     {
449 449
         $short_desc = $this->get('EVT_short_desc');
450
-        if (! empty($short_desc) || $not_full_desc) {
450
+        if ( ! empty($short_desc) || $not_full_desc) {
451 451
             return $short_desc;
452 452
         }
453 453
         $full_desc = $this->get('EVT_desc');
@@ -917,7 +917,7 @@  discard block
 block discarded – undo
917 917
     public function perform_sold_out_status_check(): bool
918 918
     {
919 919
         // get all tickets
920
-        $tickets     = $this->tickets(
920
+        $tickets = $this->tickets(
921 921
             [
922 922
                 'default_where_conditions' => 'none',
923 923
                 'order_by'                 => ['TKT_qty' => 'ASC'],
@@ -925,7 +925,7 @@  discard block
 block discarded – undo
925 925
         );
926 926
         $all_expired = true;
927 927
         foreach ($tickets as $ticket) {
928
-            if (! $ticket->is_expired()) {
928
+            if ( ! $ticket->is_expired()) {
929 929
                 $all_expired = false;
930 930
                 break;
931 931
             }
@@ -1017,7 +1017,7 @@  discard block
 block discarded – undo
1017 1017
      */
1018 1018
     public function is_sold_out(bool $actual = false): bool
1019 1019
     {
1020
-        if (! $actual) {
1020
+        if ( ! $actual) {
1021 1021
             return $this->status() === EEM_Event::sold_out;
1022 1022
         }
1023 1023
         return $this->perform_sold_out_status_check();
@@ -1067,11 +1067,11 @@  discard block
 block discarded – undo
1067 1067
     public function get_active_status(bool $reset = false)
1068 1068
     {
1069 1069
         // if the active status has already been set, then just use that value (unless we are resetting it)
1070
-        if (! empty($this->_active_status) && ! $reset) {
1070
+        if ( ! empty($this->_active_status) && ! $reset) {
1071 1071
             return $this->_active_status;
1072 1072
         }
1073 1073
         // first check if event id is present on this object
1074
-        if (! $this->ID()) {
1074
+        if ( ! $this->ID()) {
1075 1075
             return false;
1076 1076
         }
1077 1077
         $where_params_for_event = [['EVT_ID' => $this->ID()]];
@@ -1162,7 +1162,7 @@  discard block
 block discarded – undo
1162 1162
     public function get_number_of_tickets_sold()
1163 1163
     {
1164 1164
         $tkt_sold = 0;
1165
-        if (! $this->ID()) {
1165
+        if ( ! $this->ID()) {
1166 1166
             return 0;
1167 1167
         }
1168 1168
         $datetimes = $this->datetimes();
@@ -1353,7 +1353,7 @@  discard block
 block discarded – undo
1353 1353
             ]
1354 1354
         );
1355 1355
         $field_to_update   = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1356
-        $other_field       = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1356
+        $other_field       = EEM_Event_Question_Group::instance()->fieldNameForContext( ! $for_primary);
1357 1357
         if ($existing_relation->get($other_field) === false) {
1358 1358
             // Delete it. It's now no longer for primary or additional question groups.
1359 1359
             return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
Please login to merge, or discard this patch.
core/db_classes/EE_CPT_Base.class.php 2 patches
Indentation   +482 added lines, -482 removed lines patch added patch discarded remove patch
@@ -12,486 +12,486 @@
 block discarded – undo
12 12
 abstract class EE_CPT_Base extends EE_Soft_Delete_Base_Class
13 13
 {
14 14
 
15
-    /**
16
-     * This is a property for holding cached feature images on CPT objects.
17
-     * Cache's are set on the first "feature_image()" method call.
18
-     * Each key in the array corresponds to the requested size.
19
-     *
20
-     * @var array
21
-     */
22
-    protected $_feature_image = [];
23
-
24
-    /**
25
-     * @var WP_Post the WP_Post that corresponds with this CPT model object
26
-     */
27
-    protected $_wp_post;
28
-
29
-
30
-    abstract public function wp_user();
31
-
32
-
33
-    /**
34
-     * Adds to the specified event category. If it category doesn't exist, creates it.
35
-     *
36
-     * @param string      $category_name
37
-     * @param string|null $category_description    optional
38
-     * @param int|null    $parent_term_taxonomy_id optional
39
-     * @return EE_Term_Taxonomy
40
-     * @throws EE_Error
41
-     * @throws ReflectionException
42
-     */
43
-    public function add_event_category(
44
-        string $category_name,
45
-        string $category_description = '',
46
-        int $parent_term_taxonomy_id = 0
47
-    ): EE_Term_Taxonomy {
48
-        return $this->get_model()->add_event_category(
49
-            $this,
50
-            $category_name,
51
-            $category_description,
52
-            $parent_term_taxonomy_id
53
-        );
54
-    }
55
-
56
-
57
-    /**
58
-     * Removes the event category by specified name from being related ot this event
59
-     *
60
-     * @param string $category_name
61
-     * @return bool
62
-     * @throws EE_Error
63
-     * @throws ReflectionException
64
-     */
65
-    public function remove_event_category(string $category_name): bool
66
-    {
67
-        return $this->get_model()->remove_event_category($this, $category_name);
68
-    }
69
-
70
-
71
-    /**
72
-     * Removes the relation to the specified term taxonomy, and maintains the
73
-     * data integrity of the term taxonomy provided
74
-     *
75
-     * @param EE_Term_Taxonomy $term_taxonomy
76
-     * @return EE_Base_Class the relation was removed from
77
-     * @throws EE_Error
78
-     * @throws ReflectionException
79
-     */
80
-    public function remove_relation_to_term_taxonomy(EE_Term_Taxonomy $term_taxonomy): ?EE_Base_Class
81
-    {
82
-        if (! $term_taxonomy) {
83
-            EE_Error::add_error(
84
-                sprintf(
85
-                    esc_html__(
86
-                        "No Term_Taxonomy provided which to remove from model object of type %s and id %d",
87
-                        "event_espresso"
88
-                    ),
89
-                    get_class($this),
90
-                    $this->ID()
91
-                ),
92
-                __FILE__,
93
-                __FUNCTION__,
94
-                __LINE__
95
-            );
96
-            return null;
97
-        }
98
-        $term_taxonomy->set_count($term_taxonomy->count() - 1);
99
-        $term_taxonomy->save();
100
-        return $this->_remove_relation_to($term_taxonomy, 'Term_Taxonomy');
101
-    }
102
-
103
-
104
-    /**
105
-     * The main purpose of this method is to return the post type for the model object
106
-     *
107
-     * @return string
108
-     * @throws EE_Error
109
-     * @throws ReflectionException
110
-     */
111
-    public function post_type(): string
112
-    {
113
-        return $this->get_model()->post_type();
114
-    }
115
-
116
-
117
-    /**
118
-     * The main purpose of this method is to return the parent for the model object
119
-     *
120
-     * @return int
121
-     * @throws EE_Error
122
-     */
123
-    public function parent(): int
124
-    {
125
-        return $this->get('parent');
126
-    }
127
-
128
-
129
-    /**
130
-     * return the _status property
131
-     *
132
-     * @return string
133
-     * @throws EE_Error
134
-     */
135
-    public function status(): string
136
-    {
137
-        return $this->get('status');
138
-    }
139
-
140
-
141
-    /**
142
-     * @param string $status
143
-     * @throws EE_Error
144
-     * @throws ReflectionException
145
-     */
146
-    public function set_status(string $status)
147
-    {
148
-        $this->set('status', $status);
149
-    }
150
-
151
-
152
-    /**
153
-     * See _get_feature_image. Returns the HTML to display a featured image
154
-     *
155
-     * @param string       $size
156
-     * @param string|array $attr
157
-     * @return string of html
158
-     * @throws EE_Error
159
-     * @throws ReflectionException
160
-     */
161
-    public function feature_image(string $size = 'thumbnail', $attr = ''): string
162
-    {
163
-        return $this->_get_feature_image($size, $attr);
164
-    }
165
-
166
-
167
-    /**
168
-     * This calls the equivalent model method for retrieving the feature image
169
-     * which in turn is a wrapper for WordPress' get_the_post_thumbnail() function.
170
-     *
171
-     * @link   http://codex.wordpress.org/Function_Reference/get_the_post_thumbnail
172
-     * @access protected
173
-     * @param string|array $size (optional) Image size. Defaults to 'post-thumbnail' but can also be a 2-item array
174
-     *                           representing width and height in pixels (i.e. array(32,32) ).
175
-     * @param string|array $attr Optional. Query string or array of attributes.
176
-     * @return string HTML image element
177
-     * @throws EE_Error
178
-     * @throws ReflectionException
179
-     */
180
-    protected function _get_feature_image($size, $attr): string
181
-    {
182
-        // first let's see if we already have the _feature_image property set
183
-        // AND if it has a cached element on it FOR the given size
184
-        $attr_key                           = is_array($attr)
185
-            ? implode('_', $attr)
186
-            : $attr;
187
-        $cache_key                          = is_array($size)
188
-            ? implode('_', $size) . $attr_key
189
-            : $size . $attr_key;
190
-        $this->_feature_image[ $cache_key ] = $this->_feature_image[ $cache_key ]
191
-                                              ?? $this->get_model()->get_feature_image($this->ID(), $size, $attr);
192
-        return $this->_feature_image[ $cache_key ];
193
-    }
194
-
195
-
196
-    /**
197
-     * This uses the wp "wp_get_attachment_image_src()" function to return the feature image for the current class
198
-     * using the given size params.
199
-     *
200
-     * @param string|array $size  can either be a string: 'thumbnail', 'medium', 'large', 'full' OR 2-item array
201
-     *                            representing width and height in pixels eg. array(32,32).
202
-     * @return string|boolean          the url of the image or false if not found
203
-     * @throws EE_Error
204
-     */
205
-    public function feature_image_url($size = 'thumbnail')
206
-    {
207
-        $attachment = wp_get_attachment_image_src(get_post_thumbnail_id($this->ID()), $size);
208
-        return ! empty($attachment)
209
-            ? $attachment[0]
210
-            : false;
211
-    }
212
-
213
-
214
-    /**
215
-     * This is a method for restoring this_obj using details from the given $revision_id
216
-     *
217
-     * @param int   $revision_id       ID of the revision we're getting data from
218
-     * @param array $related_obj_names if included this will be used to restore for related obj
219
-     *                                 if not included then we just do restore on the meta.
220
-     *                                 We will accept an array of related_obj_names for restoration here.
221
-     * @param array $where_query       You can optionally include an array of key=>value pairs
222
-     *                                 that allow you to further constrict the relation to being added.
223
-     *                                 However, keep in mind that the columns (keys) given
224
-     *                                 must match a column on the JOIN table and currently
225
-     *                                 only the HABTM models accept these additional conditions.
226
-     *                                 Also remember that if an exact match isn't found for these extra cols/val pairs,
227
-     *                                 then a NEW row is created in the join table.
228
-     *                                 This array is INDEXED by RELATED OBJ NAME (so it corresponds with the obj_names
229
-     *                                 sent);
230
-     * @return void
231
-     * @throws EE_Error
232
-     * @throws ReflectionException
233
-     */
234
-    public function restore_revision(int $revision_id, array $related_obj_names = [], array $where_query = [])
235
-    {
236
-        // get revision object
237
-        $revision_obj = $this->get_model()->get_one_by_ID($revision_id);
238
-        if ($revision_obj instanceof EE_CPT_Base) {
239
-            // no related_obj_name so we assume we're saving a revision on this object.
240
-            if (empty($related_obj_names)) {
241
-                $fields = $this->get_model()->get_meta_table_fields();
242
-                foreach ($fields as $field) {
243
-                    $this->set($field, $revision_obj->get($field));
244
-                }
245
-                $this->save();
246
-            }
247
-            foreach ($related_obj_names as $related_name) {
248
-                // related_obj_name so we're saving a revision on an object related to this object
249
-                // do we have $where_query params for this related object?  If we do then we include that.
250
-                $cols_n_values         = $where_query[ $related_name ] ?? [];
251
-                $where_params          = ! empty($cols_n_values)
252
-                    ? [$cols_n_values]
253
-                    : [];
254
-                $related_objs          = $this->get_many_related($related_name, $where_params);
255
-                $revision_related_objs = $revision_obj->get_many_related($related_name, $where_params);
256
-                // load helper
257
-                // remove related objs from this object that are not in revision
258
-                // array_diff *should* work cause I think objects are indexed by ID?
259
-                $related_to_remove = EEH_Array::object_array_diff($related_objs, $revision_related_objs);
260
-                foreach ($related_to_remove as $rr) {
261
-                    $this->_remove_relation_to($rr, $related_name, $cols_n_values);
262
-                }
263
-                // add all related objs attached to revision to this object
264
-                foreach ($revision_related_objs as $r_obj) {
265
-                    $this->_add_relation_to($r_obj, $related_name, $cols_n_values);
266
-                }
267
-            }
268
-        }
269
-    }
270
-
271
-
272
-    /**
273
-     * Wrapper for get_post_meta, http://codex.wordpress.org/Function_Reference/get_post_meta
274
-     * If only $id is set:
275
-     *  it will return all meta values in an associative array.
276
-     * If $single is set to false, or left blank:
277
-     *  the function returns an array containing all values of the specified key.
278
-     * If $single is set to true:
279
-     *  the function returns the first value of the specified key (not in an array)
280
-     *
281
-     * @param string $meta_key
282
-     * @param bool   $single
283
-     * @return mixed
284
-     * @throws EE_Error
285
-     */
286
-    public function get_post_meta(string $meta_key = '', bool $single = false)
287
-    {
288
-        return get_post_meta($this->ID(), $meta_key, $single);
289
-    }
290
-
291
-
292
-    /**
293
-     * Wrapper for update_post_meta, http://codex.wordpress.org/Function_Reference/update_post_meta
294
-     * Returns meta_id if the meta doesn't exist,
295
-     * otherwise returns true on success and false on failure.
296
-     * NOTE: If the meta_value passed to this function is the same
297
-     * as the value that is already in the database, this function returns false.
298
-     *
299
-     * @param string $meta_key
300
-     * @param mixed  $meta_value
301
-     * @param mixed  $prev_value
302
-     * @return bool|int
303
-     * @throws EE_Error
304
-     * @throws ReflectionException
305
-     */
306
-    public function update_post_meta(string $meta_key, $meta_value, $prev_value = null)
307
-    {
308
-        if (! $this->ID()) {
309
-            $this->save();
310
-        }
311
-        return update_post_meta($this->ID(), $meta_key, $meta_value, $prev_value);
312
-    }
313
-
314
-
315
-    /**
316
-     * Wrapper for add_post_meta, http://codex.wordpress.org/Function_Reference/add_post_meta
317
-     *
318
-     * @param string $meta_key
319
-     * @param mixed $meta_value
320
-     * @param bool  $unique     If postmeta for this $meta_key already exists,
321
-     *                          whether to add an additional item or not
322
-     * @return boolean          Boolean true, except if the $unique argument was set to true
323
-     *                          and a custom field with the given key already exists,
324
-     *                          in which case false is returned.
325
-     * @throws EE_Error
326
-     * @throws ReflectionException
327
-     */
328
-    public function add_post_meta(string $meta_key, $meta_value, bool $unique = false): bool
329
-    {
330
-        if ($this->ID()) {
331
-            $this->save();
332
-        }
333
-        return add_post_meta($this->ID(), $meta_key, $meta_value, $unique);
334
-    }
335
-
336
-
337
-    /**
338
-     * Wrapper for delete_post_meta, http://codex.wordpress.org/Function_Reference/delete_post_meta
339
-     *
340
-     * @param string $meta_key
341
-     * @param mixed $meta_value
342
-     * @return boolean False for failure. True for success.
343
-     * @throws EE_Error
344
-     */
345
-    public function delete_post_meta(string $meta_key, $meta_value = ''): bool
346
-    {
347
-        if (! $this->ID()) {
348
-            // there is obviously no postmeta for this if it's not saved
349
-            // so let's just report this as a success
350
-            return true;
351
-        }
352
-        return delete_post_meta($this->ID(), $meta_key, $meta_value);
353
-    }
354
-
355
-
356
-    /**
357
-     * Gets the URL for viewing this event on the front-end
358
-     *
359
-     * @return string
360
-     * @throws EE_Error
361
-     */
362
-    public function get_permalink(): string
363
-    {
364
-        return get_permalink($this->ID());
365
-    }
366
-
367
-
368
-    /**
369
-     * Gets all the term-taxonomies for this CPT
370
-     *
371
-     * @param array $query_params
372
-     * @return EE_Term_Taxonomy[]
373
-     * @throws EE_Error
374
-     * @throws ReflectionException
375
-     */
376
-    public function term_taxonomies(array $query_params = []): array
377
-    {
378
-        return $this->get_many_related('Term_Taxonomy', $query_params);
379
-    }
380
-
381
-
382
-    /**
383
-     * @return string[]
384
-     * @throws EE_Error
385
-     * @throws ReflectionException
386
-     */
387
-    public function get_custom_post_statuses(): array
388
-    {
389
-        return $this->get_model()->get_custom_post_statuses();
390
-    }
391
-
392
-
393
-    /**
394
-     * @return string[]
395
-     * @throws EE_Error
396
-     * @throws ReflectionException
397
-     */
398
-    public function get_all_post_statuses(): array
399
-    {
400
-        return $this->get_model()->get_status_array();
401
-    }
402
-
403
-
404
-    /**
405
-     * When fetching a new value for a post field that uses the global $post for rendering,
406
-     * set the global $post temporarily to be this model object; and afterwards restore it
407
-     *
408
-     * @param string      $field_name
409
-     * @param bool        $pretty
410
-     * @param string|null $extra_cache_ref
411
-     * @return mixed
412
-     * @throws EE_Error
413
-     * @throws ReflectionException
414
-     */
415
-    protected function _get_fresh_property(string $field_name, bool $pretty = false, string $extra_cache_ref = '')
416
-    {
417
-        global $post;
418
-
419
-        if (
420
-            $pretty
421
-            && (
422
-                ! ($post instanceof WP_Post && $post->ID)
423
-                || absint($post->ID) !== $this->ID()
424
-            )
425
-            && $this->get_model()->field_settings_for($field_name) instanceof EE_Post_Content_Field
426
-        ) {
427
-            $old_post     = $post;
428
-            $post         = $this->wp_post();
429
-            $return_value = parent::_get_fresh_property($field_name, $pretty, $extra_cache_ref);
430
-            $post         = $old_post;
431
-        } else {
432
-            $return_value = parent::_get_fresh_property($field_name, $pretty, $extra_cache_ref);
433
-        }
434
-        return $return_value;
435
-    }
436
-
437
-
438
-    /**
439
-     * Returns the WP post associated with this CPT model object.
440
-     * If this CPT is saved, fetches it from the DB.
441
-     * Otherwise, create an unsaved WP_POst object. Caches the post internally.
442
-     *
443
-     * @return WP_Post
444
-     * @throws EE_Error
445
-     * @throws ReflectionException
446
-     */
447
-    public function wp_post(): WP_Post
448
-    {
449
-        global $wpdb;
450
-        if (! $this->_wp_post instanceof WP_Post) {
451
-            if ($this->ID()) {
452
-                $this->_wp_post = get_post($this->ID());
453
-            } else {
454
-                $simulated_db_result = new stdClass();
455
-                $field_settings      = $this->get_model()->field_settings(true);
456
-                foreach ($field_settings as $field_name => $field_obj) {
457
-                    if (
458
-                        $this->get_model()->get_table_obj_by_alias($field_obj->get_table_alias())
459
-                             ->get_table_name() === $wpdb->posts
460
-                    ) {
461
-                        $column = $field_obj->get_table_column();
462
-
463
-                        if ($field_obj instanceof EE_Datetime_Field) {
464
-                            $value_on_model_obj = $this->get_DateTime_object($field_name);
465
-                        } elseif ($field_obj->is_db_only_field()) {
466
-                            $value_on_model_obj = $field_obj->get_default_value();
467
-                        } else {
468
-                            $value_on_model_obj = $this->get_raw($field_name);
469
-                        }
470
-                        $simulated_db_result->{$column} = $field_obj->prepare_for_use_in_db($value_on_model_obj);
471
-                    }
472
-                }
473
-                $this->_wp_post = new WP_Post($simulated_db_result);
474
-            }
475
-            // and let's make retrieving the EE CPT object easy too
476
-            $classname = get_class($this);
477
-            if (! isset($this->_wp_post->{$classname})) {
478
-                $this->_wp_post->{$classname} = $this;
479
-            }
480
-        }
481
-        return $this->_wp_post;
482
-    }
483
-
484
-
485
-    /**
486
-     * Don't serialize the WP Post. That's just duplicate data and we want to avoid recursion
487
-     *
488
-     * @return array
489
-     * @throws EE_Error
490
-     * @throws ReflectionException
491
-     */
492
-    public function __sleep()
493
-    {
494
-        $properties_to_serialize = parent::__sleep();
495
-        return array_diff($properties_to_serialize, ['_wp_post']);
496
-    }
15
+	/**
16
+	 * This is a property for holding cached feature images on CPT objects.
17
+	 * Cache's are set on the first "feature_image()" method call.
18
+	 * Each key in the array corresponds to the requested size.
19
+	 *
20
+	 * @var array
21
+	 */
22
+	protected $_feature_image = [];
23
+
24
+	/**
25
+	 * @var WP_Post the WP_Post that corresponds with this CPT model object
26
+	 */
27
+	protected $_wp_post;
28
+
29
+
30
+	abstract public function wp_user();
31
+
32
+
33
+	/**
34
+	 * Adds to the specified event category. If it category doesn't exist, creates it.
35
+	 *
36
+	 * @param string      $category_name
37
+	 * @param string|null $category_description    optional
38
+	 * @param int|null    $parent_term_taxonomy_id optional
39
+	 * @return EE_Term_Taxonomy
40
+	 * @throws EE_Error
41
+	 * @throws ReflectionException
42
+	 */
43
+	public function add_event_category(
44
+		string $category_name,
45
+		string $category_description = '',
46
+		int $parent_term_taxonomy_id = 0
47
+	): EE_Term_Taxonomy {
48
+		return $this->get_model()->add_event_category(
49
+			$this,
50
+			$category_name,
51
+			$category_description,
52
+			$parent_term_taxonomy_id
53
+		);
54
+	}
55
+
56
+
57
+	/**
58
+	 * Removes the event category by specified name from being related ot this event
59
+	 *
60
+	 * @param string $category_name
61
+	 * @return bool
62
+	 * @throws EE_Error
63
+	 * @throws ReflectionException
64
+	 */
65
+	public function remove_event_category(string $category_name): bool
66
+	{
67
+		return $this->get_model()->remove_event_category($this, $category_name);
68
+	}
69
+
70
+
71
+	/**
72
+	 * Removes the relation to the specified term taxonomy, and maintains the
73
+	 * data integrity of the term taxonomy provided
74
+	 *
75
+	 * @param EE_Term_Taxonomy $term_taxonomy
76
+	 * @return EE_Base_Class the relation was removed from
77
+	 * @throws EE_Error
78
+	 * @throws ReflectionException
79
+	 */
80
+	public function remove_relation_to_term_taxonomy(EE_Term_Taxonomy $term_taxonomy): ?EE_Base_Class
81
+	{
82
+		if (! $term_taxonomy) {
83
+			EE_Error::add_error(
84
+				sprintf(
85
+					esc_html__(
86
+						"No Term_Taxonomy provided which to remove from model object of type %s and id %d",
87
+						"event_espresso"
88
+					),
89
+					get_class($this),
90
+					$this->ID()
91
+				),
92
+				__FILE__,
93
+				__FUNCTION__,
94
+				__LINE__
95
+			);
96
+			return null;
97
+		}
98
+		$term_taxonomy->set_count($term_taxonomy->count() - 1);
99
+		$term_taxonomy->save();
100
+		return $this->_remove_relation_to($term_taxonomy, 'Term_Taxonomy');
101
+	}
102
+
103
+
104
+	/**
105
+	 * The main purpose of this method is to return the post type for the model object
106
+	 *
107
+	 * @return string
108
+	 * @throws EE_Error
109
+	 * @throws ReflectionException
110
+	 */
111
+	public function post_type(): string
112
+	{
113
+		return $this->get_model()->post_type();
114
+	}
115
+
116
+
117
+	/**
118
+	 * The main purpose of this method is to return the parent for the model object
119
+	 *
120
+	 * @return int
121
+	 * @throws EE_Error
122
+	 */
123
+	public function parent(): int
124
+	{
125
+		return $this->get('parent');
126
+	}
127
+
128
+
129
+	/**
130
+	 * return the _status property
131
+	 *
132
+	 * @return string
133
+	 * @throws EE_Error
134
+	 */
135
+	public function status(): string
136
+	{
137
+		return $this->get('status');
138
+	}
139
+
140
+
141
+	/**
142
+	 * @param string $status
143
+	 * @throws EE_Error
144
+	 * @throws ReflectionException
145
+	 */
146
+	public function set_status(string $status)
147
+	{
148
+		$this->set('status', $status);
149
+	}
150
+
151
+
152
+	/**
153
+	 * See _get_feature_image. Returns the HTML to display a featured image
154
+	 *
155
+	 * @param string       $size
156
+	 * @param string|array $attr
157
+	 * @return string of html
158
+	 * @throws EE_Error
159
+	 * @throws ReflectionException
160
+	 */
161
+	public function feature_image(string $size = 'thumbnail', $attr = ''): string
162
+	{
163
+		return $this->_get_feature_image($size, $attr);
164
+	}
165
+
166
+
167
+	/**
168
+	 * This calls the equivalent model method for retrieving the feature image
169
+	 * which in turn is a wrapper for WordPress' get_the_post_thumbnail() function.
170
+	 *
171
+	 * @link   http://codex.wordpress.org/Function_Reference/get_the_post_thumbnail
172
+	 * @access protected
173
+	 * @param string|array $size (optional) Image size. Defaults to 'post-thumbnail' but can also be a 2-item array
174
+	 *                           representing width and height in pixels (i.e. array(32,32) ).
175
+	 * @param string|array $attr Optional. Query string or array of attributes.
176
+	 * @return string HTML image element
177
+	 * @throws EE_Error
178
+	 * @throws ReflectionException
179
+	 */
180
+	protected function _get_feature_image($size, $attr): string
181
+	{
182
+		// first let's see if we already have the _feature_image property set
183
+		// AND if it has a cached element on it FOR the given size
184
+		$attr_key                           = is_array($attr)
185
+			? implode('_', $attr)
186
+			: $attr;
187
+		$cache_key                          = is_array($size)
188
+			? implode('_', $size) . $attr_key
189
+			: $size . $attr_key;
190
+		$this->_feature_image[ $cache_key ] = $this->_feature_image[ $cache_key ]
191
+											  ?? $this->get_model()->get_feature_image($this->ID(), $size, $attr);
192
+		return $this->_feature_image[ $cache_key ];
193
+	}
194
+
195
+
196
+	/**
197
+	 * This uses the wp "wp_get_attachment_image_src()" function to return the feature image for the current class
198
+	 * using the given size params.
199
+	 *
200
+	 * @param string|array $size  can either be a string: 'thumbnail', 'medium', 'large', 'full' OR 2-item array
201
+	 *                            representing width and height in pixels eg. array(32,32).
202
+	 * @return string|boolean          the url of the image or false if not found
203
+	 * @throws EE_Error
204
+	 */
205
+	public function feature_image_url($size = 'thumbnail')
206
+	{
207
+		$attachment = wp_get_attachment_image_src(get_post_thumbnail_id($this->ID()), $size);
208
+		return ! empty($attachment)
209
+			? $attachment[0]
210
+			: false;
211
+	}
212
+
213
+
214
+	/**
215
+	 * This is a method for restoring this_obj using details from the given $revision_id
216
+	 *
217
+	 * @param int   $revision_id       ID of the revision we're getting data from
218
+	 * @param array $related_obj_names if included this will be used to restore for related obj
219
+	 *                                 if not included then we just do restore on the meta.
220
+	 *                                 We will accept an array of related_obj_names for restoration here.
221
+	 * @param array $where_query       You can optionally include an array of key=>value pairs
222
+	 *                                 that allow you to further constrict the relation to being added.
223
+	 *                                 However, keep in mind that the columns (keys) given
224
+	 *                                 must match a column on the JOIN table and currently
225
+	 *                                 only the HABTM models accept these additional conditions.
226
+	 *                                 Also remember that if an exact match isn't found for these extra cols/val pairs,
227
+	 *                                 then a NEW row is created in the join table.
228
+	 *                                 This array is INDEXED by RELATED OBJ NAME (so it corresponds with the obj_names
229
+	 *                                 sent);
230
+	 * @return void
231
+	 * @throws EE_Error
232
+	 * @throws ReflectionException
233
+	 */
234
+	public function restore_revision(int $revision_id, array $related_obj_names = [], array $where_query = [])
235
+	{
236
+		// get revision object
237
+		$revision_obj = $this->get_model()->get_one_by_ID($revision_id);
238
+		if ($revision_obj instanceof EE_CPT_Base) {
239
+			// no related_obj_name so we assume we're saving a revision on this object.
240
+			if (empty($related_obj_names)) {
241
+				$fields = $this->get_model()->get_meta_table_fields();
242
+				foreach ($fields as $field) {
243
+					$this->set($field, $revision_obj->get($field));
244
+				}
245
+				$this->save();
246
+			}
247
+			foreach ($related_obj_names as $related_name) {
248
+				// related_obj_name so we're saving a revision on an object related to this object
249
+				// do we have $where_query params for this related object?  If we do then we include that.
250
+				$cols_n_values         = $where_query[ $related_name ] ?? [];
251
+				$where_params          = ! empty($cols_n_values)
252
+					? [$cols_n_values]
253
+					: [];
254
+				$related_objs          = $this->get_many_related($related_name, $where_params);
255
+				$revision_related_objs = $revision_obj->get_many_related($related_name, $where_params);
256
+				// load helper
257
+				// remove related objs from this object that are not in revision
258
+				// array_diff *should* work cause I think objects are indexed by ID?
259
+				$related_to_remove = EEH_Array::object_array_diff($related_objs, $revision_related_objs);
260
+				foreach ($related_to_remove as $rr) {
261
+					$this->_remove_relation_to($rr, $related_name, $cols_n_values);
262
+				}
263
+				// add all related objs attached to revision to this object
264
+				foreach ($revision_related_objs as $r_obj) {
265
+					$this->_add_relation_to($r_obj, $related_name, $cols_n_values);
266
+				}
267
+			}
268
+		}
269
+	}
270
+
271
+
272
+	/**
273
+	 * Wrapper for get_post_meta, http://codex.wordpress.org/Function_Reference/get_post_meta
274
+	 * If only $id is set:
275
+	 *  it will return all meta values in an associative array.
276
+	 * If $single is set to false, or left blank:
277
+	 *  the function returns an array containing all values of the specified key.
278
+	 * If $single is set to true:
279
+	 *  the function returns the first value of the specified key (not in an array)
280
+	 *
281
+	 * @param string $meta_key
282
+	 * @param bool   $single
283
+	 * @return mixed
284
+	 * @throws EE_Error
285
+	 */
286
+	public function get_post_meta(string $meta_key = '', bool $single = false)
287
+	{
288
+		return get_post_meta($this->ID(), $meta_key, $single);
289
+	}
290
+
291
+
292
+	/**
293
+	 * Wrapper for update_post_meta, http://codex.wordpress.org/Function_Reference/update_post_meta
294
+	 * Returns meta_id if the meta doesn't exist,
295
+	 * otherwise returns true on success and false on failure.
296
+	 * NOTE: If the meta_value passed to this function is the same
297
+	 * as the value that is already in the database, this function returns false.
298
+	 *
299
+	 * @param string $meta_key
300
+	 * @param mixed  $meta_value
301
+	 * @param mixed  $prev_value
302
+	 * @return bool|int
303
+	 * @throws EE_Error
304
+	 * @throws ReflectionException
305
+	 */
306
+	public function update_post_meta(string $meta_key, $meta_value, $prev_value = null)
307
+	{
308
+		if (! $this->ID()) {
309
+			$this->save();
310
+		}
311
+		return update_post_meta($this->ID(), $meta_key, $meta_value, $prev_value);
312
+	}
313
+
314
+
315
+	/**
316
+	 * Wrapper for add_post_meta, http://codex.wordpress.org/Function_Reference/add_post_meta
317
+	 *
318
+	 * @param string $meta_key
319
+	 * @param mixed $meta_value
320
+	 * @param bool  $unique     If postmeta for this $meta_key already exists,
321
+	 *                          whether to add an additional item or not
322
+	 * @return boolean          Boolean true, except if the $unique argument was set to true
323
+	 *                          and a custom field with the given key already exists,
324
+	 *                          in which case false is returned.
325
+	 * @throws EE_Error
326
+	 * @throws ReflectionException
327
+	 */
328
+	public function add_post_meta(string $meta_key, $meta_value, bool $unique = false): bool
329
+	{
330
+		if ($this->ID()) {
331
+			$this->save();
332
+		}
333
+		return add_post_meta($this->ID(), $meta_key, $meta_value, $unique);
334
+	}
335
+
336
+
337
+	/**
338
+	 * Wrapper for delete_post_meta, http://codex.wordpress.org/Function_Reference/delete_post_meta
339
+	 *
340
+	 * @param string $meta_key
341
+	 * @param mixed $meta_value
342
+	 * @return boolean False for failure. True for success.
343
+	 * @throws EE_Error
344
+	 */
345
+	public function delete_post_meta(string $meta_key, $meta_value = ''): bool
346
+	{
347
+		if (! $this->ID()) {
348
+			// there is obviously no postmeta for this if it's not saved
349
+			// so let's just report this as a success
350
+			return true;
351
+		}
352
+		return delete_post_meta($this->ID(), $meta_key, $meta_value);
353
+	}
354
+
355
+
356
+	/**
357
+	 * Gets the URL for viewing this event on the front-end
358
+	 *
359
+	 * @return string
360
+	 * @throws EE_Error
361
+	 */
362
+	public function get_permalink(): string
363
+	{
364
+		return get_permalink($this->ID());
365
+	}
366
+
367
+
368
+	/**
369
+	 * Gets all the term-taxonomies for this CPT
370
+	 *
371
+	 * @param array $query_params
372
+	 * @return EE_Term_Taxonomy[]
373
+	 * @throws EE_Error
374
+	 * @throws ReflectionException
375
+	 */
376
+	public function term_taxonomies(array $query_params = []): array
377
+	{
378
+		return $this->get_many_related('Term_Taxonomy', $query_params);
379
+	}
380
+
381
+
382
+	/**
383
+	 * @return string[]
384
+	 * @throws EE_Error
385
+	 * @throws ReflectionException
386
+	 */
387
+	public function get_custom_post_statuses(): array
388
+	{
389
+		return $this->get_model()->get_custom_post_statuses();
390
+	}
391
+
392
+
393
+	/**
394
+	 * @return string[]
395
+	 * @throws EE_Error
396
+	 * @throws ReflectionException
397
+	 */
398
+	public function get_all_post_statuses(): array
399
+	{
400
+		return $this->get_model()->get_status_array();
401
+	}
402
+
403
+
404
+	/**
405
+	 * When fetching a new value for a post field that uses the global $post for rendering,
406
+	 * set the global $post temporarily to be this model object; and afterwards restore it
407
+	 *
408
+	 * @param string      $field_name
409
+	 * @param bool        $pretty
410
+	 * @param string|null $extra_cache_ref
411
+	 * @return mixed
412
+	 * @throws EE_Error
413
+	 * @throws ReflectionException
414
+	 */
415
+	protected function _get_fresh_property(string $field_name, bool $pretty = false, string $extra_cache_ref = '')
416
+	{
417
+		global $post;
418
+
419
+		if (
420
+			$pretty
421
+			&& (
422
+				! ($post instanceof WP_Post && $post->ID)
423
+				|| absint($post->ID) !== $this->ID()
424
+			)
425
+			&& $this->get_model()->field_settings_for($field_name) instanceof EE_Post_Content_Field
426
+		) {
427
+			$old_post     = $post;
428
+			$post         = $this->wp_post();
429
+			$return_value = parent::_get_fresh_property($field_name, $pretty, $extra_cache_ref);
430
+			$post         = $old_post;
431
+		} else {
432
+			$return_value = parent::_get_fresh_property($field_name, $pretty, $extra_cache_ref);
433
+		}
434
+		return $return_value;
435
+	}
436
+
437
+
438
+	/**
439
+	 * Returns the WP post associated with this CPT model object.
440
+	 * If this CPT is saved, fetches it from the DB.
441
+	 * Otherwise, create an unsaved WP_POst object. Caches the post internally.
442
+	 *
443
+	 * @return WP_Post
444
+	 * @throws EE_Error
445
+	 * @throws ReflectionException
446
+	 */
447
+	public function wp_post(): WP_Post
448
+	{
449
+		global $wpdb;
450
+		if (! $this->_wp_post instanceof WP_Post) {
451
+			if ($this->ID()) {
452
+				$this->_wp_post = get_post($this->ID());
453
+			} else {
454
+				$simulated_db_result = new stdClass();
455
+				$field_settings      = $this->get_model()->field_settings(true);
456
+				foreach ($field_settings as $field_name => $field_obj) {
457
+					if (
458
+						$this->get_model()->get_table_obj_by_alias($field_obj->get_table_alias())
459
+							 ->get_table_name() === $wpdb->posts
460
+					) {
461
+						$column = $field_obj->get_table_column();
462
+
463
+						if ($field_obj instanceof EE_Datetime_Field) {
464
+							$value_on_model_obj = $this->get_DateTime_object($field_name);
465
+						} elseif ($field_obj->is_db_only_field()) {
466
+							$value_on_model_obj = $field_obj->get_default_value();
467
+						} else {
468
+							$value_on_model_obj = $this->get_raw($field_name);
469
+						}
470
+						$simulated_db_result->{$column} = $field_obj->prepare_for_use_in_db($value_on_model_obj);
471
+					}
472
+				}
473
+				$this->_wp_post = new WP_Post($simulated_db_result);
474
+			}
475
+			// and let's make retrieving the EE CPT object easy too
476
+			$classname = get_class($this);
477
+			if (! isset($this->_wp_post->{$classname})) {
478
+				$this->_wp_post->{$classname} = $this;
479
+			}
480
+		}
481
+		return $this->_wp_post;
482
+	}
483
+
484
+
485
+	/**
486
+	 * Don't serialize the WP Post. That's just duplicate data and we want to avoid recursion
487
+	 *
488
+	 * @return array
489
+	 * @throws EE_Error
490
+	 * @throws ReflectionException
491
+	 */
492
+	public function __sleep()
493
+	{
494
+		$properties_to_serialize = parent::__sleep();
495
+		return array_diff($properties_to_serialize, ['_wp_post']);
496
+	}
497 497
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
      */
80 80
     public function remove_relation_to_term_taxonomy(EE_Term_Taxonomy $term_taxonomy): ?EE_Base_Class
81 81
     {
82
-        if (! $term_taxonomy) {
82
+        if ( ! $term_taxonomy) {
83 83
             EE_Error::add_error(
84 84
                 sprintf(
85 85
                     esc_html__(
@@ -185,11 +185,11 @@  discard block
 block discarded – undo
185 185
             ? implode('_', $attr)
186 186
             : $attr;
187 187
         $cache_key                          = is_array($size)
188
-            ? implode('_', $size) . $attr_key
189
-            : $size . $attr_key;
190
-        $this->_feature_image[ $cache_key ] = $this->_feature_image[ $cache_key ]
188
+            ? implode('_', $size).$attr_key
189
+            : $size.$attr_key;
190
+        $this->_feature_image[$cache_key] = $this->_feature_image[$cache_key]
191 191
                                               ?? $this->get_model()->get_feature_image($this->ID(), $size, $attr);
192
-        return $this->_feature_image[ $cache_key ];
192
+        return $this->_feature_image[$cache_key];
193 193
     }
194 194
 
195 195
 
@@ -247,7 +247,7 @@  discard block
 block discarded – undo
247 247
             foreach ($related_obj_names as $related_name) {
248 248
                 // related_obj_name so we're saving a revision on an object related to this object
249 249
                 // do we have $where_query params for this related object?  If we do then we include that.
250
-                $cols_n_values         = $where_query[ $related_name ] ?? [];
250
+                $cols_n_values         = $where_query[$related_name] ?? [];
251 251
                 $where_params          = ! empty($cols_n_values)
252 252
                     ? [$cols_n_values]
253 253
                     : [];
@@ -305,7 +305,7 @@  discard block
 block discarded – undo
305 305
      */
306 306
     public function update_post_meta(string $meta_key, $meta_value, $prev_value = null)
307 307
     {
308
-        if (! $this->ID()) {
308
+        if ( ! $this->ID()) {
309 309
             $this->save();
310 310
         }
311 311
         return update_post_meta($this->ID(), $meta_key, $meta_value, $prev_value);
@@ -344,7 +344,7 @@  discard block
 block discarded – undo
344 344
      */
345 345
     public function delete_post_meta(string $meta_key, $meta_value = ''): bool
346 346
     {
347
-        if (! $this->ID()) {
347
+        if ( ! $this->ID()) {
348 348
             // there is obviously no postmeta for this if it's not saved
349 349
             // so let's just report this as a success
350 350
             return true;
@@ -447,7 +447,7 @@  discard block
 block discarded – undo
447 447
     public function wp_post(): WP_Post
448 448
     {
449 449
         global $wpdb;
450
-        if (! $this->_wp_post instanceof WP_Post) {
450
+        if ( ! $this->_wp_post instanceof WP_Post) {
451 451
             if ($this->ID()) {
452 452
                 $this->_wp_post = get_post($this->ID());
453 453
             } else {
@@ -474,7 +474,7 @@  discard block
 block discarded – undo
474 474
             }
475 475
             // and let's make retrieving the EE CPT object easy too
476 476
             $classname = get_class($this);
477
-            if (! isset($this->_wp_post->{$classname})) {
477
+            if ( ! isset($this->_wp_post->{$classname})) {
478 478
                 $this->_wp_post->{$classname} = $this;
479 479
             }
480 480
         }
Please login to merge, or discard this patch.
core/db_classes/EE_Payment_Method.class.php 1 patch
Indentation   +570 added lines, -570 removed lines patch added patch discarded remove patch
@@ -11,580 +11,580 @@
 block discarded – undo
11 11
 class EE_Payment_Method extends EE_Base_Class
12 12
 {
13 13
 
14
-    /**
15
-     * Payment Method type object, which has all the info about this type of payment method,
16
-     * including functions for processing payments, to get settings forms, etc.
17
-     *
18
-     * @var EE_PMT_Base
19
-     */
20
-    protected $_type_obj;
21
-
22
-
23
-    /**
24
-     * @param array $props_n_values
25
-     * @return EE_Payment_Method
26
-     * @throws \EE_Error
27
-     */
28
-    public static function new_instance($props_n_values = array())
29
-    {
30
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__);
31
-        return $has_object ? $has_object : new self($props_n_values, false);
32
-    }
33
-
34
-
35
-    /**
36
-     * @param array $props_n_values
37
-     * @return EE_Payment_Method
38
-     * @throws \EE_Error
39
-     */
40
-    public static function new_instance_from_db($props_n_values = array())
41
-    {
42
-        return new self($props_n_values, true);
43
-    }
44
-
45
-
46
-
47
-    /**
48
-     * Checks if there is a payment method class of the given 'PMD_type', and if so returns the classname.
49
-     * Otherwise returns a normal EE_Payment_Method
50
-     *
51
-     * @param array $props_n_values where 'PMD_type' is a gateway name like 'Paypal_Standard','Invoice',etc (basically
52
-     *                              the classname minus 'EEPM_')
53
-     * @return string
54
-     */
55
-    // private static function _payment_method_type($props_n_values)
56
-    // {
57
-    //     EE_Registry::instance()->load_lib('Payment_Method_Manager');
58
-    //     $type_string = isset($props_n_values['PMD_type']) ? $props_n_values['PMD_type'] : null;
59
-    //     if (EE_Payment_Method_Manager::instance()->payment_method_type_exists($type_string)) {
60
-    //         return 'EEPM_' . $type_string;
61
-    //     } else {
62
-    //         return __CLASS__;
63
-    //     }
64
-    // }
65
-
66
-
67
-    /**
68
-     * Gets whether this payment method can be used anywhere at all (ie frontend cart, admin, etc)
69
-     *
70
-     * @return boolean
71
-     */
72
-    public function active()
73
-    {
74
-        return array_intersect(array_keys(EEM_Payment_Method::instance()->scopes()), $this->scope());
75
-    }
76
-
77
-
78
-    /**
79
-     * Sets this PM as active by making it usable within the CART scope. Offline gateways
80
-     * are also usable from the admin-scope as well. DOES NOT SAVE it
81
-     *
82
-     * @throws \EE_Error
83
-     */
84
-    public function set_active()
85
-    {
86
-        $default_scopes = array(EEM_Payment_Method::scope_cart);
87
-        if (
88
-            $this->type_obj() &&
89
-            $this->type_obj()->payment_occurs() === EE_PMT_Base::offline
90
-        ) {
91
-            $default_scopes[] = EEM_Payment_Method::scope_admin;
92
-        }
93
-        $this->set_scope($default_scopes);
94
-    }
95
-
96
-
97
-    /**
98
-     * Makes this payment method apply to NO scopes at all. DOES NOT SAVE it.
99
-     */
100
-    public function deactivate()
101
-    {
102
-        $this->set_scope(array());
103
-    }
104
-
105
-
106
-    /**
107
-     * Gets button_url
108
-     *
109
-     * @return string
110
-     */
111
-    public function button_url()
112
-    {
113
-        return $this->get('PMD_button_url');
114
-    }
115
-
116
-
117
-    /**
118
-     * Sets button_url
119
-     *
120
-     * @param string $button_url
121
-     */
122
-    public function set_button_url($button_url)
123
-    {
124
-        $this->set('PMD_button_url', $button_url);
125
-    }
126
-
127
-
128
-    /**
129
-     * Gets debug_mode
130
-     *
131
-     * @return boolean
132
-     */
133
-    public function debug_mode()
134
-    {
135
-        return $this->get('PMD_debug_mode');
136
-    }
137
-
138
-
139
-    /**
140
-     * Sets debug_mode
141
-     *
142
-     * @param boolean $debug_mode
143
-     */
144
-    public function set_debug_mode($debug_mode)
145
-    {
146
-        $this->set('PMD_debug_mode', $debug_mode);
147
-    }
148
-
149
-
150
-    /**
151
-     * Gets description
152
-     *
153
-     * @return string
154
-     */
155
-    public function description()
156
-    {
157
-        return $this->get('PMD_desc');
158
-    }
159
-
160
-
161
-    /**
162
-     * Sets description
163
-     *
164
-     * @param string $description
165
-     */
166
-    public function set_description($description)
167
-    {
168
-        $this->set('PMD_desc', $description);
169
-    }
170
-
171
-
172
-    /**
173
-     * Gets name
174
-     *
175
-     * @return string
176
-     */
177
-    public function name()
178
-    {
179
-        return $this->get('PMD_name');
180
-    }
181
-
182
-
183
-    /**
184
-     * Sets name
185
-     *
186
-     * @param string $name
187
-     */
188
-    public function set_name($name)
189
-    {
190
-        $this->set('PMD_name', $name);
191
-    }
192
-
193
-
194
-    /**
195
-     * Gets open_by_default
196
-     *
197
-     * @return boolean
198
-     */
199
-    public function open_by_default()
200
-    {
201
-        return $this->get('PMD_open_by_default');
202
-    }
203
-
204
-
205
-    /**
206
-     * Sets open_by_default
207
-     *
208
-     * @param boolean $open_by_default
209
-     */
210
-    public function set_open_by_default($open_by_default)
211
-    {
212
-        $this->set('PMD_open_by_default', $open_by_default);
213
-    }
214
-
215
-
216
-    /**
217
-     * Gets order
218
-     *
219
-     * @return int
220
-     */
221
-    public function order()
222
-    {
223
-        return $this->get('PMD_order');
224
-    }
225
-
226
-
227
-    /**
228
-     * Sets order
229
-     *
230
-     * @param int $order
231
-     */
232
-    public function set_order($order)
233
-    {
234
-        $this->set('PMD_order', $order);
235
-    }
236
-
237
-
238
-    /**
239
-     * Gets slug
240
-     *
241
-     * @return string
242
-     */
243
-    public function slug()
244
-    {
245
-        return $this->get('PMD_slug');
246
-    }
247
-
248
-
249
-    /**
250
-     * Sets slug
251
-     *
252
-     * @param string $slug
253
-     */
254
-    public function set_slug($slug)
255
-    {
256
-        $this->set('PMD_slug', $slug);
257
-    }
258
-
259
-
260
-    /**
261
-     * Gets type
262
-     *
263
-     * @return string
264
-     */
265
-    public function type()
266
-    {
267
-        return $this->get('PMD_type');
268
-    }
269
-
270
-
271
-    /**
272
-     * Sets type
273
-     *
274
-     * @param string $type
275
-     */
276
-    public function set_type($type)
277
-    {
278
-        $this->set('PMD_type', $type);
279
-    }
280
-
281
-
282
-    /**
283
-     * Gets wp_user
284
-     *
285
-     * @return int
286
-     */
287
-    public function wp_user()
288
-    {
289
-        return $this->get('PMD_wp_user');
290
-    }
291
-
292
-
293
-    /**
294
-     * Sets wp_user
295
-     *
296
-     * @param int $wp_user_id
297
-     */
298
-    public function set_wp_user($wp_user_id)
299
-    {
300
-        $this->set('PMD_wp_user', $wp_user_id);
301
-    }
302
-
303
-    /**
304
-     * Overrides parent so when PMD_type is changed we refresh the _type_obj
305
-     *
306
-     * @param string  $field_name
307
-     * @param mixed   $field_value
308
-     * @param boolean $use_default
309
-     */
310
-    public function set($field_name, $field_value, bool $use_default = false)
311
-    {
312
-        if ($field_name === 'PMD_type') {
313
-            // the type has probably changed, so forget about its old type object
314
-            $this->_type_obj = null;
315
-        }
316
-        parent::set($field_name, $field_value, $use_default);
317
-    }
318
-
319
-
320
-    /**
321
-     * Gets admin_name
322
-     *
323
-     * @return string
324
-     */
325
-    public function admin_name()
326
-    {
327
-        return $this->get('PMD_admin_name');
328
-    }
329
-
330
-
331
-    /**
332
-     * Sets admin_name
333
-     *
334
-     * @param string $admin_name
335
-     */
336
-    public function set_admin_name($admin_name)
337
-    {
338
-        $this->set('PMD_admin_name', $admin_name);
339
-    }
340
-
341
-
342
-    /**
343
-     * Gets admin_desc
344
-     *
345
-     * @return string
346
-     */
347
-    public function admin_desc()
348
-    {
349
-        return $this->get('PMD_admin_desc');
350
-    }
351
-
352
-
353
-    /**
354
-     * Sets admin_desc
355
-     *
356
-     * @param string $admin_desc
357
-     */
358
-    public function set_admin_desc($admin_desc)
359
-    {
360
-        $this->set('PMD_admin_desc', $admin_desc);
361
-    }
362
-
363
-
364
-    /**
365
-     * Gets scope
366
-     *
367
-     * @return array
368
-     */
369
-    public function scope()
370
-    {
371
-        return $this->get('PMD_scope');
372
-    }
373
-
374
-
375
-    /**
376
-     * Sets scope
377
-     *
378
-     * @param array $scope
379
-     */
380
-    public function set_scope($scope)
381
-    {
382
-        $this->set('PMD_scope', $scope);
383
-    }
384
-
385
-
386
-    /**
387
-     * Gets the payment method type for this payment method instance
388
-     *
389
-     * @return EE_PMT_Base
390
-     * @throws EE_Error
391
-     */
392
-    public function type_obj()
393
-    {
394
-        if (! $this->_type_obj) {
395
-            EE_Registry::instance()->load_lib('Payment_Method_Manager');
396
-            if (EE_Payment_Method_Manager::instance()->payment_method_type_exists($this->type())) {
397
-                $class_name = EE_Payment_Method_Manager::instance()->payment_method_class_from_type($this->type());
398
-                if (! class_exists($class_name)) {
399
-                    throw new EE_Error(
400
-                        sprintf(
401
-                            __(
402
-                                'An attempt to use the "%1$s" payment method failed, so it was deactivated.%2$sWas the "%1$s" Plugin recently deactivated? It can be reactivated on the %3$sPlugins Admin Page%4$s',
403
-                                'event_espresso'
404
-                            ),
405
-                            $class_name,
406
-                            '<br />',
407
-                            '<a href="' . admin_url('plugins.php') . '">',
408
-                            '</a>'
409
-                        )
410
-                    );
411
-                }
412
-                $r = new ReflectionClass($class_name);
413
-                $this->_type_obj = $r->newInstanceArgs(array($this));
414
-            } else {
415
-                throw new EE_Error(
416
-                    sprintf(
417
-                        __(
418
-                            'A payment method of type "%1$s" does not exist. Only ones existing are: %2$s',
419
-                            'event_espresso'
420
-                        ),
421
-                        $this->type(),
422
-                        implode(',', EE_Payment_Method_Manager::instance()->payment_method_type_names())
423
-                    )
424
-                );
425
-            }
426
-        }
427
-        return $this->_type_obj;
428
-    }
429
-
430
-
431
-    /**
432
-     * Returns a simple array of key-value pairs combining the payment method's fields (without the 'PMD_' prefix)
433
-     * and the extra meta. Mostly used for passing off ot gateways.     *
434
-     *
435
-     * @return array
436
-     */
437
-    public function settings_array()
438
-    {
439
-        $fields = $this->model_field_array();
440
-        $extra_meta = $this->all_extra_meta_array();
441
-        // remove the model's prefix from the fields
442
-        $combined_settings_array = array();
443
-        foreach ($fields as $key => $value) {
444
-            if (strpos($key, 'PMD_') === 0) {
445
-                $key_sans_model_prefix = str_replace('PMD_', '', $key);
446
-                $combined_settings_array [ $key_sans_model_prefix ] = $value;
447
-            }
448
-        }
449
-        $combined_settings_array = array_merge($extra_meta, $combined_settings_array);
450
-        return $combined_settings_array;
451
-    }
452
-
453
-
454
-    /**
455
-     * Gets the HTML for displaying the payment method on a page.
456
-     *
457
-     * @param string $url
458
-     * @param string $css_class
459
-     * @return string of HTML for displaying the button
460
-     * @throws \EE_Error
461
-     */
462
-    public function button_html($url = '', $css_class = '')
463
-    {
464
-        $payment_occurs = $this->type_obj()->payment_occurs();
465
-        return '
14
+	/**
15
+	 * Payment Method type object, which has all the info about this type of payment method,
16
+	 * including functions for processing payments, to get settings forms, etc.
17
+	 *
18
+	 * @var EE_PMT_Base
19
+	 */
20
+	protected $_type_obj;
21
+
22
+
23
+	/**
24
+	 * @param array $props_n_values
25
+	 * @return EE_Payment_Method
26
+	 * @throws \EE_Error
27
+	 */
28
+	public static function new_instance($props_n_values = array())
29
+	{
30
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__);
31
+		return $has_object ? $has_object : new self($props_n_values, false);
32
+	}
33
+
34
+
35
+	/**
36
+	 * @param array $props_n_values
37
+	 * @return EE_Payment_Method
38
+	 * @throws \EE_Error
39
+	 */
40
+	public static function new_instance_from_db($props_n_values = array())
41
+	{
42
+		return new self($props_n_values, true);
43
+	}
44
+
45
+
46
+
47
+	/**
48
+	 * Checks if there is a payment method class of the given 'PMD_type', and if so returns the classname.
49
+	 * Otherwise returns a normal EE_Payment_Method
50
+	 *
51
+	 * @param array $props_n_values where 'PMD_type' is a gateway name like 'Paypal_Standard','Invoice',etc (basically
52
+	 *                              the classname minus 'EEPM_')
53
+	 * @return string
54
+	 */
55
+	// private static function _payment_method_type($props_n_values)
56
+	// {
57
+	//     EE_Registry::instance()->load_lib('Payment_Method_Manager');
58
+	//     $type_string = isset($props_n_values['PMD_type']) ? $props_n_values['PMD_type'] : null;
59
+	//     if (EE_Payment_Method_Manager::instance()->payment_method_type_exists($type_string)) {
60
+	//         return 'EEPM_' . $type_string;
61
+	//     } else {
62
+	//         return __CLASS__;
63
+	//     }
64
+	// }
65
+
66
+
67
+	/**
68
+	 * Gets whether this payment method can be used anywhere at all (ie frontend cart, admin, etc)
69
+	 *
70
+	 * @return boolean
71
+	 */
72
+	public function active()
73
+	{
74
+		return array_intersect(array_keys(EEM_Payment_Method::instance()->scopes()), $this->scope());
75
+	}
76
+
77
+
78
+	/**
79
+	 * Sets this PM as active by making it usable within the CART scope. Offline gateways
80
+	 * are also usable from the admin-scope as well. DOES NOT SAVE it
81
+	 *
82
+	 * @throws \EE_Error
83
+	 */
84
+	public function set_active()
85
+	{
86
+		$default_scopes = array(EEM_Payment_Method::scope_cart);
87
+		if (
88
+			$this->type_obj() &&
89
+			$this->type_obj()->payment_occurs() === EE_PMT_Base::offline
90
+		) {
91
+			$default_scopes[] = EEM_Payment_Method::scope_admin;
92
+		}
93
+		$this->set_scope($default_scopes);
94
+	}
95
+
96
+
97
+	/**
98
+	 * Makes this payment method apply to NO scopes at all. DOES NOT SAVE it.
99
+	 */
100
+	public function deactivate()
101
+	{
102
+		$this->set_scope(array());
103
+	}
104
+
105
+
106
+	/**
107
+	 * Gets button_url
108
+	 *
109
+	 * @return string
110
+	 */
111
+	public function button_url()
112
+	{
113
+		return $this->get('PMD_button_url');
114
+	}
115
+
116
+
117
+	/**
118
+	 * Sets button_url
119
+	 *
120
+	 * @param string $button_url
121
+	 */
122
+	public function set_button_url($button_url)
123
+	{
124
+		$this->set('PMD_button_url', $button_url);
125
+	}
126
+
127
+
128
+	/**
129
+	 * Gets debug_mode
130
+	 *
131
+	 * @return boolean
132
+	 */
133
+	public function debug_mode()
134
+	{
135
+		return $this->get('PMD_debug_mode');
136
+	}
137
+
138
+
139
+	/**
140
+	 * Sets debug_mode
141
+	 *
142
+	 * @param boolean $debug_mode
143
+	 */
144
+	public function set_debug_mode($debug_mode)
145
+	{
146
+		$this->set('PMD_debug_mode', $debug_mode);
147
+	}
148
+
149
+
150
+	/**
151
+	 * Gets description
152
+	 *
153
+	 * @return string
154
+	 */
155
+	public function description()
156
+	{
157
+		return $this->get('PMD_desc');
158
+	}
159
+
160
+
161
+	/**
162
+	 * Sets description
163
+	 *
164
+	 * @param string $description
165
+	 */
166
+	public function set_description($description)
167
+	{
168
+		$this->set('PMD_desc', $description);
169
+	}
170
+
171
+
172
+	/**
173
+	 * Gets name
174
+	 *
175
+	 * @return string
176
+	 */
177
+	public function name()
178
+	{
179
+		return $this->get('PMD_name');
180
+	}
181
+
182
+
183
+	/**
184
+	 * Sets name
185
+	 *
186
+	 * @param string $name
187
+	 */
188
+	public function set_name($name)
189
+	{
190
+		$this->set('PMD_name', $name);
191
+	}
192
+
193
+
194
+	/**
195
+	 * Gets open_by_default
196
+	 *
197
+	 * @return boolean
198
+	 */
199
+	public function open_by_default()
200
+	{
201
+		return $this->get('PMD_open_by_default');
202
+	}
203
+
204
+
205
+	/**
206
+	 * Sets open_by_default
207
+	 *
208
+	 * @param boolean $open_by_default
209
+	 */
210
+	public function set_open_by_default($open_by_default)
211
+	{
212
+		$this->set('PMD_open_by_default', $open_by_default);
213
+	}
214
+
215
+
216
+	/**
217
+	 * Gets order
218
+	 *
219
+	 * @return int
220
+	 */
221
+	public function order()
222
+	{
223
+		return $this->get('PMD_order');
224
+	}
225
+
226
+
227
+	/**
228
+	 * Sets order
229
+	 *
230
+	 * @param int $order
231
+	 */
232
+	public function set_order($order)
233
+	{
234
+		$this->set('PMD_order', $order);
235
+	}
236
+
237
+
238
+	/**
239
+	 * Gets slug
240
+	 *
241
+	 * @return string
242
+	 */
243
+	public function slug()
244
+	{
245
+		return $this->get('PMD_slug');
246
+	}
247
+
248
+
249
+	/**
250
+	 * Sets slug
251
+	 *
252
+	 * @param string $slug
253
+	 */
254
+	public function set_slug($slug)
255
+	{
256
+		$this->set('PMD_slug', $slug);
257
+	}
258
+
259
+
260
+	/**
261
+	 * Gets type
262
+	 *
263
+	 * @return string
264
+	 */
265
+	public function type()
266
+	{
267
+		return $this->get('PMD_type');
268
+	}
269
+
270
+
271
+	/**
272
+	 * Sets type
273
+	 *
274
+	 * @param string $type
275
+	 */
276
+	public function set_type($type)
277
+	{
278
+		$this->set('PMD_type', $type);
279
+	}
280
+
281
+
282
+	/**
283
+	 * Gets wp_user
284
+	 *
285
+	 * @return int
286
+	 */
287
+	public function wp_user()
288
+	{
289
+		return $this->get('PMD_wp_user');
290
+	}
291
+
292
+
293
+	/**
294
+	 * Sets wp_user
295
+	 *
296
+	 * @param int $wp_user_id
297
+	 */
298
+	public function set_wp_user($wp_user_id)
299
+	{
300
+		$this->set('PMD_wp_user', $wp_user_id);
301
+	}
302
+
303
+	/**
304
+	 * Overrides parent so when PMD_type is changed we refresh the _type_obj
305
+	 *
306
+	 * @param string  $field_name
307
+	 * @param mixed   $field_value
308
+	 * @param boolean $use_default
309
+	 */
310
+	public function set($field_name, $field_value, bool $use_default = false)
311
+	{
312
+		if ($field_name === 'PMD_type') {
313
+			// the type has probably changed, so forget about its old type object
314
+			$this->_type_obj = null;
315
+		}
316
+		parent::set($field_name, $field_value, $use_default);
317
+	}
318
+
319
+
320
+	/**
321
+	 * Gets admin_name
322
+	 *
323
+	 * @return string
324
+	 */
325
+	public function admin_name()
326
+	{
327
+		return $this->get('PMD_admin_name');
328
+	}
329
+
330
+
331
+	/**
332
+	 * Sets admin_name
333
+	 *
334
+	 * @param string $admin_name
335
+	 */
336
+	public function set_admin_name($admin_name)
337
+	{
338
+		$this->set('PMD_admin_name', $admin_name);
339
+	}
340
+
341
+
342
+	/**
343
+	 * Gets admin_desc
344
+	 *
345
+	 * @return string
346
+	 */
347
+	public function admin_desc()
348
+	{
349
+		return $this->get('PMD_admin_desc');
350
+	}
351
+
352
+
353
+	/**
354
+	 * Sets admin_desc
355
+	 *
356
+	 * @param string $admin_desc
357
+	 */
358
+	public function set_admin_desc($admin_desc)
359
+	{
360
+		$this->set('PMD_admin_desc', $admin_desc);
361
+	}
362
+
363
+
364
+	/**
365
+	 * Gets scope
366
+	 *
367
+	 * @return array
368
+	 */
369
+	public function scope()
370
+	{
371
+		return $this->get('PMD_scope');
372
+	}
373
+
374
+
375
+	/**
376
+	 * Sets scope
377
+	 *
378
+	 * @param array $scope
379
+	 */
380
+	public function set_scope($scope)
381
+	{
382
+		$this->set('PMD_scope', $scope);
383
+	}
384
+
385
+
386
+	/**
387
+	 * Gets the payment method type for this payment method instance
388
+	 *
389
+	 * @return EE_PMT_Base
390
+	 * @throws EE_Error
391
+	 */
392
+	public function type_obj()
393
+	{
394
+		if (! $this->_type_obj) {
395
+			EE_Registry::instance()->load_lib('Payment_Method_Manager');
396
+			if (EE_Payment_Method_Manager::instance()->payment_method_type_exists($this->type())) {
397
+				$class_name = EE_Payment_Method_Manager::instance()->payment_method_class_from_type($this->type());
398
+				if (! class_exists($class_name)) {
399
+					throw new EE_Error(
400
+						sprintf(
401
+							__(
402
+								'An attempt to use the "%1$s" payment method failed, so it was deactivated.%2$sWas the "%1$s" Plugin recently deactivated? It can be reactivated on the %3$sPlugins Admin Page%4$s',
403
+								'event_espresso'
404
+							),
405
+							$class_name,
406
+							'<br />',
407
+							'<a href="' . admin_url('plugins.php') . '">',
408
+							'</a>'
409
+						)
410
+					);
411
+				}
412
+				$r = new ReflectionClass($class_name);
413
+				$this->_type_obj = $r->newInstanceArgs(array($this));
414
+			} else {
415
+				throw new EE_Error(
416
+					sprintf(
417
+						__(
418
+							'A payment method of type "%1$s" does not exist. Only ones existing are: %2$s',
419
+							'event_espresso'
420
+						),
421
+						$this->type(),
422
+						implode(',', EE_Payment_Method_Manager::instance()->payment_method_type_names())
423
+					)
424
+				);
425
+			}
426
+		}
427
+		return $this->_type_obj;
428
+	}
429
+
430
+
431
+	/**
432
+	 * Returns a simple array of key-value pairs combining the payment method's fields (without the 'PMD_' prefix)
433
+	 * and the extra meta. Mostly used for passing off ot gateways.     *
434
+	 *
435
+	 * @return array
436
+	 */
437
+	public function settings_array()
438
+	{
439
+		$fields = $this->model_field_array();
440
+		$extra_meta = $this->all_extra_meta_array();
441
+		// remove the model's prefix from the fields
442
+		$combined_settings_array = array();
443
+		foreach ($fields as $key => $value) {
444
+			if (strpos($key, 'PMD_') === 0) {
445
+				$key_sans_model_prefix = str_replace('PMD_', '', $key);
446
+				$combined_settings_array [ $key_sans_model_prefix ] = $value;
447
+			}
448
+		}
449
+		$combined_settings_array = array_merge($extra_meta, $combined_settings_array);
450
+		return $combined_settings_array;
451
+	}
452
+
453
+
454
+	/**
455
+	 * Gets the HTML for displaying the payment method on a page.
456
+	 *
457
+	 * @param string $url
458
+	 * @param string $css_class
459
+	 * @return string of HTML for displaying the button
460
+	 * @throws \EE_Error
461
+	 */
462
+	public function button_html($url = '', $css_class = '')
463
+	{
464
+		$payment_occurs = $this->type_obj()->payment_occurs();
465
+		return '
466 466
 		 <div id="'
467
-               . $this->slug()
468
-               . '-payment-option-dv" class="'
469
-               . $payment_occurs . '-payment-gateway reg-page-payment-option-dv' . $css_class . '">
467
+			   . $this->slug()
468
+			   . '-payment-option-dv" class="'
469
+			   . $payment_occurs . '-payment-gateway reg-page-payment-option-dv' . $css_class . '">
470 470
 			<a id="payment-gateway-button-' . $this->slug()
471
-               . '" class="reg-page-payment-option-lnk" rel="'
472
-               . $this->slug() . '" href="' . $url . '" >
471
+			   . '" class="reg-page-payment-option-lnk" rel="'
472
+			   . $this->slug() . '" href="' . $url . '" >
473 473
 				<img src="' . $this->button_url() . '" alt="' . sprintf(
474
-                   esc_attr__('Pay using %s', 'event_espresso'),
475
-                   $this->get_pretty('PMD_name', 'form_input')
476
-               ) . '" />
474
+				   esc_attr__('Pay using %s', 'event_espresso'),
475
+				   $this->get_pretty('PMD_name', 'form_input')
476
+			   ) . '" />
477 477
 			</a>
478 478
 		</div>
479 479
 ';
480
-    }
481
-
482
-
483
-    /**
484
-     * Gets all the currencies which are an option for this payment method
485
-     * (as defined by the gateway and the currently active currencies)
486
-     *
487
-     * @return EE_Currency[]
488
-     * @throws \EE_Error
489
-     */
490
-    public function get_all_usable_currencies()
491
-    {
492
-        return EEM_Currency::instance()->get_all_currencies_usable_by($this->type_obj());
493
-    }
494
-
495
-
496
-    /**
497
-     * Reports whether or not this payment method can be used for this payment method
498
-     *
499
-     * @param string $currency_code currency ID (code)
500
-     * @return boolean
501
-     * @throws \EE_Error
502
-     */
503
-    public function usable_for_currency($currency_code)
504
-    {
505
-        foreach ($this->get_all_usable_currencies() as $currency_obj) {
506
-            if ($currency_obj->ID() === $currency_code) {
507
-                return true;
508
-            }
509
-        }
510
-        return false;
511
-    }
512
-
513
-
514
-    /**
515
-     * Returns TRUE if this payment method's gateway is an instance of EE_Onsite_Gateway
516
-     *
517
-     * @return bool
518
-     * @throws \EE_Error
519
-     */
520
-    public function is_on_site()
521
-    {
522
-        return $this->type_obj()->payment_occurs() === EE_PMT_Base::onsite;
523
-    }
524
-
525
-
526
-    /**
527
-     * Returns TRUE if this payment method's gateway is an instance of EE_Offsite_Gateway
528
-     *
529
-     * @return bool
530
-     * @throws \EE_Error
531
-     */
532
-    public function is_off_site()
533
-    {
534
-        return $this->type_obj()->payment_occurs() === EE_PMT_Base::offsite;
535
-    }
536
-
537
-
538
-    /**
539
-     * Returns TRUE if this payment method does not utilize a gateway
540
-     *
541
-     * @return bool
542
-     * @throws \EE_Error
543
-     */
544
-    public function is_off_line()
545
-    {
546
-        return $this->type_obj()->payment_occurs() === EE_PMT_Base::offline;
547
-    }
548
-
549
-    /**
550
-     * Overrides default __sleep so the object type is NOT cached.
551
-     * This way we can rely on the normal EE_Payment_Method::type_obj() logic
552
-     * to load the required classes, and don't need them at the time of unserialization
553
-     *
554
-     * @return array
555
-     */
556
-    public function __sleep()
557
-    {
558
-        $properties = get_object_vars($this);
559
-        unset($properties['_type_obj']);
560
-        return array_keys($properties);
561
-    }
562
-
563
-
564
-    /**
565
-     * Overrides parent to add some logging for when payment methods get deactivated
566
-     *
567
-     * @param array $set_cols_n_values
568
-     * @return int @see EE_Base_Class::save()
569
-     * @throws \EE_Error
570
-     */
571
-    public function save($set_cols_n_values = array())
572
-    {
573
-        $results = parent::save($set_cols_n_values);
574
-        if ($this->get_original('PMD_scope') !== $this->get('PMD_scope')) {
575
-            EE_Log::instance()->log(
576
-                __FILE__,
577
-                __FUNCTION__,
578
-                sprintf(
579
-                    __('Set new scope on payment method %1$s to %2$s from %3$s on URL %4$s', 'event_espresso'),
580
-                    $this->name(),
581
-                    serialize($this->get_original('PMD_scope')),
582
-                    serialize($this->get('PMD_scope')),
583
-                    EE_Registry::instance()->REQ->get_current_page_permalink()
584
-                ),
585
-                'payment_method_change'
586
-            );
587
-        }
588
-        return $results;
589
-    }
480
+	}
481
+
482
+
483
+	/**
484
+	 * Gets all the currencies which are an option for this payment method
485
+	 * (as defined by the gateway and the currently active currencies)
486
+	 *
487
+	 * @return EE_Currency[]
488
+	 * @throws \EE_Error
489
+	 */
490
+	public function get_all_usable_currencies()
491
+	{
492
+		return EEM_Currency::instance()->get_all_currencies_usable_by($this->type_obj());
493
+	}
494
+
495
+
496
+	/**
497
+	 * Reports whether or not this payment method can be used for this payment method
498
+	 *
499
+	 * @param string $currency_code currency ID (code)
500
+	 * @return boolean
501
+	 * @throws \EE_Error
502
+	 */
503
+	public function usable_for_currency($currency_code)
504
+	{
505
+		foreach ($this->get_all_usable_currencies() as $currency_obj) {
506
+			if ($currency_obj->ID() === $currency_code) {
507
+				return true;
508
+			}
509
+		}
510
+		return false;
511
+	}
512
+
513
+
514
+	/**
515
+	 * Returns TRUE if this payment method's gateway is an instance of EE_Onsite_Gateway
516
+	 *
517
+	 * @return bool
518
+	 * @throws \EE_Error
519
+	 */
520
+	public function is_on_site()
521
+	{
522
+		return $this->type_obj()->payment_occurs() === EE_PMT_Base::onsite;
523
+	}
524
+
525
+
526
+	/**
527
+	 * Returns TRUE if this payment method's gateway is an instance of EE_Offsite_Gateway
528
+	 *
529
+	 * @return bool
530
+	 * @throws \EE_Error
531
+	 */
532
+	public function is_off_site()
533
+	{
534
+		return $this->type_obj()->payment_occurs() === EE_PMT_Base::offsite;
535
+	}
536
+
537
+
538
+	/**
539
+	 * Returns TRUE if this payment method does not utilize a gateway
540
+	 *
541
+	 * @return bool
542
+	 * @throws \EE_Error
543
+	 */
544
+	public function is_off_line()
545
+	{
546
+		return $this->type_obj()->payment_occurs() === EE_PMT_Base::offline;
547
+	}
548
+
549
+	/**
550
+	 * Overrides default __sleep so the object type is NOT cached.
551
+	 * This way we can rely on the normal EE_Payment_Method::type_obj() logic
552
+	 * to load the required classes, and don't need them at the time of unserialization
553
+	 *
554
+	 * @return array
555
+	 */
556
+	public function __sleep()
557
+	{
558
+		$properties = get_object_vars($this);
559
+		unset($properties['_type_obj']);
560
+		return array_keys($properties);
561
+	}
562
+
563
+
564
+	/**
565
+	 * Overrides parent to add some logging for when payment methods get deactivated
566
+	 *
567
+	 * @param array $set_cols_n_values
568
+	 * @return int @see EE_Base_Class::save()
569
+	 * @throws \EE_Error
570
+	 */
571
+	public function save($set_cols_n_values = array())
572
+	{
573
+		$results = parent::save($set_cols_n_values);
574
+		if ($this->get_original('PMD_scope') !== $this->get('PMD_scope')) {
575
+			EE_Log::instance()->log(
576
+				__FILE__,
577
+				__FUNCTION__,
578
+				sprintf(
579
+					__('Set new scope on payment method %1$s to %2$s from %3$s on URL %4$s', 'event_espresso'),
580
+					$this->name(),
581
+					serialize($this->get_original('PMD_scope')),
582
+					serialize($this->get('PMD_scope')),
583
+					EE_Registry::instance()->REQ->get_current_page_permalink()
584
+				),
585
+				'payment_method_change'
586
+			);
587
+		}
588
+		return $results;
589
+	}
590 590
 }
Please login to merge, or discard this patch.
core/business/EE_Transaction_Processor.class.php 1 patch
Indentation   +955 added lines, -955 removed lines patch added patch discarded remove patch
@@ -17,959 +17,959 @@
 block discarded – undo
17 17
 class EE_Transaction_Processor extends EE_Processor_Base
18 18
 {
19 19
 
20
-    /**
21
-     * @var EE_Registration_Processor $_instance
22
-     * @access    private
23
-     */
24
-    private static $_instance;
25
-
26
-    /**
27
-     * array of query WHERE params to use when retrieving cached registrations from a transaction
28
-     *
29
-     * @var array $registration_query_params
30
-     * @access private
31
-     */
32
-    private $_registration_query_params = array();
33
-
34
-    /**
35
-     * @deprecated
36
-     * @var string
37
-     */
38
-    protected $_old_txn_status;
39
-
40
-    /**
41
-     * @deprecated
42
-     * @var string
43
-     */
44
-    protected $_new_txn_status;
45
-
46
-
47
-    /**
48
-     * @singleton method used to instantiate class object
49
-     * @access    public
50
-     * @param array $registration_query_params
51
-     * @return EE_Transaction_Processor instance
52
-     */
53
-    public static function instance($registration_query_params = array())
54
-    {
55
-        // check if class object is instantiated
56
-        if (! self::$_instance instanceof EE_Transaction_Processor) {
57
-            self::$_instance = new self($registration_query_params);
58
-        }
59
-        return self::$_instance;
60
-    }
61
-
62
-
63
-    /**
64
-     * @param array $registration_query_params
65
-     */
66
-    private function __construct($registration_query_params = array())
67
-    {
68
-        // make sure some query params are set for retrieving registrations
69
-        $this->_set_registration_query_params($registration_query_params);
70
-    }
71
-
72
-
73
-    /**
74
-     * @access private
75
-     * @param array $registration_query_params
76
-     */
77
-    private function _set_registration_query_params($registration_query_params)
78
-    {
79
-        $this->_registration_query_params = ! empty($registration_query_params) ? $registration_query_params
80
-            : array('order_by' => array('REG_count' => 'ASC'));
81
-    }
82
-
83
-
84
-    /**
85
-     * manually_update_registration_statuses
86
-     *
87
-     * @access public
88
-     * @param EE_Transaction $transaction
89
-     * @param string         $new_reg_status
90
-     * @param array          $registration_query_params array of query WHERE params to use
91
-     *                                                  when retrieving cached registrations from a transaction
92
-     * @return    boolean
93
-     * @throws EE_Error
94
-     * @throws ReflectionException
95
-     */
96
-    public function manually_update_registration_statuses(
97
-        EE_Transaction $transaction,
98
-        $new_reg_status = '',
99
-        $registration_query_params = array()
100
-    ) {
101
-        $status_updates = $this->_call_method_on_registrations_via_Registration_Processor(
102
-            'manually_update_registration_status',
103
-            $transaction,
104
-            $registration_query_params,
105
-            $new_reg_status
106
-        );
107
-        // send messages
108
-        /** @type EE_Registration_Processor $registration_processor */
109
-        $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
110
-        $registration_processor->trigger_registration_update_notifications(
111
-            $transaction->primary_registration(),
112
-            array('manually_updated' => true)
113
-        );
114
-        do_action(
115
-            'AHEE__EE_Transaction_Processor__manually_update_registration_statuses',
116
-            $transaction,
117
-            $status_updates
118
-        );
119
-        return $status_updates;
120
-    }
121
-
122
-
123
-    /**
124
-     * toggle_registration_statuses_for_default_approved_events
125
-     *
126
-     * @access public
127
-     * @param EE_Transaction $transaction
128
-     * @param array          $registration_query_params array of query WHERE params to use
129
-     *                                                  when retrieving cached registrations from a transaction
130
-     * @return    boolean
131
-     * @throws EE_Error
132
-     */
133
-    public function toggle_registration_statuses_for_default_approved_events(
134
-        EE_Transaction $transaction,
135
-        $registration_query_params = array()
136
-    ) {
137
-        $status_updates = $this->_call_method_on_registrations_via_Registration_Processor(
138
-            'toggle_registration_status_for_default_approved_events',
139
-            $transaction,
140
-            $registration_query_params
141
-        );
142
-        do_action(
143
-            'AHEE__EE_Transaction_Processor__toggle_registration_statuses_for_default_approved_events',
144
-            $transaction,
145
-            $status_updates
146
-        );
147
-        return $status_updates;
148
-    }
149
-
150
-
151
-    /**
152
-     * toggle_registration_statuses_if_no_monies_owing
153
-     *
154
-     * @access public
155
-     * @param EE_Transaction $transaction
156
-     * @param array          $registration_query_params array of query WHERE params to use
157
-     *                                                  when retrieving cached registrations from a transaction
158
-     * @return    boolean
159
-     * @throws EE_Error
160
-     */
161
-    public function toggle_registration_statuses_if_no_monies_owing(
162
-        EE_Transaction $transaction,
163
-        $registration_query_params = array()
164
-    ) {
165
-        $status_updates = $this->_call_method_on_registrations_via_Registration_Processor(
166
-            'toggle_registration_status_if_no_monies_owing',
167
-            $transaction,
168
-            $registration_query_params
169
-        );
170
-        do_action(
171
-            'AHEE__EE_Transaction_Processor__toggle_registration_statuses_if_no_monies_owing',
172
-            $transaction,
173
-            $status_updates
174
-        );
175
-        return $status_updates;
176
-    }
177
-
178
-
179
-    /**
180
-     * update_transaction_and_registrations_after_checkout_or_payment
181
-     * cycles thru related registrations and calls update_registration_after_checkout_or_payment() on each
182
-     *
183
-     * @param EE_Transaction    $transaction
184
-     * @param EE_Payment | NULL $payment
185
-     * @param array             $registration_query_params    array of query WHERE params to use
186
-     *                                                         when retrieving cached registrations from a transaction
187
-     * @param bool              $trigger_notifications        whether or not to call
188
-     *                                                         \EE_Registration_Processor::trigger_registration_update_notifications()
189
-     * @return array
190
-     * @throws EE_Error
191
-     * @throws ReflectionException
192
-     */
193
-    public function update_transaction_and_registrations_after_checkout_or_payment(
194
-        EE_Transaction $transaction,
195
-        $payment = null,
196
-        $registration_query_params = array(),
197
-        $trigger_notifications = true
198
-    ): array {
199
-        // make sure some query params are set for retrieving registrations
200
-        $this->_set_registration_query_params($registration_query_params);
201
-        // get final reg step status
202
-        $finalized = $transaction->final_reg_step_completed();
203
-        // if the 'finalize_registration' step has been initiated (has a timestamp)
204
-        // but has not yet been fully completed (TRUE)
205
-        if (is_int($finalized) && $finalized !== false && $finalized !== true) {
206
-            $transaction->set_reg_step_completed('finalize_registration');
207
-            $finalized = true;
208
-        }
209
-        $transaction->save();
210
-        // array of details to aid in decision making by systems
211
-        $update_params = array(
212
-            'old_txn_status'  => $transaction->old_txn_status(),
213
-            'new_txn_status'  => $transaction->status_ID(),
214
-            'finalized'       => $finalized,
215
-            'revisit'         => $this->_revisit,
216
-            'payment_updates' => $payment instanceof EE_Payment ? true : false,
217
-            'last_payment'    => $payment,
218
-        );
219
-        // now update the registrations and add the results to our $update_params
220
-        $update_params['status_updates'] = $this->_call_method_on_registrations_via_Registration_Processor(
221
-            'update_registration_after_checkout_or_payment',
222
-            $transaction,
223
-            $this->_registration_query_params,
224
-            $update_params
225
-        );
226
-        if ($trigger_notifications) {
227
-            // send messages
228
-            /** @type EE_Registration_Processor $registration_processor */
229
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
230
-            $registration_processor->trigger_registration_update_notifications(
231
-                $transaction->primary_registration(),
232
-                $update_params
233
-            );
234
-        }
235
-        do_action(
236
-            'AHEE__EE_Transaction_Processor__update_transaction_and_registrations_after_checkout_or_payment',
237
-            $transaction,
238
-            $update_params
239
-        );
240
-        return $update_params;
241
-    }
242
-
243
-
244
-    /**
245
-     * update_transaction_after_registration_reopened
246
-     * readjusts TXN and Line Item totals after a registration is changed from
247
-     * cancelled or declined to another reg status such as pending payment or approved
248
-     *
249
-     * @param EE_Registration $registration
250
-     * @param array           $closed_reg_statuses
251
-     * @param bool            $update_txn
252
-     * @return bool
253
-     * @throws EE_Error
254
-     * @throws ReflectionException
255
-     */
256
-    public function update_transaction_after_reinstating_canceled_registration(
257
-        EE_Registration $registration,
258
-        $closed_reg_statuses = array(),
259
-        $update_txn = true
260
-    ) {
261
-        // these reg statuses should not be considered in any calculations involving monies owing
262
-        $closed_reg_statuses = ! empty($closed_reg_statuses) ? $closed_reg_statuses
263
-            : EEM_Registration::closed_reg_statuses();
264
-        if (in_array($registration->status_ID(), $closed_reg_statuses, true)) {
265
-            return false;
266
-        }
267
-        try {
268
-            $transaction = $this->get_transaction_for_registration($registration);
269
-            $ticket_line_item = $this->get_ticket_line_item_for_transaction_registration(
270
-                $transaction,
271
-                $registration
272
-            );
273
-            // un-cancel the ticket
274
-            $success = EEH_Line_Item::reinstate_canceled_ticket_line_item($ticket_line_item);
275
-        } catch (EE_Error $e) {
276
-            EE_Error::add_error(
277
-                sprintf(
278
-                    __(
279
-                        'The Ticket Line Item for Registration %1$d could not be reinstated because :%2$s%3$s',
280
-                        'event_espresso'
281
-                    ),
282
-                    $registration->ID(),
283
-                    '<br />',
284
-                    $e->getMessage()
285
-                ),
286
-                __FILE__,
287
-                __FUNCTION__,
288
-                __LINE__
289
-            );
290
-            return false;
291
-        }
292
-        if ($update_txn) {
293
-            return $transaction->save() ? $success : false;
294
-        }
295
-        return $success;
296
-    }
297
-
298
-
299
-    /**
300
-     * update_transaction_after_canceled_or_declined_registration
301
-     * readjusts TXN and Line Item totals after a registration is cancelled or declined
302
-     *
303
-     * @param EE_Registration $registration
304
-     * @param array           $closed_reg_statuses
305
-     * @param bool            $update_txn
306
-     * @return bool
307
-     * @throws EE_Error
308
-     * @throws ReflectionException
309
-     */
310
-    public function update_transaction_after_canceled_or_declined_registration(
311
-        EE_Registration $registration,
312
-        $closed_reg_statuses = array(),
313
-        $update_txn = true
314
-    ) {
315
-        // these reg statuses should not be considered in any calculations involving monies owing
316
-        $closed_reg_statuses = ! empty($closed_reg_statuses) ? $closed_reg_statuses
317
-            : EEM_Registration::closed_reg_statuses();
318
-        if (! in_array($registration->status_ID(), $closed_reg_statuses, true)) {
319
-            return false;
320
-        }
321
-        try {
322
-            $transaction = $this->get_transaction_for_registration($registration);
323
-            if (
324
-                apply_filters(
325
-                    'FHEE__EE_Transaction_Processor__update_transaction_after_canceled_or_declined_registration__cancel_ticket_line_item',
326
-                    true,
327
-                    $registration,
328
-                    $transaction
329
-                )
330
-            ) {
331
-                $ticket_line_item = $this->get_ticket_line_item_for_transaction_registration(
332
-                    $transaction,
333
-                    $registration
334
-                );
335
-                EEH_Line_Item::cancel_ticket_line_item($ticket_line_item);
336
-            }
337
-        } catch (EE_Error $e) {
338
-            EE_Error::add_error(
339
-                sprintf(
340
-                    __(
341
-                        'The Ticket Line Item for Registration %1$d could not be cancelled because :%2$s%3$s',
342
-                        'event_espresso'
343
-                    ),
344
-                    $registration->ID(),
345
-                    '<br />',
346
-                    $e->getMessage()
347
-                ),
348
-                __FILE__,
349
-                __FUNCTION__,
350
-                __LINE__
351
-            );
352
-            return false;
353
-        }
354
-        if ($update_txn) {
355
-            return $transaction->save() ? true : false;
356
-        }
357
-        return true;
358
-    }
359
-
360
-
361
-    /**
362
-     * get_transaction_for_registration
363
-     *
364
-     * @access    public
365
-     * @param    EE_Registration $registration
366
-     * @return    EE_Transaction
367
-     * @throws    EE_Error
368
-     */
369
-    public function get_transaction_for_registration(EE_Registration $registration)
370
-    {
371
-        $transaction = $registration->transaction();
372
-        if (! $transaction instanceof EE_Transaction) {
373
-            throw new EE_Error(
374
-                sprintf(
375
-                    __('The Transaction for Registration %1$d was not found or is invalid.', 'event_espresso'),
376
-                    $registration->ID()
377
-                )
378
-            );
379
-        }
380
-        return $transaction;
381
-    }
382
-
383
-
384
-    /**
385
-     * get_ticket_line_item_for_transaction_registration
386
-     *
387
-     * @access    public
388
-     * @param EE_Transaction  $transaction
389
-     * @param EE_Registration $registration
390
-     * @return    EE_Line_Item
391
-     * @throws    EE_Error
392
-     * @throws ReflectionException
393
-     */
394
-    public function get_ticket_line_item_for_transaction_registration(
395
-        EE_Transaction $transaction,
396
-        EE_Registration $registration
397
-    ) {
398
-        EE_Registry::instance()->load_helper('Line_Item');
399
-        $ticket_line_item = EEM_Line_Item::instance()->get_ticket_line_item_for_transaction(
400
-            $transaction->ID(),
401
-            $registration->ticket_ID()
402
-        );
403
-        if (! $ticket_line_item instanceof EE_Line_Item) {
404
-            throw new EE_Error(
405
-                sprintf(
406
-                    __(
407
-                        'The Line Item for Transaction %1$d and Ticket %2$d was not found or is invalid.',
408
-                        'event_espresso'
409
-                    ),
410
-                    $transaction->ID(),
411
-                    $registration->ticket_ID()
412
-                )
413
-            );
414
-        }
415
-        return $ticket_line_item;
416
-    }
417
-
418
-
419
-    /**
420
-     * cancel_transaction_if_all_registrations_canceled
421
-     * cycles thru related registrations and checks their statuses
422
-     * if ALL registrations are Cancelled or Declined, then this sets the TXN status to
423
-     *
424
-     * @access    public
425
-     * @param    EE_Transaction $transaction
426
-     * @param    string         $new_TXN_status
427
-     * @param    array          $registration_query_params - array of query WHERE params to use when
428
-     *                                                     retrieving cached registrations from a transaction
429
-     * @param    array          $closed_reg_statuses
430
-     * @param    bool           $update_txn
431
-     * @return    bool            true if TXN status was updated, false if not
432
-     */
433
-    public function toggle_transaction_status_if_all_registrations_canceled_or_declined(
434
-        EE_Transaction $transaction,
435
-        $new_TXN_status = '',
436
-        $registration_query_params = array(),
437
-        $closed_reg_statuses = array(),
438
-        $update_txn = true
439
-    ) {
440
-        // make sure some query params are set for retrieving registrations
441
-        $this->_set_registration_query_params($registration_query_params);
442
-        // these reg statuses should not be considered in any calculations involving monies owing
443
-        $closed_reg_statuses = ! empty($closed_reg_statuses) ? $closed_reg_statuses
444
-            : EEM_Registration::closed_reg_statuses();
445
-        // loop through cached registrations
446
-        foreach ($transaction->registrations($this->_registration_query_params) as $registration) {
447
-            if (
448
-                $registration instanceof EE_Registration
449
-                && ! in_array($registration->status_ID(), $closed_reg_statuses)
450
-            ) {
451
-                return false;
452
-            }
453
-        }
454
-        if (in_array($new_TXN_status, EEM_Transaction::txn_status_array())) {
455
-            $transaction->set_status($new_TXN_status);
456
-        }
457
-        if ($update_txn) {
458
-            return $transaction->save() ? true : false;
459
-        }
460
-        return true;
461
-    }
462
-
463
-
464
-    /**
465
-     * _call_method_on_registrations_via_Registration_Processor
466
-     * cycles thru related registrations and calls the requested method on each
467
-     *
468
-     * @access private
469
-     * @param string         $method_name
470
-     * @param EE_Transaction $transaction
471
-     * @param array          $registration_query_params array of query WHERE params to use
472
-     *                                                  when retrieving cached registrations from a transaction
473
-     * @param string         $additional_param
474
-     * @return boolean
475
-     * @throws ReflectionException
476
-     * @throws EE_Error
477
-     */
478
-    private function _call_method_on_registrations_via_Registration_Processor(
479
-        $method_name,
480
-        EE_Transaction $transaction,
481
-        $registration_query_params = array(),
482
-        $additional_param = null
483
-    ) {
484
-        $response = false;
485
-        /** @type EE_Registration_Processor $registration_processor */
486
-        $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
487
-        // check that method exists
488
-        if (! method_exists($registration_processor, $method_name)) {
489
-            throw new EE_Error(__('Method does not exist.', 'event_espresso'));
490
-        }
491
-        // make sure some query params are set for retrieving registrations
492
-        $this->_set_registration_query_params($registration_query_params);
493
-        // loop through cached registrations
494
-        foreach ($transaction->registrations($this->_registration_query_params) as $registration) {
495
-            if ($registration instanceof EE_Registration) {
496
-                if ($additional_param) {
497
-                    $response = $registration_processor->{$method_name}($registration, $additional_param)
498
-                        ? true
499
-                        : $response;
500
-                } else {
501
-                    $response = $registration_processor->{$method_name}($registration)
502
-                        ? true
503
-                        : $response;
504
-                }
505
-            }
506
-        }
507
-        return $response;
508
-    }
509
-
510
-
511
-    /**
512
-     * set_transaction_payment_method_based_on_registration_statuses
513
-     * sets or unsets the PMD_ID field on the TXN based on the related REG statuses
514
-     * basically if ALL Registrations are "Not Approved", then the EE_Transaction.PMD_ID is set to null,
515
-     * but if any Registration has a different status, then EE_Transaction.PMD_ID is set to either:
516
-     *        the first "default" Payment Method
517
-     *        the first active Payment Method
518
-     *    whichever is found first.
519
-     *
520
-     * @param EE_Registration $edited_registration
521
-     * @return void
522
-     * @throws EE_Error
523
-     * @throws ReflectionException
524
-     */
525
-    public function set_transaction_payment_method_based_on_registration_statuses(
526
-        EE_Registration $edited_registration
527
-    ) {
528
-        if ($edited_registration instanceof EE_Registration) {
529
-            $transaction = $edited_registration->transaction();
530
-            if ($transaction instanceof EE_Transaction) {
531
-                $all_not_approved = true;
532
-                foreach ($transaction->registrations() as $registration) {
533
-                    if ($registration instanceof EE_Registration) {
534
-                        // if any REG != "Not Approved" then toggle to false
535
-                        $all_not_approved = $registration->is_not_approved() ? $all_not_approved : false;
536
-                    }
537
-                }
538
-                // if ALL Registrations are "Not Approved"
539
-                if ($all_not_approved) {
540
-                    $transaction->set_payment_method_ID(null);
541
-                    $transaction->save();
542
-                } else {
543
-                    $available_payment_methods = EEM_Payment_Method::instance()->get_all_for_transaction(
544
-                        $transaction,
545
-                        EEM_Payment_Method::scope_cart
546
-                    );
547
-                    if (! empty($available_payment_methods)) {
548
-                        $PMD_ID = 0;
549
-                        foreach ($available_payment_methods as $available_payment_method) {
550
-                            if (
551
-                                $available_payment_method instanceof EE_Payment_Method
552
-                                && $available_payment_method->open_by_default()
553
-                            ) {
554
-                                $PMD_ID = $available_payment_method->ID();
555
-                                break;
556
-                            }
557
-                        }
558
-                        if (! $PMD_ID) {
559
-                            $first_payment_method = reset($available_payment_methods);
560
-                            if ($first_payment_method instanceof EE_Payment_Method) {
561
-                                $PMD_ID = $first_payment_method->ID();
562
-                            } else {
563
-                                EE_Error::add_error(
564
-                                    __(
565
-                                        'A valid Payment Method could not be determined. Please ensure that at least one Payment Method is activated.',
566
-                                        'event_espresso'
567
-                                    ),
568
-                                    __FILE__,
569
-                                    __LINE__,
570
-                                    __FUNCTION__
571
-                                );
572
-                            }
573
-                        }
574
-                        $transaction->set_payment_method_ID($PMD_ID);
575
-                        $transaction->save();
576
-                    } else {
577
-                        EE_Error::add_error(
578
-                            __(
579
-                                'Please activate at least one Payment Method in order for things to operate correctly.',
580
-                                'event_espresso'
581
-                            ),
582
-                            __FILE__,
583
-                            __LINE__,
584
-                            __FUNCTION__
585
-                        );
586
-                    }
587
-                }
588
-            }
589
-        }
590
-    }
591
-
592
-
593
-
594
-    /********************************** DEPRECATED METHODS **********************************/
595
-
596
-
597
-    /**
598
-     * @deprecated 4.9.12
599
-     * @return string
600
-     */
601
-    public function old_txn_status()
602
-    {
603
-        EE_Error::doing_it_wrong(
604
-            __METHOD__,
605
-            esc_html__(
606
-                'This logic has been moved into \EE_Transaction::old_txn_status(), please use that method instead.',
607
-                'event_espresso'
608
-            ),
609
-            '4.9.12'
610
-        );
611
-        return $this->_old_txn_status;
612
-    }
613
-
614
-
615
-    /**
616
-     * @deprecated 4.9.12
617
-     * @param string $old_txn_status
618
-     */
619
-    public function set_old_txn_status($old_txn_status)
620
-    {
621
-        EE_Error::doing_it_wrong(
622
-            __METHOD__,
623
-            esc_html__(
624
-                'This logic has been moved into \EE_Transaction::set_old_txn_status(), please use that method instead.',
625
-                'event_espresso'
626
-            ),
627
-            '4.9.12'
628
-        );
629
-        // only set the first time
630
-        if ($this->_old_txn_status === null) {
631
-            $this->_old_txn_status = $old_txn_status;
632
-        }
633
-    }
634
-
635
-
636
-    /**
637
-     * @deprecated 4.9.12
638
-     * @return string
639
-     */
640
-    public function new_txn_status()
641
-    {
642
-        EE_Error::doing_it_wrong(
643
-            __METHOD__,
644
-            esc_html__(
645
-                'This logic has been removed. Please just use \EE_Transaction::status_ID() instead.',
646
-                'event_espresso'
647
-            ),
648
-            '4.9.12'
649
-        );
650
-        return $this->_new_txn_status;
651
-    }
652
-
653
-
654
-    /**
655
-     * @deprecated 4.9.12
656
-     * @param string $new_txn_status
657
-     */
658
-    public function set_new_txn_status($new_txn_status)
659
-    {
660
-        EE_Error::doing_it_wrong(
661
-            __METHOD__,
662
-            esc_html__(
663
-                'This logic has been removed. Please just use \EE_Transaction::set_status() instead.',
664
-                'event_espresso'
665
-            ),
666
-            '4.9.12'
667
-        );
668
-        $this->_new_txn_status = $new_txn_status;
669
-    }
670
-
671
-
672
-    /**
673
-     * reg_status_updated
674
-     *
675
-     * @deprecated 4.9.12
676
-     * @return bool
677
-     */
678
-    public function txn_status_updated()
679
-    {
680
-        EE_Error::doing_it_wrong(
681
-            __METHOD__,
682
-            esc_html__(
683
-                'This logic has been moved into \EE_Transaction::txn_status_updated(), please use that method instead.',
684
-                'event_espresso'
685
-            ),
686
-            '4.9.12'
687
-        );
688
-        return $this->_new_txn_status !== $this->_old_txn_status && $this->_old_txn_status !== null ? true : false;
689
-    }
690
-
691
-
692
-    /**
693
-     * all_reg_steps_completed
694
-     * returns:
695
-     *    true if ALL reg steps have been marked as completed
696
-     *        or false if any step is not completed
697
-     *
698
-     * @deprecated 4.9.12
699
-     * @param EE_Transaction $transaction
700
-     * @return boolean
701
-     */
702
-    public function all_reg_steps_completed(EE_Transaction $transaction)
703
-    {
704
-        EE_Error::doing_it_wrong(
705
-            __METHOD__,
706
-            esc_html__(
707
-                'This logic has been moved into \EE_Transaction::all_reg_steps_completed(), please use that method instead.',
708
-                'event_espresso'
709
-            ),
710
-            '4.9.12',
711
-            '5.0.0'
712
-        );
713
-        return $transaction->all_reg_steps_completed();
714
-    }
715
-
716
-
717
-    /**
718
-     * all_reg_steps_completed_except
719
-     * returns:
720
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
721
-     *        or false if any other step is not completed
722
-     *        or false if ALL steps are completed including the exception you are testing !!!
723
-     *
724
-     * @deprecated 4.9.12
725
-     * @param EE_Transaction $transaction
726
-     * @param string         $exception
727
-     * @return boolean
728
-     */
729
-    public function all_reg_steps_completed_except(EE_Transaction $transaction, $exception = '')
730
-    {
731
-        EE_Error::doing_it_wrong(
732
-            __METHOD__,
733
-            esc_html__(
734
-                'This logic has been moved into \EE_Transaction::all_reg_steps_completed_except(), please use that method instead.',
735
-                'event_espresso'
736
-            ),
737
-            '4.9.12',
738
-            '5.0.0'
739
-        );
740
-        return $transaction->all_reg_steps_completed_except($exception);
741
-    }
742
-
743
-
744
-    /**
745
-     * all_reg_steps_completed_except
746
-     * returns:
747
-     *        true if ALL reg steps, except the final step, have been marked as completed
748
-     *        or false if any step is not completed
749
-     *    or false if ALL steps are completed including the final step !!!
750
-     *
751
-     * @deprecated 4.9.12
752
-     * @param EE_Transaction $transaction
753
-     * @return boolean
754
-     */
755
-    public function all_reg_steps_completed_except_final_step(EE_Transaction $transaction)
756
-    {
757
-        EE_Error::doing_it_wrong(
758
-            __METHOD__,
759
-            esc_html__(
760
-                'This logic has been moved into \EE_Transaction::all_reg_steps_completed_except_final_step(), please use that method instead.',
761
-                'event_espresso'
762
-            ),
763
-            '4.9.12',
764
-            '5.0.0'
765
-        );
766
-        return $transaction->all_reg_steps_completed_except_final_step();
767
-    }
768
-
769
-
770
-    /**
771
-     * reg_step_completed
772
-     * returns:
773
-     *    true if a specific reg step has been marked as completed
774
-     *    a Unix timestamp if it has been initialized but not yet completed,
775
-     *    or false if it has not yet been initialized
776
-     *
777
-     * @deprecated 4.9.12
778
-     * @param EE_Transaction $transaction
779
-     * @param string         $reg_step_slug
780
-     * @return boolean | int
781
-     */
782
-    public function reg_step_completed(EE_Transaction $transaction, $reg_step_slug)
783
-    {
784
-        EE_Error::doing_it_wrong(
785
-            __METHOD__,
786
-            esc_html__(
787
-                'This logic has been moved into \EE_Transaction::reg_step_completed(), please use that method instead.',
788
-                'event_espresso'
789
-            ),
790
-            '4.9.12',
791
-            '5.0.0'
792
-        );
793
-        return $transaction->reg_step_completed($reg_step_slug);
794
-    }
795
-
796
-
797
-    /**
798
-     * completed_final_reg_step
799
-     * returns:
800
-     *    true if the finalize_registration reg step has been marked as completed
801
-     *    a Unix timestamp if it has been initialized but not yet completed,
802
-     *    or false if it has not yet been initialized
803
-     *
804
-     * @deprecated 4.9.12
805
-     * @param EE_Transaction $transaction
806
-     * @return boolean | int
807
-     */
808
-    public function final_reg_step_completed(EE_Transaction $transaction)
809
-    {
810
-        EE_Error::doing_it_wrong(
811
-            __METHOD__,
812
-            esc_html__(
813
-                'This logic has been moved into \EE_Transaction::final_reg_step_completed(), please use that method instead.',
814
-                'event_espresso'
815
-            ),
816
-            '4.9.12',
817
-            '5.0.0'
818
-        );
819
-        return $transaction->final_reg_step_completed();
820
-    }
821
-
822
-
823
-    /**
824
-     * set_reg_step_initiated
825
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
826
-     *
827
-     * @param EE_Transaction $transaction
828
-     * @param string         $reg_step_slug
829
-     * @return boolean
830
-     * @throws EE_Error*@throws ReflectionException
831
-     * @deprecated 4.9.12
832
-     * @access     public
833
-     */
834
-    public function set_reg_step_initiated(EE_Transaction $transaction, $reg_step_slug)
835
-    {
836
-        EE_Error::doing_it_wrong(
837
-            __METHOD__,
838
-            esc_html__(
839
-                'This logic has been moved into \EE_Transaction::set_reg_step_initiated(), please use that method instead.',
840
-                'event_espresso'
841
-            ),
842
-            '4.9.12',
843
-            '5.0.0'
844
-        );
845
-        return $transaction->set_reg_step_initiated($reg_step_slug);
846
-    }
847
-
848
-
849
-    /**
850
-     * set_reg_step_completed
851
-     * given a valid TXN_reg_step, this sets the step as completed
852
-     *
853
-     * @param EE_Transaction $transaction
854
-     * @param string         $reg_step_slug
855
-     * @return boolean
856
-     * @throws EE_Error*@throws ReflectionException
857
-     * @deprecated 4.9.12
858
-     * @access     public
859
-     */
860
-    public function set_reg_step_completed(EE_Transaction $transaction, $reg_step_slug)
861
-    {
862
-        EE_Error::doing_it_wrong(
863
-            __METHOD__,
864
-            esc_html__(
865
-                'This logic has been moved into \EE_Transaction::set_reg_step_completed(), please use that method instead.',
866
-                'event_espresso'
867
-            ),
868
-            '4.9.12',
869
-            '5.0.0'
870
-        );
871
-        return $transaction->set_reg_step_completed($reg_step_slug);
872
-    }
873
-
874
-
875
-    /**
876
-     * set_reg_step_completed
877
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
878
-     *
879
-     * @param EE_Transaction $transaction
880
-     * @param string         $reg_step_slug
881
-     * @return boolean
882
-     * @throws EE_Error*@throws ReflectionException
883
-     * @deprecated 4.9.12
884
-     * @access     public
885
-     */
886
-    public function set_reg_step_not_completed(EE_Transaction $transaction, $reg_step_slug)
887
-    {
888
-        EE_Error::doing_it_wrong(
889
-            __METHOD__,
890
-            esc_html__(
891
-                'This logic has been moved into \EE_Transaction::set_reg_step_not_completed(), please use that method instead.',
892
-                'event_espresso'
893
-            ),
894
-            '4.9.12',
895
-            '5.0.0'
896
-        );
897
-        return $transaction->set_reg_step_not_completed($reg_step_slug);
898
-    }
899
-
900
-
901
-    /**
902
-     * remove_reg_step
903
-     * given a valid TXN_reg_step slug, this will remove (unset)
904
-     * the reg step from the TXN reg step array
905
-     *
906
-     * @param EE_Transaction $transaction
907
-     * @param string         $reg_step_slug
908
-     * @return void
909
-     *@deprecated 4.9.12
910
-     * @access     public
911
-     */
912
-    public function remove_reg_step(EE_Transaction $transaction, $reg_step_slug)
913
-    {
914
-        EE_Error::doing_it_wrong(
915
-            __METHOD__,
916
-            esc_html__(
917
-                'This logic has been moved into \EE_Transaction::remove_reg_step(), please use that method instead.',
918
-                'event_espresso'
919
-            ),
920
-            '4.9.12',
921
-            '5.0.0'
922
-        );
923
-        $transaction->remove_reg_step($reg_step_slug);
924
-    }
925
-
926
-
927
-    /**
928
-     *    toggle_failed_transaction_status
929
-     * upgrades a TXNs status from failed to abandoned,
930
-     * meaning that contact information has been captured for at least one registrant
931
-     *
932
-     * @param EE_Transaction $transaction
933
-     * @return    boolean
934
-     * @throws EE_Error*@throws ReflectionException
935
-     * @deprecated 4.9.12
936
-     * @access     public
937
-     */
938
-    public function toggle_failed_transaction_status(EE_Transaction $transaction)
939
-    {
940
-        EE_Error::doing_it_wrong(
941
-            __METHOD__,
942
-            esc_html__(
943
-                'This logic has been moved into \EE_Transaction::toggle_failed_transaction_status(), please use that method instead.',
944
-                'event_espresso'
945
-            ),
946
-            '4.9.12',
947
-            '5.0.0'
948
-        );
949
-        return $transaction->toggle_failed_transaction_status();
950
-    }
951
-
952
-
953
-    /**
954
-     * toggle_abandoned_transaction_status
955
-     * upgrades a TXNs status from failed or abandoned to incomplete
956
-     *
957
-     * @deprecated 4.9.12
958
-     * @access     public
959
-     * @param  EE_Transaction $transaction
960
-     * @return boolean
961
-     */
962
-    public function toggle_abandoned_transaction_status(EE_Transaction $transaction)
963
-    {
964
-        EE_Error::doing_it_wrong(
965
-            __METHOD__,
966
-            esc_html__(
967
-                'This logic has been moved into \EE_Transaction::toggle_abandoned_transaction_status(), please use that method instead.',
968
-                'event_espresso'
969
-            ),
970
-            '4.9.12',
971
-            '5.0.0'
972
-        );
973
-        return $transaction->toggle_abandoned_transaction_status();
974
-    }
20
+	/**
21
+	 * @var EE_Registration_Processor $_instance
22
+	 * @access    private
23
+	 */
24
+	private static $_instance;
25
+
26
+	/**
27
+	 * array of query WHERE params to use when retrieving cached registrations from a transaction
28
+	 *
29
+	 * @var array $registration_query_params
30
+	 * @access private
31
+	 */
32
+	private $_registration_query_params = array();
33
+
34
+	/**
35
+	 * @deprecated
36
+	 * @var string
37
+	 */
38
+	protected $_old_txn_status;
39
+
40
+	/**
41
+	 * @deprecated
42
+	 * @var string
43
+	 */
44
+	protected $_new_txn_status;
45
+
46
+
47
+	/**
48
+	 * @singleton method used to instantiate class object
49
+	 * @access    public
50
+	 * @param array $registration_query_params
51
+	 * @return EE_Transaction_Processor instance
52
+	 */
53
+	public static function instance($registration_query_params = array())
54
+	{
55
+		// check if class object is instantiated
56
+		if (! self::$_instance instanceof EE_Transaction_Processor) {
57
+			self::$_instance = new self($registration_query_params);
58
+		}
59
+		return self::$_instance;
60
+	}
61
+
62
+
63
+	/**
64
+	 * @param array $registration_query_params
65
+	 */
66
+	private function __construct($registration_query_params = array())
67
+	{
68
+		// make sure some query params are set for retrieving registrations
69
+		$this->_set_registration_query_params($registration_query_params);
70
+	}
71
+
72
+
73
+	/**
74
+	 * @access private
75
+	 * @param array $registration_query_params
76
+	 */
77
+	private function _set_registration_query_params($registration_query_params)
78
+	{
79
+		$this->_registration_query_params = ! empty($registration_query_params) ? $registration_query_params
80
+			: array('order_by' => array('REG_count' => 'ASC'));
81
+	}
82
+
83
+
84
+	/**
85
+	 * manually_update_registration_statuses
86
+	 *
87
+	 * @access public
88
+	 * @param EE_Transaction $transaction
89
+	 * @param string         $new_reg_status
90
+	 * @param array          $registration_query_params array of query WHERE params to use
91
+	 *                                                  when retrieving cached registrations from a transaction
92
+	 * @return    boolean
93
+	 * @throws EE_Error
94
+	 * @throws ReflectionException
95
+	 */
96
+	public function manually_update_registration_statuses(
97
+		EE_Transaction $transaction,
98
+		$new_reg_status = '',
99
+		$registration_query_params = array()
100
+	) {
101
+		$status_updates = $this->_call_method_on_registrations_via_Registration_Processor(
102
+			'manually_update_registration_status',
103
+			$transaction,
104
+			$registration_query_params,
105
+			$new_reg_status
106
+		);
107
+		// send messages
108
+		/** @type EE_Registration_Processor $registration_processor */
109
+		$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
110
+		$registration_processor->trigger_registration_update_notifications(
111
+			$transaction->primary_registration(),
112
+			array('manually_updated' => true)
113
+		);
114
+		do_action(
115
+			'AHEE__EE_Transaction_Processor__manually_update_registration_statuses',
116
+			$transaction,
117
+			$status_updates
118
+		);
119
+		return $status_updates;
120
+	}
121
+
122
+
123
+	/**
124
+	 * toggle_registration_statuses_for_default_approved_events
125
+	 *
126
+	 * @access public
127
+	 * @param EE_Transaction $transaction
128
+	 * @param array          $registration_query_params array of query WHERE params to use
129
+	 *                                                  when retrieving cached registrations from a transaction
130
+	 * @return    boolean
131
+	 * @throws EE_Error
132
+	 */
133
+	public function toggle_registration_statuses_for_default_approved_events(
134
+		EE_Transaction $transaction,
135
+		$registration_query_params = array()
136
+	) {
137
+		$status_updates = $this->_call_method_on_registrations_via_Registration_Processor(
138
+			'toggle_registration_status_for_default_approved_events',
139
+			$transaction,
140
+			$registration_query_params
141
+		);
142
+		do_action(
143
+			'AHEE__EE_Transaction_Processor__toggle_registration_statuses_for_default_approved_events',
144
+			$transaction,
145
+			$status_updates
146
+		);
147
+		return $status_updates;
148
+	}
149
+
150
+
151
+	/**
152
+	 * toggle_registration_statuses_if_no_monies_owing
153
+	 *
154
+	 * @access public
155
+	 * @param EE_Transaction $transaction
156
+	 * @param array          $registration_query_params array of query WHERE params to use
157
+	 *                                                  when retrieving cached registrations from a transaction
158
+	 * @return    boolean
159
+	 * @throws EE_Error
160
+	 */
161
+	public function toggle_registration_statuses_if_no_monies_owing(
162
+		EE_Transaction $transaction,
163
+		$registration_query_params = array()
164
+	) {
165
+		$status_updates = $this->_call_method_on_registrations_via_Registration_Processor(
166
+			'toggle_registration_status_if_no_monies_owing',
167
+			$transaction,
168
+			$registration_query_params
169
+		);
170
+		do_action(
171
+			'AHEE__EE_Transaction_Processor__toggle_registration_statuses_if_no_monies_owing',
172
+			$transaction,
173
+			$status_updates
174
+		);
175
+		return $status_updates;
176
+	}
177
+
178
+
179
+	/**
180
+	 * update_transaction_and_registrations_after_checkout_or_payment
181
+	 * cycles thru related registrations and calls update_registration_after_checkout_or_payment() on each
182
+	 *
183
+	 * @param EE_Transaction    $transaction
184
+	 * @param EE_Payment | NULL $payment
185
+	 * @param array             $registration_query_params    array of query WHERE params to use
186
+	 *                                                         when retrieving cached registrations from a transaction
187
+	 * @param bool              $trigger_notifications        whether or not to call
188
+	 *                                                         \EE_Registration_Processor::trigger_registration_update_notifications()
189
+	 * @return array
190
+	 * @throws EE_Error
191
+	 * @throws ReflectionException
192
+	 */
193
+	public function update_transaction_and_registrations_after_checkout_or_payment(
194
+		EE_Transaction $transaction,
195
+		$payment = null,
196
+		$registration_query_params = array(),
197
+		$trigger_notifications = true
198
+	): array {
199
+		// make sure some query params are set for retrieving registrations
200
+		$this->_set_registration_query_params($registration_query_params);
201
+		// get final reg step status
202
+		$finalized = $transaction->final_reg_step_completed();
203
+		// if the 'finalize_registration' step has been initiated (has a timestamp)
204
+		// but has not yet been fully completed (TRUE)
205
+		if (is_int($finalized) && $finalized !== false && $finalized !== true) {
206
+			$transaction->set_reg_step_completed('finalize_registration');
207
+			$finalized = true;
208
+		}
209
+		$transaction->save();
210
+		// array of details to aid in decision making by systems
211
+		$update_params = array(
212
+			'old_txn_status'  => $transaction->old_txn_status(),
213
+			'new_txn_status'  => $transaction->status_ID(),
214
+			'finalized'       => $finalized,
215
+			'revisit'         => $this->_revisit,
216
+			'payment_updates' => $payment instanceof EE_Payment ? true : false,
217
+			'last_payment'    => $payment,
218
+		);
219
+		// now update the registrations and add the results to our $update_params
220
+		$update_params['status_updates'] = $this->_call_method_on_registrations_via_Registration_Processor(
221
+			'update_registration_after_checkout_or_payment',
222
+			$transaction,
223
+			$this->_registration_query_params,
224
+			$update_params
225
+		);
226
+		if ($trigger_notifications) {
227
+			// send messages
228
+			/** @type EE_Registration_Processor $registration_processor */
229
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
230
+			$registration_processor->trigger_registration_update_notifications(
231
+				$transaction->primary_registration(),
232
+				$update_params
233
+			);
234
+		}
235
+		do_action(
236
+			'AHEE__EE_Transaction_Processor__update_transaction_and_registrations_after_checkout_or_payment',
237
+			$transaction,
238
+			$update_params
239
+		);
240
+		return $update_params;
241
+	}
242
+
243
+
244
+	/**
245
+	 * update_transaction_after_registration_reopened
246
+	 * readjusts TXN and Line Item totals after a registration is changed from
247
+	 * cancelled or declined to another reg status such as pending payment or approved
248
+	 *
249
+	 * @param EE_Registration $registration
250
+	 * @param array           $closed_reg_statuses
251
+	 * @param bool            $update_txn
252
+	 * @return bool
253
+	 * @throws EE_Error
254
+	 * @throws ReflectionException
255
+	 */
256
+	public function update_transaction_after_reinstating_canceled_registration(
257
+		EE_Registration $registration,
258
+		$closed_reg_statuses = array(),
259
+		$update_txn = true
260
+	) {
261
+		// these reg statuses should not be considered in any calculations involving monies owing
262
+		$closed_reg_statuses = ! empty($closed_reg_statuses) ? $closed_reg_statuses
263
+			: EEM_Registration::closed_reg_statuses();
264
+		if (in_array($registration->status_ID(), $closed_reg_statuses, true)) {
265
+			return false;
266
+		}
267
+		try {
268
+			$transaction = $this->get_transaction_for_registration($registration);
269
+			$ticket_line_item = $this->get_ticket_line_item_for_transaction_registration(
270
+				$transaction,
271
+				$registration
272
+			);
273
+			// un-cancel the ticket
274
+			$success = EEH_Line_Item::reinstate_canceled_ticket_line_item($ticket_line_item);
275
+		} catch (EE_Error $e) {
276
+			EE_Error::add_error(
277
+				sprintf(
278
+					__(
279
+						'The Ticket Line Item for Registration %1$d could not be reinstated because :%2$s%3$s',
280
+						'event_espresso'
281
+					),
282
+					$registration->ID(),
283
+					'<br />',
284
+					$e->getMessage()
285
+				),
286
+				__FILE__,
287
+				__FUNCTION__,
288
+				__LINE__
289
+			);
290
+			return false;
291
+		}
292
+		if ($update_txn) {
293
+			return $transaction->save() ? $success : false;
294
+		}
295
+		return $success;
296
+	}
297
+
298
+
299
+	/**
300
+	 * update_transaction_after_canceled_or_declined_registration
301
+	 * readjusts TXN and Line Item totals after a registration is cancelled or declined
302
+	 *
303
+	 * @param EE_Registration $registration
304
+	 * @param array           $closed_reg_statuses
305
+	 * @param bool            $update_txn
306
+	 * @return bool
307
+	 * @throws EE_Error
308
+	 * @throws ReflectionException
309
+	 */
310
+	public function update_transaction_after_canceled_or_declined_registration(
311
+		EE_Registration $registration,
312
+		$closed_reg_statuses = array(),
313
+		$update_txn = true
314
+	) {
315
+		// these reg statuses should not be considered in any calculations involving monies owing
316
+		$closed_reg_statuses = ! empty($closed_reg_statuses) ? $closed_reg_statuses
317
+			: EEM_Registration::closed_reg_statuses();
318
+		if (! in_array($registration->status_ID(), $closed_reg_statuses, true)) {
319
+			return false;
320
+		}
321
+		try {
322
+			$transaction = $this->get_transaction_for_registration($registration);
323
+			if (
324
+				apply_filters(
325
+					'FHEE__EE_Transaction_Processor__update_transaction_after_canceled_or_declined_registration__cancel_ticket_line_item',
326
+					true,
327
+					$registration,
328
+					$transaction
329
+				)
330
+			) {
331
+				$ticket_line_item = $this->get_ticket_line_item_for_transaction_registration(
332
+					$transaction,
333
+					$registration
334
+				);
335
+				EEH_Line_Item::cancel_ticket_line_item($ticket_line_item);
336
+			}
337
+		} catch (EE_Error $e) {
338
+			EE_Error::add_error(
339
+				sprintf(
340
+					__(
341
+						'The Ticket Line Item for Registration %1$d could not be cancelled because :%2$s%3$s',
342
+						'event_espresso'
343
+					),
344
+					$registration->ID(),
345
+					'<br />',
346
+					$e->getMessage()
347
+				),
348
+				__FILE__,
349
+				__FUNCTION__,
350
+				__LINE__
351
+			);
352
+			return false;
353
+		}
354
+		if ($update_txn) {
355
+			return $transaction->save() ? true : false;
356
+		}
357
+		return true;
358
+	}
359
+
360
+
361
+	/**
362
+	 * get_transaction_for_registration
363
+	 *
364
+	 * @access    public
365
+	 * @param    EE_Registration $registration
366
+	 * @return    EE_Transaction
367
+	 * @throws    EE_Error
368
+	 */
369
+	public function get_transaction_for_registration(EE_Registration $registration)
370
+	{
371
+		$transaction = $registration->transaction();
372
+		if (! $transaction instanceof EE_Transaction) {
373
+			throw new EE_Error(
374
+				sprintf(
375
+					__('The Transaction for Registration %1$d was not found or is invalid.', 'event_espresso'),
376
+					$registration->ID()
377
+				)
378
+			);
379
+		}
380
+		return $transaction;
381
+	}
382
+
383
+
384
+	/**
385
+	 * get_ticket_line_item_for_transaction_registration
386
+	 *
387
+	 * @access    public
388
+	 * @param EE_Transaction  $transaction
389
+	 * @param EE_Registration $registration
390
+	 * @return    EE_Line_Item
391
+	 * @throws    EE_Error
392
+	 * @throws ReflectionException
393
+	 */
394
+	public function get_ticket_line_item_for_transaction_registration(
395
+		EE_Transaction $transaction,
396
+		EE_Registration $registration
397
+	) {
398
+		EE_Registry::instance()->load_helper('Line_Item');
399
+		$ticket_line_item = EEM_Line_Item::instance()->get_ticket_line_item_for_transaction(
400
+			$transaction->ID(),
401
+			$registration->ticket_ID()
402
+		);
403
+		if (! $ticket_line_item instanceof EE_Line_Item) {
404
+			throw new EE_Error(
405
+				sprintf(
406
+					__(
407
+						'The Line Item for Transaction %1$d and Ticket %2$d was not found or is invalid.',
408
+						'event_espresso'
409
+					),
410
+					$transaction->ID(),
411
+					$registration->ticket_ID()
412
+				)
413
+			);
414
+		}
415
+		return $ticket_line_item;
416
+	}
417
+
418
+
419
+	/**
420
+	 * cancel_transaction_if_all_registrations_canceled
421
+	 * cycles thru related registrations and checks their statuses
422
+	 * if ALL registrations are Cancelled or Declined, then this sets the TXN status to
423
+	 *
424
+	 * @access    public
425
+	 * @param    EE_Transaction $transaction
426
+	 * @param    string         $new_TXN_status
427
+	 * @param    array          $registration_query_params - array of query WHERE params to use when
428
+	 *                                                     retrieving cached registrations from a transaction
429
+	 * @param    array          $closed_reg_statuses
430
+	 * @param    bool           $update_txn
431
+	 * @return    bool            true if TXN status was updated, false if not
432
+	 */
433
+	public function toggle_transaction_status_if_all_registrations_canceled_or_declined(
434
+		EE_Transaction $transaction,
435
+		$new_TXN_status = '',
436
+		$registration_query_params = array(),
437
+		$closed_reg_statuses = array(),
438
+		$update_txn = true
439
+	) {
440
+		// make sure some query params are set for retrieving registrations
441
+		$this->_set_registration_query_params($registration_query_params);
442
+		// these reg statuses should not be considered in any calculations involving monies owing
443
+		$closed_reg_statuses = ! empty($closed_reg_statuses) ? $closed_reg_statuses
444
+			: EEM_Registration::closed_reg_statuses();
445
+		// loop through cached registrations
446
+		foreach ($transaction->registrations($this->_registration_query_params) as $registration) {
447
+			if (
448
+				$registration instanceof EE_Registration
449
+				&& ! in_array($registration->status_ID(), $closed_reg_statuses)
450
+			) {
451
+				return false;
452
+			}
453
+		}
454
+		if (in_array($new_TXN_status, EEM_Transaction::txn_status_array())) {
455
+			$transaction->set_status($new_TXN_status);
456
+		}
457
+		if ($update_txn) {
458
+			return $transaction->save() ? true : false;
459
+		}
460
+		return true;
461
+	}
462
+
463
+
464
+	/**
465
+	 * _call_method_on_registrations_via_Registration_Processor
466
+	 * cycles thru related registrations and calls the requested method on each
467
+	 *
468
+	 * @access private
469
+	 * @param string         $method_name
470
+	 * @param EE_Transaction $transaction
471
+	 * @param array          $registration_query_params array of query WHERE params to use
472
+	 *                                                  when retrieving cached registrations from a transaction
473
+	 * @param string         $additional_param
474
+	 * @return boolean
475
+	 * @throws ReflectionException
476
+	 * @throws EE_Error
477
+	 */
478
+	private function _call_method_on_registrations_via_Registration_Processor(
479
+		$method_name,
480
+		EE_Transaction $transaction,
481
+		$registration_query_params = array(),
482
+		$additional_param = null
483
+	) {
484
+		$response = false;
485
+		/** @type EE_Registration_Processor $registration_processor */
486
+		$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
487
+		// check that method exists
488
+		if (! method_exists($registration_processor, $method_name)) {
489
+			throw new EE_Error(__('Method does not exist.', 'event_espresso'));
490
+		}
491
+		// make sure some query params are set for retrieving registrations
492
+		$this->_set_registration_query_params($registration_query_params);
493
+		// loop through cached registrations
494
+		foreach ($transaction->registrations($this->_registration_query_params) as $registration) {
495
+			if ($registration instanceof EE_Registration) {
496
+				if ($additional_param) {
497
+					$response = $registration_processor->{$method_name}($registration, $additional_param)
498
+						? true
499
+						: $response;
500
+				} else {
501
+					$response = $registration_processor->{$method_name}($registration)
502
+						? true
503
+						: $response;
504
+				}
505
+			}
506
+		}
507
+		return $response;
508
+	}
509
+
510
+
511
+	/**
512
+	 * set_transaction_payment_method_based_on_registration_statuses
513
+	 * sets or unsets the PMD_ID field on the TXN based on the related REG statuses
514
+	 * basically if ALL Registrations are "Not Approved", then the EE_Transaction.PMD_ID is set to null,
515
+	 * but if any Registration has a different status, then EE_Transaction.PMD_ID is set to either:
516
+	 *        the first "default" Payment Method
517
+	 *        the first active Payment Method
518
+	 *    whichever is found first.
519
+	 *
520
+	 * @param EE_Registration $edited_registration
521
+	 * @return void
522
+	 * @throws EE_Error
523
+	 * @throws ReflectionException
524
+	 */
525
+	public function set_transaction_payment_method_based_on_registration_statuses(
526
+		EE_Registration $edited_registration
527
+	) {
528
+		if ($edited_registration instanceof EE_Registration) {
529
+			$transaction = $edited_registration->transaction();
530
+			if ($transaction instanceof EE_Transaction) {
531
+				$all_not_approved = true;
532
+				foreach ($transaction->registrations() as $registration) {
533
+					if ($registration instanceof EE_Registration) {
534
+						// if any REG != "Not Approved" then toggle to false
535
+						$all_not_approved = $registration->is_not_approved() ? $all_not_approved : false;
536
+					}
537
+				}
538
+				// if ALL Registrations are "Not Approved"
539
+				if ($all_not_approved) {
540
+					$transaction->set_payment_method_ID(null);
541
+					$transaction->save();
542
+				} else {
543
+					$available_payment_methods = EEM_Payment_Method::instance()->get_all_for_transaction(
544
+						$transaction,
545
+						EEM_Payment_Method::scope_cart
546
+					);
547
+					if (! empty($available_payment_methods)) {
548
+						$PMD_ID = 0;
549
+						foreach ($available_payment_methods as $available_payment_method) {
550
+							if (
551
+								$available_payment_method instanceof EE_Payment_Method
552
+								&& $available_payment_method->open_by_default()
553
+							) {
554
+								$PMD_ID = $available_payment_method->ID();
555
+								break;
556
+							}
557
+						}
558
+						if (! $PMD_ID) {
559
+							$first_payment_method = reset($available_payment_methods);
560
+							if ($first_payment_method instanceof EE_Payment_Method) {
561
+								$PMD_ID = $first_payment_method->ID();
562
+							} else {
563
+								EE_Error::add_error(
564
+									__(
565
+										'A valid Payment Method could not be determined. Please ensure that at least one Payment Method is activated.',
566
+										'event_espresso'
567
+									),
568
+									__FILE__,
569
+									__LINE__,
570
+									__FUNCTION__
571
+								);
572
+							}
573
+						}
574
+						$transaction->set_payment_method_ID($PMD_ID);
575
+						$transaction->save();
576
+					} else {
577
+						EE_Error::add_error(
578
+							__(
579
+								'Please activate at least one Payment Method in order for things to operate correctly.',
580
+								'event_espresso'
581
+							),
582
+							__FILE__,
583
+							__LINE__,
584
+							__FUNCTION__
585
+						);
586
+					}
587
+				}
588
+			}
589
+		}
590
+	}
591
+
592
+
593
+
594
+	/********************************** DEPRECATED METHODS **********************************/
595
+
596
+
597
+	/**
598
+	 * @deprecated 4.9.12
599
+	 * @return string
600
+	 */
601
+	public function old_txn_status()
602
+	{
603
+		EE_Error::doing_it_wrong(
604
+			__METHOD__,
605
+			esc_html__(
606
+				'This logic has been moved into \EE_Transaction::old_txn_status(), please use that method instead.',
607
+				'event_espresso'
608
+			),
609
+			'4.9.12'
610
+		);
611
+		return $this->_old_txn_status;
612
+	}
613
+
614
+
615
+	/**
616
+	 * @deprecated 4.9.12
617
+	 * @param string $old_txn_status
618
+	 */
619
+	public function set_old_txn_status($old_txn_status)
620
+	{
621
+		EE_Error::doing_it_wrong(
622
+			__METHOD__,
623
+			esc_html__(
624
+				'This logic has been moved into \EE_Transaction::set_old_txn_status(), please use that method instead.',
625
+				'event_espresso'
626
+			),
627
+			'4.9.12'
628
+		);
629
+		// only set the first time
630
+		if ($this->_old_txn_status === null) {
631
+			$this->_old_txn_status = $old_txn_status;
632
+		}
633
+	}
634
+
635
+
636
+	/**
637
+	 * @deprecated 4.9.12
638
+	 * @return string
639
+	 */
640
+	public function new_txn_status()
641
+	{
642
+		EE_Error::doing_it_wrong(
643
+			__METHOD__,
644
+			esc_html__(
645
+				'This logic has been removed. Please just use \EE_Transaction::status_ID() instead.',
646
+				'event_espresso'
647
+			),
648
+			'4.9.12'
649
+		);
650
+		return $this->_new_txn_status;
651
+	}
652
+
653
+
654
+	/**
655
+	 * @deprecated 4.9.12
656
+	 * @param string $new_txn_status
657
+	 */
658
+	public function set_new_txn_status($new_txn_status)
659
+	{
660
+		EE_Error::doing_it_wrong(
661
+			__METHOD__,
662
+			esc_html__(
663
+				'This logic has been removed. Please just use \EE_Transaction::set_status() instead.',
664
+				'event_espresso'
665
+			),
666
+			'4.9.12'
667
+		);
668
+		$this->_new_txn_status = $new_txn_status;
669
+	}
670
+
671
+
672
+	/**
673
+	 * reg_status_updated
674
+	 *
675
+	 * @deprecated 4.9.12
676
+	 * @return bool
677
+	 */
678
+	public function txn_status_updated()
679
+	{
680
+		EE_Error::doing_it_wrong(
681
+			__METHOD__,
682
+			esc_html__(
683
+				'This logic has been moved into \EE_Transaction::txn_status_updated(), please use that method instead.',
684
+				'event_espresso'
685
+			),
686
+			'4.9.12'
687
+		);
688
+		return $this->_new_txn_status !== $this->_old_txn_status && $this->_old_txn_status !== null ? true : false;
689
+	}
690
+
691
+
692
+	/**
693
+	 * all_reg_steps_completed
694
+	 * returns:
695
+	 *    true if ALL reg steps have been marked as completed
696
+	 *        or false if any step is not completed
697
+	 *
698
+	 * @deprecated 4.9.12
699
+	 * @param EE_Transaction $transaction
700
+	 * @return boolean
701
+	 */
702
+	public function all_reg_steps_completed(EE_Transaction $transaction)
703
+	{
704
+		EE_Error::doing_it_wrong(
705
+			__METHOD__,
706
+			esc_html__(
707
+				'This logic has been moved into \EE_Transaction::all_reg_steps_completed(), please use that method instead.',
708
+				'event_espresso'
709
+			),
710
+			'4.9.12',
711
+			'5.0.0'
712
+		);
713
+		return $transaction->all_reg_steps_completed();
714
+	}
715
+
716
+
717
+	/**
718
+	 * all_reg_steps_completed_except
719
+	 * returns:
720
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
721
+	 *        or false if any other step is not completed
722
+	 *        or false if ALL steps are completed including the exception you are testing !!!
723
+	 *
724
+	 * @deprecated 4.9.12
725
+	 * @param EE_Transaction $transaction
726
+	 * @param string         $exception
727
+	 * @return boolean
728
+	 */
729
+	public function all_reg_steps_completed_except(EE_Transaction $transaction, $exception = '')
730
+	{
731
+		EE_Error::doing_it_wrong(
732
+			__METHOD__,
733
+			esc_html__(
734
+				'This logic has been moved into \EE_Transaction::all_reg_steps_completed_except(), please use that method instead.',
735
+				'event_espresso'
736
+			),
737
+			'4.9.12',
738
+			'5.0.0'
739
+		);
740
+		return $transaction->all_reg_steps_completed_except($exception);
741
+	}
742
+
743
+
744
+	/**
745
+	 * all_reg_steps_completed_except
746
+	 * returns:
747
+	 *        true if ALL reg steps, except the final step, have been marked as completed
748
+	 *        or false if any step is not completed
749
+	 *    or false if ALL steps are completed including the final step !!!
750
+	 *
751
+	 * @deprecated 4.9.12
752
+	 * @param EE_Transaction $transaction
753
+	 * @return boolean
754
+	 */
755
+	public function all_reg_steps_completed_except_final_step(EE_Transaction $transaction)
756
+	{
757
+		EE_Error::doing_it_wrong(
758
+			__METHOD__,
759
+			esc_html__(
760
+				'This logic has been moved into \EE_Transaction::all_reg_steps_completed_except_final_step(), please use that method instead.',
761
+				'event_espresso'
762
+			),
763
+			'4.9.12',
764
+			'5.0.0'
765
+		);
766
+		return $transaction->all_reg_steps_completed_except_final_step();
767
+	}
768
+
769
+
770
+	/**
771
+	 * reg_step_completed
772
+	 * returns:
773
+	 *    true if a specific reg step has been marked as completed
774
+	 *    a Unix timestamp if it has been initialized but not yet completed,
775
+	 *    or false if it has not yet been initialized
776
+	 *
777
+	 * @deprecated 4.9.12
778
+	 * @param EE_Transaction $transaction
779
+	 * @param string         $reg_step_slug
780
+	 * @return boolean | int
781
+	 */
782
+	public function reg_step_completed(EE_Transaction $transaction, $reg_step_slug)
783
+	{
784
+		EE_Error::doing_it_wrong(
785
+			__METHOD__,
786
+			esc_html__(
787
+				'This logic has been moved into \EE_Transaction::reg_step_completed(), please use that method instead.',
788
+				'event_espresso'
789
+			),
790
+			'4.9.12',
791
+			'5.0.0'
792
+		);
793
+		return $transaction->reg_step_completed($reg_step_slug);
794
+	}
795
+
796
+
797
+	/**
798
+	 * completed_final_reg_step
799
+	 * returns:
800
+	 *    true if the finalize_registration reg step has been marked as completed
801
+	 *    a Unix timestamp if it has been initialized but not yet completed,
802
+	 *    or false if it has not yet been initialized
803
+	 *
804
+	 * @deprecated 4.9.12
805
+	 * @param EE_Transaction $transaction
806
+	 * @return boolean | int
807
+	 */
808
+	public function final_reg_step_completed(EE_Transaction $transaction)
809
+	{
810
+		EE_Error::doing_it_wrong(
811
+			__METHOD__,
812
+			esc_html__(
813
+				'This logic has been moved into \EE_Transaction::final_reg_step_completed(), please use that method instead.',
814
+				'event_espresso'
815
+			),
816
+			'4.9.12',
817
+			'5.0.0'
818
+		);
819
+		return $transaction->final_reg_step_completed();
820
+	}
821
+
822
+
823
+	/**
824
+	 * set_reg_step_initiated
825
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
826
+	 *
827
+	 * @param EE_Transaction $transaction
828
+	 * @param string         $reg_step_slug
829
+	 * @return boolean
830
+	 * @throws EE_Error*@throws ReflectionException
831
+	 * @deprecated 4.9.12
832
+	 * @access     public
833
+	 */
834
+	public function set_reg_step_initiated(EE_Transaction $transaction, $reg_step_slug)
835
+	{
836
+		EE_Error::doing_it_wrong(
837
+			__METHOD__,
838
+			esc_html__(
839
+				'This logic has been moved into \EE_Transaction::set_reg_step_initiated(), please use that method instead.',
840
+				'event_espresso'
841
+			),
842
+			'4.9.12',
843
+			'5.0.0'
844
+		);
845
+		return $transaction->set_reg_step_initiated($reg_step_slug);
846
+	}
847
+
848
+
849
+	/**
850
+	 * set_reg_step_completed
851
+	 * given a valid TXN_reg_step, this sets the step as completed
852
+	 *
853
+	 * @param EE_Transaction $transaction
854
+	 * @param string         $reg_step_slug
855
+	 * @return boolean
856
+	 * @throws EE_Error*@throws ReflectionException
857
+	 * @deprecated 4.9.12
858
+	 * @access     public
859
+	 */
860
+	public function set_reg_step_completed(EE_Transaction $transaction, $reg_step_slug)
861
+	{
862
+		EE_Error::doing_it_wrong(
863
+			__METHOD__,
864
+			esc_html__(
865
+				'This logic has been moved into \EE_Transaction::set_reg_step_completed(), please use that method instead.',
866
+				'event_espresso'
867
+			),
868
+			'4.9.12',
869
+			'5.0.0'
870
+		);
871
+		return $transaction->set_reg_step_completed($reg_step_slug);
872
+	}
873
+
874
+
875
+	/**
876
+	 * set_reg_step_completed
877
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
878
+	 *
879
+	 * @param EE_Transaction $transaction
880
+	 * @param string         $reg_step_slug
881
+	 * @return boolean
882
+	 * @throws EE_Error*@throws ReflectionException
883
+	 * @deprecated 4.9.12
884
+	 * @access     public
885
+	 */
886
+	public function set_reg_step_not_completed(EE_Transaction $transaction, $reg_step_slug)
887
+	{
888
+		EE_Error::doing_it_wrong(
889
+			__METHOD__,
890
+			esc_html__(
891
+				'This logic has been moved into \EE_Transaction::set_reg_step_not_completed(), please use that method instead.',
892
+				'event_espresso'
893
+			),
894
+			'4.9.12',
895
+			'5.0.0'
896
+		);
897
+		return $transaction->set_reg_step_not_completed($reg_step_slug);
898
+	}
899
+
900
+
901
+	/**
902
+	 * remove_reg_step
903
+	 * given a valid TXN_reg_step slug, this will remove (unset)
904
+	 * the reg step from the TXN reg step array
905
+	 *
906
+	 * @param EE_Transaction $transaction
907
+	 * @param string         $reg_step_slug
908
+	 * @return void
909
+	 *@deprecated 4.9.12
910
+	 * @access     public
911
+	 */
912
+	public function remove_reg_step(EE_Transaction $transaction, $reg_step_slug)
913
+	{
914
+		EE_Error::doing_it_wrong(
915
+			__METHOD__,
916
+			esc_html__(
917
+				'This logic has been moved into \EE_Transaction::remove_reg_step(), please use that method instead.',
918
+				'event_espresso'
919
+			),
920
+			'4.9.12',
921
+			'5.0.0'
922
+		);
923
+		$transaction->remove_reg_step($reg_step_slug);
924
+	}
925
+
926
+
927
+	/**
928
+	 *    toggle_failed_transaction_status
929
+	 * upgrades a TXNs status from failed to abandoned,
930
+	 * meaning that contact information has been captured for at least one registrant
931
+	 *
932
+	 * @param EE_Transaction $transaction
933
+	 * @return    boolean
934
+	 * @throws EE_Error*@throws ReflectionException
935
+	 * @deprecated 4.9.12
936
+	 * @access     public
937
+	 */
938
+	public function toggle_failed_transaction_status(EE_Transaction $transaction)
939
+	{
940
+		EE_Error::doing_it_wrong(
941
+			__METHOD__,
942
+			esc_html__(
943
+				'This logic has been moved into \EE_Transaction::toggle_failed_transaction_status(), please use that method instead.',
944
+				'event_espresso'
945
+			),
946
+			'4.9.12',
947
+			'5.0.0'
948
+		);
949
+		return $transaction->toggle_failed_transaction_status();
950
+	}
951
+
952
+
953
+	/**
954
+	 * toggle_abandoned_transaction_status
955
+	 * upgrades a TXNs status from failed or abandoned to incomplete
956
+	 *
957
+	 * @deprecated 4.9.12
958
+	 * @access     public
959
+	 * @param  EE_Transaction $transaction
960
+	 * @return boolean
961
+	 */
962
+	public function toggle_abandoned_transaction_status(EE_Transaction $transaction)
963
+	{
964
+		EE_Error::doing_it_wrong(
965
+			__METHOD__,
966
+			esc_html__(
967
+				'This logic has been moved into \EE_Transaction::toggle_abandoned_transaction_status(), please use that method instead.',
968
+				'event_espresso'
969
+			),
970
+			'4.9.12',
971
+			'5.0.0'
972
+		);
973
+		return $transaction->toggle_abandoned_transaction_status();
974
+	}
975 975
 }
Please login to merge, or discard this patch.